본문 바로가기

Swift

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

왕초보의 Collection View 이해하기


사진앱과 비슷한 UX를 제공하는 Collection View 만들기 1에서 만든 소스에 특정 Cell을 선택했을 때 세부 뷰로 이동하여 세부 이미지를 보여주는 기능을 만들어보려 한다.

원래 1의 BurstAlbumVC는 burst album의 사진을 가져와 대표사진을 보여주는 것이었기 때문에 다음 뷰에서는 대표사진에 해당되는 Burst Image 모두 가져와서 뿌려주는 것이다.


일단 Burst Image 뷰를 호출하는 것까지만 만들어보고 이 뷰의 내용은 다음에 채워볼 것이다.


Main.Storyboard


새로운 View Controller 하나를 추가한다. 새 Controller 파일(BurstImageVC)을 하나 만들고 이 View와 연결시킨다.

그리고 BurstAlbumVC의 Cell을 Control + Drag하여 BurstImageVC의 View에 연결시킨다.

라고 생각하였으나 생각해보니 그냥 이렇게 연결시킬 경우 선택된 셀 정보를 다음 뷰로 넘기기 애매하다는 생각이 들었다. 다시 생각해보니 collectionView 메소드 중에 didSelectItemAtIndexPath가 있다. 이걸 이용해야겠다.


collectionView(..didSelectItemAtIndexPath..) 메소드에서 performSegueWithIdentifier를 이용해서 다른 뷰를 호출하는 방법을 알아냈다. 허나 이렇게 호출한다 하더라도 xcode에서 경고 메시지가 나오는데 이를 해결하기 위해서는 Main.Storyboard에서 다음과 같이 처리해주어야 한다.


경고 메시지의 전체는 아래와 같다.

  • Main.storyboard Frame for "Burst Images Collection" will be different at run time.
  • Main.storyboard: warning: Unsupported Configuration: Scene is unreachable due to lack of entry points and does not have an identifier for runtime access via -instantiateViewControllerWithIdentifier:.

이 상태에서 빌드는 성공되지만 시뮬레이터에서 셀을 선택하는 순간 에러가 난다. (사실 에러가 나는지는 모르겠다. 내가 에러가 나는 이유는 코드의 다른 부분에서 나는 것임을 나중에 확인했다.)

이 경고들을 해결하기 위해서는 다음과 같이 해주어야 한다.


우선 뷰와 뷰를 연결시킨다. Burst Album View Controller를 클릭하고 control + drag하여 Burst Image View 위에서 놓고 팝업이 뜨면 show를 선택한다.

아래 그림에서 보면 비록 control + drag 선은 보이지 않지만 어디서 시작해서 어디서 놓았는지 알 수 있을 것이다.(좌상단 빨간 화살표에서 시작해서 우하단 빨간 화살표까지.. ^^;)


이제 경고창을 확인하면 앞의 두 경고는 사라지고 다음과 같은 새로운 경고를 볼 수 있다.

Main.storyboard: warning: Unsupported Configuration: Segues initiated directly from view controllers must have an identifier


지금 다시 화면을 보면 뷰와 뷰 사이에 화살표로 연결된 것을 볼 수 있는데 이 연결 부위를 클릭하면 나오는 Identifier가 비어있다는 경고이다. 우상단의 Identifier에 뭐라고 써주면 이 경고는 사라진다.

뭐라고 써주냐?! 그것은 바로 performSegueWithIdentifier 메소드에서 작성한 segue.identifier가 있는데 그것과 동일하게 써주면 된다.



BurstAlbumVC.swift


위 과정에 대한 코딩 내용은 이 파일에 들어있으므로 내용을 확인해보자.

BurstAnimator.zip


//
//  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
        
        // subtype이 SmartAlbumUserLibrary이면 카메라롤을 의미한다. SmartAlbumBursts이 Burst앨범을 의미한다.
        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)")
        
        

    }

    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) {
        
        // 셀이 선택되었음을 보여주기 위해서 셀의 모양에 변화를 주려 했으나 실제 셀이 선택되는 순간 뷰가 변경되므로 무의미한 코드가 되어버렸다.
        if let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as? BurstAlbumCVC {
        
            cell.layer.borderColor = UIColor.yellowColor().CGColor
            cell.layer.borderWidth = 5
            
        }
        
        // 셀이 선택될 때 BurstImageVC를 호출하기 위한 코드이다. 나중에 Burst Images를 불러오기 위하여 호출할 뷰에 burstIdentifier값을 넘겨주도록 하였다.
        // BurstImageSegue는 Main.Storyboard에서 뷰와 뷰 사이의 연결고리에 설정한 identifier 값과 동일하게 설정한다.
        let burstIdentifier = burstImages[indexPath.item].burstIdentifier
        performSegueWithIdentifier("BurstImageSegue", sender: burstIdentifier)
    }
    

    // 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.
        
        // 위 performSegueWithIdentifier가 호출될 때 넘긴 burstIdentifier를 다음 뷰에 넘겨준다.
        if segue.identifier == "BurstImageSegue" {
            if let burstImageVC = segue.destinationViewController as? BurstImageVC {
                if let burstIdentifier = sender as? String {
                    burstImageVC.burstIdentifier = burstIdentifier
                }
            }
        }
    }


}