본문 바로가기

Swift

사진앱과 비슷한 UX를 제공하는 Collection View 만들기 1

왕초보의 Collection View 이해하기


이제 Collection View에 Navgation Bar를 놓을 수 있게 되었다. 이 두 가지 기능을 이용하여 앞서 만들어본 Collection View를 좀 더 보기 좋게 만들어보고자 한다.

사진 앨범 중 카메라 롤 앨범에 접근하여 사진 목록을 가져와서 Collection View에 뿌려주는 앱을 코딩하였다. 맥북에서 디버깅을 하기 위하여 카메라 롤 앨범에 접근하였지만 원래 목적은 고속연사앨범(Burst Album) 사진을 뿌려주는 것이다.


먼저 소스부터.. 


BurstAnimator.zip



Main.Storyboard


스토리 보드는 아래와 같이 구성해보았다. 앞서 설명했었던 Collection View와 Navigation Bar를 조합한 것이다.



BurstAlbumVC.swift


여기서는 Photos에 대한 설명은 하지 않도록 하겠다. 구글링해서 누덕누덕 만든 것이고 아직 이해도가 높지 않기 때문이다. 우선 Collection View를 더 이해하고 나서 Photos(PHAsset 관련 놈들)이해할 예정이다.


앞서 Collection View 설명 글에서는 Cell이 어떻게 되던간에 Cell들이 Collection View에 뿌려지는 것을 보았는데 이제 사진앱처럼 보기 좋게 나열되도록 하고 싶어졌다.

그러다보니 셀에 대한 spacing 개념도 필요하게 되었고 셀 크기 설정, 셀 안에 있는 image view의 크기 설정 등이 필요하게 되었다.

또한 Collection View가 실행되면서 어떤 메소드가 먼저 호출되고 어떤 메소드가 나중에 호출되는지도 알 필요가 있어졌다.


먼저 apple에서 설명하는 Collection View의 Cell 구성을 간단하게 알고 넘어가는 것이 좋다. 이 링크를 한 번 보고 오자.

Collection View에서 header, footer가 어디에 있는지, cell들은 어떻게 배열되는지, cell간의 간격이 뭔지 등을 알 수 있을 것이다. 이러한 내용들을 대충 훑어본 기본 지식이 있다고 가정하고 Cell을 설정하기 시작했다.


그런데 Cell을 설정하다보니 메소드가 어떻게 호출되는지 궁금해졌다.

확인해보니 아래와 같은 순서로 메소드가 호출됨을 알 수 있었다.

  1. numberOfSectionsInCollectionView

  2. collectionView(collectionView: , numberOfItemsInSection section: Int) -> Int

  3. collectionView(collectionView: , layout : , sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize

  4. collectionView(collectionView: , layout : , minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat

  5. collectionView(collectionView: , layout : , minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat

자세한 사항은 아래 코드를 보자.

(viewDidLoad()의 주석 내용은 아직 구현되지 않은 부분도 있으므로 너무 신경쓰지 말자)

//
//  BurstAlbumVC.swift
//  BurstAnimator
//
//  Created by SeoDongHee on 2016. 4. 24..
//  Copyright © 2016년 SeoDongHee. All rights reserved.
//

import UIKit
import Photos

private var reuseIdentifier = "Cell"

class BurstAlbumVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {

    @IBOutlet weak var burstAlbumCollectionView: UICollectionView!
    
    var burstImages, burstAlbum, burst: PHFetchResult!
    var options = PHFetchOptions()
    let imageManager = PHCachingImageManager()
    
    var scale: CGFloat!
    var targetSizeX: CGFloat!

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        // UX 설정
        // 1. Collection View
        burstAlbumCollectionView.backgroundColor = UIColor.whiteColor()
        // Collection View 설정
        burstAlbumCollectionView.delegate = self
        burstAlbumCollectionView.dataSource = self
        
        // 화면의 가로 사이즈를 구한다.
        // 화면이 landscape라면 세로 사이즈를 구한다.
        scale = UIScreen.mainScreen().scale
        // 화면의 좁은 쪽을 기준으로 3등분한다.
        targetSizeX = CGRectGetWidth(UIScreen.mainScreen().bounds) * scale / 3
        print("targetSizeX = \(targetSizeX)")
        
        options.includeAllBurstAssets = true
        
        burstAlbum = PHAssetCollection.fetchAssetCollectionsWithType(.SmartAlbum, subtype: .SmartAlbumUserLibrary, options: options)
        print("assetCollection.count = \(burstAlbum.count)")
        
        
        let collection = burstAlbum.firstObject as! PHAssetCollection //burstAlbum[0] as! PHAssetCollection
        burstImages = PHAsset.fetchKeyAssetsInAssetCollection(collection, options: options)
        print("images.count = \(burstImages.count)")
        
        
//        if ((burstImages[0].representsBurst) != nil) {
//            if let bi = burstImages[0].burstIdentifier {
//                burst = PHAsset.fetchAssetsWithBurstIdentifier(bi!, options: options)
//                print("burst = \(burst.count)")
//            }
//        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    
    
    // Collection View가 처리되는 과정은 아래 메소드의 순서대로이다.
    // 1. numberOfSectionsInCollectionView가 가정 먼저 실행되어 Section의 개수를 파악한다.
    func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
        
        // 이 샘플에서는 Section은 1개이다.
        print("numberOfSectionsInCollectionView = 1")
        return 1
    }
    
    // 2. numberOfItemsInSection가 실행되어 Section당 Item의 개수를 파악한다.
    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        
        print("numberOfItemsInSection = \(burstImages.count)")
        // 이 샘플에서는 images의 개수가 cell의 개수이다.
        return burstImages.count
    }
    
    // 3. 셀의 크기 설정이 이루어진다.
    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
        
        // 사진앱과 가장 비슷한 UX를 제공하려면 minimumInteritemSpacingForSectionAtIndex, minimumLineSpacingForSectionAtIndex 둘 다 1로 설정하는 것이다.
        // 이 크기를 감안해서 Cell의 크기를 설정해 주어야 한다.
        // 만약 Spacing을 고려하지 않고 Cell 크기를 설정하게 되어 미묘하게 Cell 크기가 가로 크기를 넘길 경우 이쁘지 않은 레이아웃을 보게 될 것이다.
        // 그러므로 최종 Cell의 크기는 Spacing 값을 참조하여 빼주도록 한다.
        targetSizeX = burstAlbumCollectionView.frame.width / 3 - 1 // Min Spacing For Cell
        print("Cell 크기 설정 - targetSizeX = \(targetSizeX)")
        
        return CGSizeMake(targetSizeX, targetSizeX)
    }
    
    
    // 4. Cell 내부 아이템의 최소 스페이싱을 설정한다. 셀간의 가로 간격이라고 생각하면 된다.
    // 상세한 내역은 여기 참조 : https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/UsingtheFlowLayout/UsingtheFlowLayout.html
    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat {
        
        print("minimumInteritemSpacingForSectionAtIndex 설정")
        return 1 as CGFloat
    }
    
    // 5. Cell 간 라인 스페이싱을 설정한다. 셀간의 세로 간격이라고 생각하면 된다.
    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat {
        
        print("minimumLineSpacingForSectionAtIndex 설정")
        return 1 as CGFloat
    }
    
    // 셀의 내용을 설정하는 메소드
    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        
        if let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as? BurstAlbumCVC {
            
            cell.imageManager = imageManager
            print("Cell 내용 설정 - targetSizeX = \(targetSizeX)")
            cell.targetSizeX = targetSizeX
            cell.imageAsset = burstImages[indexPath.item] as? PHAsset
            
            return cell
        }
        else {
            return UICollectionViewCell()
        }
    }
    
    // 셀이 선택되었을 때를 설정하는 메소드
    func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
        
        // 나중에 정의
    }
    
    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        // Get the new view controller using segue.destinationViewController.
        // Pass the selected object to the new view controller.
    }
    */

}

위 코드대로 실행시키면 아래 그림과 같이 정상적으로 사진들이 배열된다.


그러나 위 코드에서 spacing에 해당하는 만큼 Cell 크기를 줄여주지 않거나 하게 되면 1픽셀의 차이라도 아래 그림처럼 Cell 배열이 무너지게 된다.