LBTATools의 LBTAList로 CollectionView에 Cell 추가하기

  1. 일반적인 Cell 생성 방법
  2. LBTAListCell class 생성
  3. Cell 요소 디자인
    • UIStackView.Alignment
    • padding, margin
    • minimumLineSpacing

1. 일반적인 Cell 생성 방법

collectionView에서 Custom Cell을 사용하는 방법은 다음과 같다.

identifier 설정

사용할 Custom Cell에 대해서 identifier를 설정한다.

스토리보드에서는 직접 Attributes Inspector에서 Identifier를 설정해준다.

programmatically(스토리보드를 사용하지 않으면) collectionView를 선언한 ViewController에서 String type property로 identifier로 register함수를 통해 등록한다.

register

Document에는 다음과 같이 register함수를 설명했다.

Register a class for use in creating new collection view cells.

즉 class를 등록하는 것이다. register 함수의 첫번째 인자로는 cellClass가 들어간다.

    fileprivate let cellId = "cellId"

    // viewDidLoad()
    collectionView.register(T.self, forCellWithReuseIdentifier: cellId)

LBTAListController에서는 제네릭으로 선언되어있는 T type이 사용되었다.

dequeueReusableCell

collecionView에서도 tableView와 마찬가지로 cell들을 dequeue해서 재사용하는 방식을 사용한다.

    fileprivate let cellId = "cellId"

    override open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! T
        cell.item = items[indexPath.row]
        cell.parentController = self
        return cell
    }

dequeuing 할 때에는 type casting은 필수이다! as!

cell.parentController = self

parentController is set to the ListHeaderController that is rendering this cell. This is useful for scenarios where a cell contains a UIButton and you want to use addTarget(…) to trigger an action in the controller.

추후에 UIButton을 cell에 추가할 때 addTarget을 사용하여 UIButton의 action을 처리할 때 편리하게 사용하기 위한 것.

또한 이 parentController 는 optional로 LBTAListCell에 선언되어있다.

2. LBTAListCell class 생성

LBTA github

일단 UIColor로 dummy cell을 만든다.

class RecentMessageCell: LBTAListCell<UIColor> {
    override var item: UIColor! {
        didSet {
            backgroundColor = item
        }
    }
}

didSet을호 생성된 item객체들을 모두 처리하여 cell로 만들어준다. collectionViewCell의 view 요소들에 data를 넣어주는 initializer 같은 역할을 한다.

    override func viewDidLoad() {
        super.viewDidLoad()
        
        items = [.red, .blue, .green]
    }

sizeForItemAt

sizeForItemAt 에서 size를 원하는 값으로 설정, UICollectionViewDelegateFlowLayout 프로토콜을 채택해야지 사용할 수 있다.

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return .init(width: view.frame.width, height: 120)
    }

collectionView.contentInset.top 과 collectionView.scrollIndicatorInsets.top은 동시에 주는 것이 자연스럽다.

LBTAListCell 세부 구현 내용

open class LBTAListCell<T>: UICollectionViewCell {
    
    /// item is fed in automatically from ListHeaderController
    open var item: T!
    
    /// parentController is set to the ListHeaderController that is rendering this cell.  This is useful for scenarios where a cell contains a UIButton and you want to use addTarget(...) to trigger an action in the controller.
    open weak var parentController: UIViewController?
    
    /// 90% of lists need a separator line
    public let separatorView = UIView(backgroundColor: UIColor(white: 0.6, alpha: 0.5))
    
    /// This convenience method sets up the separate line with some left padding
    open func addSeparatorView(leftPadding: CGFloat = 0) {
        addSubview(separatorView)
        separatorView.anchor(top: nil, leading: leadingAnchor, bottom: bottomAnchor, trailing: trailingAnchor, padding: .init(top: 0, left: leftPadding, bottom: 0, right: 0), size: .init(width: 0, height: 0.5))
    }
    /// This sets up the separator to be anchored to some leading anchor
    open func addSeparatorView(leadingAnchor: NSLayoutXAxisAnchor) {
        addSubview(separatorView)
        separatorView.anchor(top: nil, leading: leadingAnchor, bottom: bottomAnchor, trailing: trailingAnchor, size: .init(width: 0, height: 0.5))
    }
    
    override public init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .white
        setupViews()
    }
    
    /// In your custom ListCell classes, just override setupViews() to provide custom behavior.  We do this to avoid overriding init methods.
    open func setupViews() {}
    
    required public init?(coder aDecoder: NSCoder) {
        fatalError()
    }
}

LBTAHeaderListController에서 Header부분을 제외한 구현 부분

LBTAListController는 LBTAListHeaderController<T, U, UICollectionReusableView> 를 상속한 class이다. Header부분에 UICollectionReusableView 을 넣어서 빈 header이다.

    open var items = [U]() {
        didSet {
            collectionView.reloadData()
        }
    }
    
    fileprivate let cellId = "cellId"
    
    override open func viewDidLoad() {
        super.viewDidLoad()
        collectionView.backgroundColor = .white
        
        collectionView.register(T.self, forCellWithReuseIdentifier: cellId)
   
    /// ListHeaderController automatically dequeues your T cell and sets the correct item object on it.
    override open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! T
        cell.item = items[indexPath.row]
        cell.parentController = self
        return cell
    }

didSet에는 items가 바뀌면 collectionView를 reload 해준다.

3. Cell 요소 디자인

cell도 view에 속하기 때문에 view처럼 디자인 하면 된다.

setupViews()

cell에 사용할 view properties를 추가하게 되면 initializer가 필요하게 되는데, setupViews()를 사용하면 이를 손쉽게 피할 수 있다. 그리고 viewDidLoad() 와 같은 역할을 한다.

구현 내용

    override public init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .white
        setupViews()
    }
    
    /// In your custom ListCell classes, just override setupViews() to provide custom behavior.  We do this to avoid overriding init methods.
    open func setupViews() {}

Cell의 구성 요소 U는 처음에는 UIColor를 사용하면 기본적으로 잘 rendering 되는지 그리고 요소들의 기본 위치를 정하기에 매우 훌륭하다.

UIStackView.Alignment Document

case fill

A layout where the stack view resizes its arranged views so that they fill the available space perpendicular to the stack view’s axis. 스택뷰의 axis와 수직 방향으로 채운다

case leading

A layout for vertical stacks where the stack view aligns the leading edge of its arranged views along its leading edge. This is equivalent to the top alignment for horizontal stacks. vertical 스택뷰에서 왼쪽 정렬

static var top: UIStackView.Alignment

A layout for horizontal stacks where the stack view aligns the top edge of its arranged views along its top edge. This is equivalent to the UIStackView.Alignment.leading alignment for vertical stacks. horizontal 스택뷰 위 정렬

case firstBaseline

A layout where the stack view aligns its arranged views based on their first baseline. This alignment is only valid for horizontal stacks.

case center

A layout where the stack view aligns the center of its arranged views with its center along its axis. 축에 맞게 center로 정렬

case trailing

A layout for vertical stacks where the stack view aligns the trailing edge of its arranged views along its trailing edge. This is equivalent to the bottom alignment for horizontal stacks.

static var bottom: UIStackView.Alignment

A layout for horizontal stacks where the stack view aligns the bottom edge of its arranged views along its bottom edge. This is equivalent to the UIStackView.Alignment.trailing alignment for vertical stacks.

case lastBaseline

A layout where the stack view aligns its arranged views based on their last baseline. This alignment is only valid for horizontal stacks.

padding, margin

.padLeft(16).padRight(16)

stack에 바로 추가 가능

구현 내용

extension UIStackView {
    
    @discardableResult
    open func withMargins(_ margins: UIEdgeInsets) -> UIStackView {
        layoutMargins = margins
        isLayoutMarginsRelativeArrangement = true
        return self
    }
    
    @discardableResult
    open func padLeft(_ left: CGFloat) -> UIStackView {
        isLayoutMarginsRelativeArrangement = true
        layoutMargins.left = left
        return self
    }

minimumLineSpacing

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return 0
    }

태그: ,

카테고리:

업데이트:

댓글남기기