컬렉션 뷰(Collection View)란 위의 그림에서 볼 수 있듯이 테이블처럼 일정한 가로 세로 개수를 가진 목록 오브젝트를 뜻합니다. 이 테이블 뷰를 생성하는 방법을 스위프트(Swift), 스토리보드(Storyboard) 기준으로 알아보겠습니다.

 

1) 컬렉션 뷰(Collection View) 오브젝트 추가

  • 오른쪽 상단의 [+] 버튼을 클릭합니다.
  • Collection View 오브젝트를 메인 뷰로 끌어와 추가합니다.

 

  • 컬렉션 뷰를 양쪽으로 잡아당겨 화면에 꽉 채웁니다.

 

2) 컬렉션 뷰에 오토 레이아웃 설정

  • 컬렉션 뷰를 마우스 오른쪽 버튼 또는 control + 마우스 왼쪽 버튼을 누른 채로 View (위 그림의 파란색 영역)으로 드래그합니다.

 

  • shift 버튼을 누른 채 메뉴를 클릭해 위의 그림처럼 체크박스를 표시합니다. 이후 제약(constraints)가 생기며 자동 레이아웃이 설정됩니다.

 

3) 뷰 컨트롤러에서 UICollectionViewDataSource, UICollectionViewViewDelegate 프로토콜 상속받기

먼저 컬렉션 뷰를 표시하기 위해 컨트롤러에서 반드시 제공해야 하는 정보가 2가지 있습니다.

  1. 컬렉션 뷰의 셀은 총 몇 개? (UICollectionViewDataSource)

  2. 컬렉션 뷰를 어떻게 보여줄 것인가 ? (UICollectionViewDelegate)

이러한 정보를 제공하기 위해
UICollectionViewDataSource, UICollectionViewDelegate
프로토콜을 구현해야 합니다. 뷰 컨트롤러 클래스의 최상단 부분에 다음 두 개 프로토콜을 추가합니다.

import UIKit

class ViewController: UIViewController,
                      UICollectionViewDataSource,
                      UICollectionViewDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }


}

위 부분을 추가하면 아래와 같은 에러가 발생할 것입니다.[Fix] 버튼을 눌러 프로토콜 조각(protocol stub)을 추가합니다.

눌렀다면 다음 부분이 추가될 것입니다.

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    // ... code ...
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    // ... code ...
}

두 함수 모두 UICollectionViewDataSource와 관련된 부분입니다.

첫 번째 함수는 콜렉션 뷰에 총 몇 개의 셀(cell)을 표시할 것인지를 구현합니다. Int 값을 리턴하라고 나와 있으므로 return 10 등으로 작성합니다.

두 번째 함수는 해당 cell에 무슨 셀을 표시할 지를 결정합니다. UICollectionViewCell을 리턴하라고 합니다.

 

예제 코드를 작성하면 아래와 같습니다.

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return 10
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as?
            UICollectionViewCell else {
        return UICollectionViewCell()
    }
    
    return cell
}
  • dequeReusableCell은 재활용 가능한 셀을 큐(queue)의 형태로 추가/제거합니다. 만약 재사용 큐에 셀이 존재한다면 현재 재사용 큐에 있는 셀을 dequeue(꺼냄)해주고, 재사용 큐에 셀이 없으면 새로운 셀을 생성합니다.
  • for:는 셀의 위치를 ​​지정하는 색인 ​​경로입니다. 항상 데이터 원본 개체에서 제공하는 인덱스 경로를 지정해야 합니다. indexPath를 적으면 됩니다.
  • cell:은 아까 추가한 테이블 뷰 셀의 구분자(identifier)를 적는 곳입니다. 이 구분자를 통해 셀을 가져오고 그 셀을 재활용합니다. 구분자 추가 방법은 밑에서 설명하겠습니다.

 

4) 컬렉션 뷰 셀에 구분자(identifier) 추가

  • 메인 스토리보드에서 컬렉션 뷰 셀을 선택한 뒤, 오른쪽에서 Show the Attributes Inspector(위 그림의 빨간 박스 아이콘)를 선택한 뒤, Identifier에 아까 프로토콜 조각에서 지정했던 cell 구분자를 입력합니다.
  • 셀이 표시되는지 구분하기 위해 Background에 색상을 추가합니다.

 

5) 컬렉션 뷰와 메인 뷰 컨트롤러에 Datasource, Delegate 연결

 

 

  • 테이블 뷰에서 control + 마우스 왼쪽 또는 마우스 오른쪽 버튼을 이용에 파란색 영역(View Controller)으로 드래그합니다.

 

  • 위와 같은 메뉴가 나타나는데 dataSource, delegate 메뉴를 클릭하여 체크해줍니다.

 

  • 컬렉션 뷰를 선택한 다음 오른쪽 옵션 창에서 Show the Connection Inspector 버튼을 클릭해 위와 같이 연결되었는지 확인합니다.

 

동작 확인

 

추가1) 커스텀 셀 작성

커스텀 셀의 작성 원리는 iOS 프로그래밍: 테이블 뷰 (Swift, 스토리보드) 2 – 커스텀 셀(custom cell) 추가 에서 나온 것과 매우 유사합니다. 이 부분의 내용을 알고 있다고 가정하고 설명하겠습니다.

 

먼저 Assets.xcassets에 사진 파일들을 저장합니다.

 

 

컬렉션 뷰 내의 셀의 사이즈를 조정하고, Image 오브젝트와 Label 오브젝트를 추가합니다.

 

사이즈를 작성한 뒤, 새로운 클래스를 생성합니다.

class Cell: UICollectionViewCell {
    @IBOutlet weak var imgView: UIImageView!
    @IBOutlet weak var nameLabel: UILabel!
    
    func update(info: PictureInfo) {
        imgView.image = info.image
        nameLabel.text = info.name
    }
}

 

커스텀 셀을 선택한 후, Identity Inspector 메뉴에서 Class 를 위에서 작성한 Cell 클래스로 지정합니다.

 

메인 스토리보드에서 Assistant를 활성화한 뒤, 오브젝트와 @IBOutlet 을 연결합니다.

 

다음, ImageInfo 구조체를 생성합니다.

struct ImageInfo {
    let name: String
    
    var image: UIImage? {
        return UIImage(named: "\(name).jpg")
    }
    
    init (name: String) {
        self.name = name
    }
}

 

이미지 데이터를 담는 ViewModel을 생성합니다.

// view model
class ImageViewModel {
    let imageInfoList: [ImageInfo] = [
        ImageInfo(name: "Chrysanthemum"),
        ImageInfo(name: "Desert"),
        ImageInfo(name: "Hydrangeas"),
        ImageInfo(name: "Koala"),
        ImageInfo(name: "Lighthouse"),
        ImageInfo(name: "Penguins"),
        ImageInfo(name: "Tulip"),
    ]
    
    var countOfImageList: Int {
        return imageInfoList.count
    }
    
    func imageInfo(at index: Int) -> ImageInfo {
        return imageInfoList[index]
    }
}

 

컬렉션 뷰 표시와 관련된 코드를 편집합니다.

let viewModel = ImageViewModel() // 뷰모델 변수를 추가

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return viewModel.countOfImageList // 뷰모델에서 카운트 가져옴
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as?
            Cell else {
        return UICollectionViewCell()
    }
    
    let imageInfo = viewModel.imageInfo(at: indexPath.item) // indexPath.item을 기준으로 뷰모델에서 ImageInfo 가져옴
    cell.update(info: imageInfo) // 해당 셀을 업데이트
    return cell

}

// 셀이 선택되었을 때
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    print(indexPath.row)
}

 

그리고 마지막으로 해당 셀을 클릭하면, 파일 이름이 나오는 경고창을 만들어 보도록 하겠습니다.

// 셀이 선택되었을 때
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    
    let imageInfo = viewModel.imageInfo(at: indexPath.item)
    
    let dialog = UIAlertController(title: "\(imageInfo.name)", message: "", preferredStyle: .alert)
    let action = UIAlertAction(title: "확인", style: UIAlertAction.Style.default)

    dialog.addAction(action)

    self.present(dialog, animated: true, completion: nil)

}

 

이것들을 종합한 ViewController.swift의 내용은 다음과 같습니다.

import UIKit

struct ImageInfo {
    let name: String
    
    var image: UIImage? {
        return UIImage(named: "\(name).jpg")
    }
    
    init (name: String) {
        self.name = name
    }
}


class ViewController: UIViewController,
                      UICollectionViewDataSource,
                      UICollectionViewDelegate {
    
    let viewModel = ImageViewModel() // 뷰모델 변수를 추가합니다.
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return viewModel.countOfImageList // 뷰모델에서 카운트 가져옴
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as?
                Cell else {
            return UICollectionViewCell()
        }
        
        let imageInfo = viewModel.imageInfo(at: indexPath.item) // indexPath.item을 기준으로 뷰모델에서 ImageInfo 가져옴
        cell.update(info: imageInfo) // 해당 셀을 업데이트
        return cell

    }
    
    // 셀이 선택되었을 때
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        
        let imageInfo = viewModel.imageInfo(at: indexPath.item)
        
        let dialog = UIAlertController(title: "\(imageInfo.name)", message: "", preferredStyle: .alert)
        let action = UIAlertAction(title: "확인", style: UIAlertAction.Style.default)

        dialog.addAction(action)

        self.present(dialog, animated: true, completion: nil)

    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }


}

class Cell: UICollectionViewCell {
    @IBOutlet weak var imgView: UIImageView!
    @IBOutlet weak var nameLabel: UILabel!
    
    func update(info: ImageInfo) {
        imgView.image = info.image
        nameLabel.text = info.name
    }
    
}

// view model
class ImageViewModel {
    let imageInfoList: [ImageInfo] = [
        ImageInfo(name: "Chrysanthemum"),
        ImageInfo(name: "Desert"),
        ImageInfo(name: "Hydrangeas"),
        ImageInfo(name: "Jellyfish"),
        ImageInfo(name: "Koala"),
        ImageInfo(name: "Lighthouse"),
        ImageInfo(name: "Penguins"),
        ImageInfo(name: "Tulips"),
    ]
    
    var countOfImageList: Int {
        return imageInfoList.count
    }
    
    func imageInfo(at index: Int) -> ImageInfo {
        return imageInfoList[index]
    }
}

 

추가2) 디바이스 크기에 따른 셀 사이즈 조정 예제

크기를 임의로 조정하려면 뷰 컨트롤러 클래스에 UICollectionViewDelegateFlowLayout 를 추가하고 스토리보드에서 컬렉션 뷰의 Estimate SizeNone으로 설정합니다.

case 1 – 가로 2칸, 기기 사이즈에 맞추기
// 사이즈 결정
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    
    let itemSpacing: CGFloat = 10
    let textAreaHeight: CGFloat = 65
    
    let width: CGFloat = (collectionView.bounds.width - itemSpacing) / 2
    let height: CGFloat = width * 10/7 + textAreaHeight
    return CGSize(width: width, height: height)
}

 

case 2 – 가로 세로 칸수를 정하고 싶을 때 (출처)
let sectionInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    let width = collectionView.frame.width
    let height = collectionView.frame.height
    let itemsPerRow: CGFloat = 2
    let widthPadding = sectionInsets.left * (itemsPerRow + 1)
    let itemsPerColumn: CGFloat = 3
    let heightPadding = sectionInsets.top * (itemsPerColumn + 1)
    let cellWidth = (width - widthPadding) / itemsPerRow
    let cellHeight = (height - heightPadding) / itemsPerColumn
    
    return CGSize(width: cellWidth, height: cellHeight)
    
}

 

문의 | 코멘트 또는 yoonbumtae@gmail.com


카테고리: Swift


0개의 댓글

답글 남기기

Avatar placeholder

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다