apple/iOS

iOS Snapkit 10 | CollectionView 코드로 구성하는 법 03

lgvv 2021. 8. 23. 00:44

✅ 이번 시간에는 rx 적용 및 컬렉션 뷰 Flow의 값을 줄 때, 상하 간격도 주는 것을 함께 해보았다.


결과와 코드를 보면서 함께하자.

 

⭐️ 진짜 핵심은 제일 아래쪽에 위치하고 있습니다:)

우리가 원하는 결과

 

✅ 코드리뷰

//
//  File.swift
//  SnapKit_practice
//
//  Created by Hamlit Jason on 2021/08/22.
//

import UIKit
import RxDataSources
import RxSwift
import RxCocoa
import CoreLocation
import Foundation
import Differentiator
import Then

// Cell
class MyCollectionViewCell3 : UICollectionViewCell {
    static let identifier = "cell3"
    
    var img = UIImageView().then {
        $0.translatesAutoresizingMaskIntoConstraints = false
        $0.image = UIImage(named: "testImage")
    }
    
    var label = UILabel().then {
        $0.translatesAutoresizingMaskIntoConstraints = false
        $0.text = "상어상어"
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.cellSetting()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func cellSetting() {
        self.backgroundColor = .gray
        self.addSubview(img)
        self.addSubview(label)
        
        img.contentMode = .scaleToFill
        img.snp.makeConstraints {
            $0.leading.top.trailing.equalTo(0)
            $0.bottom.equalTo(-20)
        }
        label.snp.makeConstraints {
            $0.leading.bottom.trailing.equalTo(0)
            $0.top.equalTo(img.snp.bottom)
        }
    }
}

class ViewController10 : UIViewController {
    
    var array = ["first","second","third","fourth","fifth","6","7","8","9","10","11","12"]
    
    var collectionView : UICollectionView = {
        var layout = UICollectionViewFlowLayout()
        layout.minimumLineSpacing = 0
        layout.scrollDirection = .vertical
        layout.sectionInset = .zero
        
        let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
        cv.backgroundColor = .green
        return cv
    }()
    
    let sectionInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let data = Observable<[String]>.of(self.array)
        
        
        view.addSubview(collectionView)
        collectionView.delegate = self // rx쓰더라도 이거 꼭 연결해야해
        // collectionView.dataSource = self
        
        autoLayout()
        collectionView.register(MyCollectionViewCell3.self, forCellWithReuseIdentifier: MyCollectionViewCell3.identifier)
        
        
        data.asObservable()
            .bind(to: collectionView.rx
                    .items(
                        cellIdentifier: MyCollectionViewCell3.identifier,
                        cellType: MyCollectionViewCell3.self)
            ) { index, recommend, cell in
                cell.img.image = UIImage(named: "testImage")
                cell.label.text = "index \(index)"
            }
        
    }
}

extension ViewController10 : UICollectionViewDelegateFlowLayout {
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let width = collectionView.bounds.width
        let height = collectionView.bounds.height
        
        let itemsPerRow: CGFloat = 3 // 최초 화면에 보여져야하는 row rottn
        let widthPadding = sectionInsets.left * (itemsPerRow + 1)
        var itemsPerColumn: CGFloat = 4 // 최초 화면에 보여져야하는 columns 갯수
        itemsPerColumn = ceil(itemsPerColumn)
        print(itemsPerColumn)
        let heightPadding = sectionInsets.top * (itemsPerColumn + 1)
        
        let cellWidth = (width - widthPadding) / itemsPerRow
        let cellHeight = (height - heightPadding) / itemsPerColumn
        
        
        return CGSize(width: cellWidth, height: cellHeight)
    }
    
    // case A
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        return sectionInsets
    }
    
    // case B
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return sectionInsets.left
    }
}

extension ViewController10 {
    
    private func autoLayout() {
        collectionView.snp.makeConstraints {
            $0.edges.equalTo(view.safeAreaLayoutGuide)
                .inset(UIEdgeInsets(top: 0, left: 0, bottom: 200, right: 0))
        }
    }
}

위에 코드를 보면 case A와 case B를 적어뒀는데 아래 코드를 보면서 각 부분이 주석처리 되었을 때 달라지는 변화를 관찰해보자.

 

왼쪽 : case A만 주석처리

가운데 : case A,B 모두 주석처리 

오른쪽 : case B만 주석처리

왼쪽, 가운데, 오른쪽 

코드의 결과를 보면 

case B는 상하의 간격을 조절하는 코드다

case A는 inset으로 뷰의 가장자리에서 거리를 조절하는 코드이다.

 

❗️✅ 위의 코드의 치명적인 단점 

위의 코드에서는

itemsPerRow 와 itemsPerColumn를 하드 코딩하게 주고 있다.

이런 경우에 어떤 문제가 발생하냐면 우리가 초록생 영역에 우선적으로 보여져야하는 Row와 Coloumn의 갯수가 정해져 버리게 된다.

물론 스크롤은 가능하나 화면에 딱 맞게 보여져야 함으로 컬렉션 뷰의 Height에 따라서 컬렉션 뷰 셀의 비율을 우리가 원하는대로 가져가지 못하는 경우가 생길 수 있다.

 

아래의 사진을 보면 문제점을 정확하게 짚어낼 수 있다.

위 코드의 치명적인 단점.

 

🟠 만약 우리가 위의 코드로 레이아웃을 사용하고자 한다면, 더 세심한 주의가 필요하다고 생각된다.

 

✅ 그렇다면 조금 더 common하게 사용할 방법은 없을까?

있지 당연히!! 지금 여기부터가 정말정말 핵심 부분이니까 꼭 집중해서 보기 바란다.

//
//  File.swift
//  SnapKit_practice
//
//  Created by Hamlit Jason on 2021/08/22.
//

import UIKit
import RxDataSources
import RxSwift
import RxCocoa
import CoreLocation
import Foundation
import Differentiator
import Then

// Cell
class MyCollectionViewCell3 : UICollectionViewCell {
    static let identifier = "cell3"
    
    var img = UIImageView().then {
        $0.translatesAutoresizingMaskIntoConstraints = false
        $0.image = UIImage(named: "testImage")
    }
    
    var label = UILabel().then {
        $0.translatesAutoresizingMaskIntoConstraints = false
        $0.text = "상어상어"
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.cellSetting()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func cellSetting() {
        self.backgroundColor = .gray
        self.addSubview(img)
        self.addSubview(label)
        
        img.contentMode = .scaleToFill
        img.snp.makeConstraints {
            $0.leading.top.trailing.equalTo(0)
            $0.bottom.equalTo(-20)
        }
        label.snp.makeConstraints {
            $0.leading.bottom.trailing.equalTo(0)
            $0.top.equalTo(img.snp.bottom)
        }
    }
}

class ViewController10 : UIViewController {
    
    var array = ["first","second","third","fourth","fifth","6","7","8","9","10","11","12"]
    
    var collectionView : UICollectionView = {
        var layout = UICollectionViewFlowLayout()
        layout.minimumLineSpacing = 0
        layout.scrollDirection = .vertical
        layout.sectionInset = .zero
        
        let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
        cv.backgroundColor = .green
        return cv
    }()
    
    let sectionInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let data = Observable<[String]>.of(self.array)
        
        
        view.addSubview(collectionView)
        collectionView.delegate = self // rx쓰더라도 이거 꼭 연결해야해
        // collectionView.dataSource = self
        
        autoLayout()
        collectionView.register(MyCollectionViewCell3.self, forCellWithReuseIdentifier: MyCollectionViewCell3.identifier)
        
        
        data.asObservable()
            .bind(to: collectionView.rx
                    .items(
                        cellIdentifier: MyCollectionViewCell3.identifier,
                        cellType: MyCollectionViewCell3.self)
            ) { index, recommend, cell in
                cell.img.image = UIImage(named: "testImage")
                cell.label.text = "index \(index)"
            }
        
    }
}

extension ViewController10 : UICollectionViewDelegateFlowLayout {
    
    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 - 20 - itemSpacing * 2) / 3
        //let height : CGFloat = width * 10/7 + textAreaHeight
        
        return CGSize(width: width, height: width)
    }
    
    // case A
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        return sectionInsets
    }
    
    // case B
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return sectionInsets.left
    }
}

extension ViewController10 {
    
    private func autoLayout() {
        collectionView.snp.makeConstraints {
            $0.edges.equalTo(view.safeAreaLayoutGuide)
                .inset(UIEdgeInsets(top: 0, left: 0, bottom: 200, right: 0))
        }
    }
}

위의 코드를 보면 UICollectionViewDelegateFlowLayout 부분만 달라진 것을 확인할 수 있다.

width의 값을 설정할때 -20을 하드코딩으로 처리한 이유는 sectionInsets으로 인하며 좌우 공간이 20만큼 빠지기 때문에 그냥 하드코딩으로 넣어주었다.

이렇게 설정하면 정말정말 예쁘게 컬렉션 뷰를 활용할 수 있다!

바뀐 코드에 대한 결과