Post

UIKit - UICollectionView 개념 및 사용 방법

UIKit - UICollectionView 개념 및 사용 방법

UICollectionView란?

UICollectionViewUITableView와 비슷하지만 커스텀 레이아웃을 통해 UItableView보다 더 다양한 화면을 구성할 수 있음.

UITableView와 유사하게 셀을 등록해주고, DataSource, Delegate를 통해 셀의 개수, 사용자 동작 처리가 가능함.

차이점으로는 UICollectionView에는 Layout을 반드시 설정해줘야만함.

UITableView와는 달리 Layout을 반드시 설정해줘야하며, 설정하지 않을 경우 에러가 발생함.

구현 예시

iTunes Search API를 통해 이미지를 가져와 CollectionView로 표시하는 예시는 아래와 같음.

iTunes Search API Document: link

ViewController.swif

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import UIKit

class ViewController: UIViewController {
    
    private let urlSession = URLSession.shared
    
    // CollectionView 셀 수
    private let columnCount: CGFloat = 3
    
    // CollectionView 간격 크기
    private let columnSpacing: CGFloat = 10
    
    let collectionView: UICollectionView = {
        
        // CollectionView Layout 인스턴스 생성
        let layout = UICollectionViewFlowLayout()
        
        // CollectionView 스크롤 방향 설정
        layout.scrollDirection = .vertical
        
        return UICollectionView(frame: .zero, collectionViewLayout: layout) // CollectionView 생성
    }()
    
    // 데이터 배열
    var dataArray: [Software] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 초기 데이터 세티
        fetchData { result in
            
            switch result{
            case .success(let data):
                
                guard let array = data else { return }
                
                self.dataArray = array
                print("dataArray count : \(self.dataArray.count)")
                
                DispatchQueue.main.async {
                    // CollectionView 데이터 로드
                    self.collectionView.reloadData()
                }
               
                
            case .failure(let error):
                print(error.localizedDescription)
            }
        }
        
        // CollectionView 설정
        setupCollectionView()
    }
    
    func setupCollectionView(){
        
        // 오토 레이아웃 설정
        setupAutoLayout()
        
        // CollectionViewCell 등록
        collectionView.register(CustomCollectionViewCell.self, forCellWithReuseIdentifier: "CustomCollectionViewCell")
        
        // CollectionView DataSource 설정
        collectionView.dataSource = self
        
        // CollectionView Delegate 설정
        collectionView.delegate = self
    }
    
    func setupAutoLayout(){
        view.addSubview(collectionView)
        
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            collectionView.topAnchor.constraint(equalTo: view.topAnchor)
        ])
    }
}

// MARK: - CollectionView DataSource
extension ViewController: UICollectionViewDataSource{
    
    // CollectionView 요소 수 반환
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        
        print("count : \(dataArray.count)")
        
        return dataArray.count
    }
    
    // CollectionView 셀 반환
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCollectionViewCell", for: indexPath)
            as! CustomCollectionViewCell
        
        cell.image = dataArray[indexPath.row].imageUrl
        
        return cell
    }
    
    // iTunes Search API 호출
    func fetchData(completionHandler: @escaping (Result<[Software]?, Error>) -> Void){
        
        let urlStr = "https://itunes.apple.com/search?term=app&media=software"
        print("urlStr : \(urlStr)")
        
        guard let url = URL(string: urlStr) else {
            print("URL 변환 실패")
            return
        }
        
        URLSession.shared.dataTask(with: url) { data, _, error in
            if error != nil{
                print("에러 발생")
                completionHandler(.failure(Error.self as! Error))
                return
            }
            
            guard let data = data else {
                print("데이터 요청 실패")
                completionHandler(.failure(Error.self as! Error))
                return
            }
            
            guard let softwareResult = try? JSONDecoder().decode(SoftwareData.self, from: data) else {
                print("파싱 실패")
                completionHandler(.failure(Error.self as! Error))
                return
            }
            
            completionHandler(.success(softwareResult.results))
        }.resume()
    }
}

// MARK: - CollectionView Layout Delegate
extension ViewController: UICollectionViewDelegateFlowLayout{
    
    // Cell 크기 설정
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

        // 전체 간격 사이즈
        let totalSpacing = columnSpacing * (columnCount - 1)
        
        // 간격 사이즈를 제외한 전체 화면 크기
        let viewWidth = self.view.frame.width - totalSpacing
        
        // 전체 화면 크기를 한 줄에 들어갈 셀 개수로 나누기 = 셀 하나의 크기
        let cellWidth = viewWidth / columnCount
        
        return CGSize(width: cellWidth, height: cellWidth)
    }
    
    // Verticle 간격 설정
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
    
        return self.columnSpacing
    }
    
    // Horizontal 간격 설정
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
        
        return self.columnSpacing
    }
}

CustomCollectionViewCell.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import UIKit

class CustomCollectionViewCell: UICollectionViewCell {
    
    let imageView: UIImageView = {
        let imageView = UIImageView()
        
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.backgroundColor = .black
        
        return imageView
    }()
    
    var image: String? {
        didSet{
            loadImage()
        }
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        addSubview(imageView)
        
        setupAutoLayout()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }
    
    func setupAutoLayout(){
        NSLayoutConstraint.activate([
            imageView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 0),
            imageView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 0),
            imageView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0),
            imageView.topAnchor.constraint(equalTo: self.topAnchor, constant: 0)
        ])
    }
    
    func loadImage(){
        
        guard let urlStr = image, let url = URL(string: urlStr) else { return }
        
        URLSession.shared.dataTask(with: url) { (data, _, _) in
            guard let data = data else { return }
            
            DispatchQueue.main.async {
                self.imageView.image = UIImage(data: data)
            }
        }.resume()
    }
}

Software.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import Foundation

struct SoftwareData: Codable{
    var resultCount: Int
    var results: [Software]
}

struct Software: Codable{
    
    var imageUrl: String?
    
    enum CodingKeys: String, CodingKey {
        case imageUrl = "artworkUrl100"
    }
}

결과

image

This post is licensed under CC BY 4.0 by the author.