Skip to main content

Command Palette

Search for a command to run...

[UIKit] UICollection Pinterest Layout - 1

Published
T

안녕하세요🙇🏻‍♂️ 세상을 더 편리하게 바꾸고 싶은 iOS 개발자 최인호입니다.

Hello 👋 I'm Inho Choi, an iOS developer who wants to change the world more conveniently.

  • 대학교 졸업
  • Apple Developer Academy @ POSTECH 1기
  • KWDC Main Organizer
  • AsyncSwift Organizer

우리의 목표는

위 그림 처럼 UICollectionView에서 Height에 맞게 Cell의 높이를 Dynamic 하게 만드는 것이다.

UICollectionView가 뭔지 모르겠다고? 그래서 준비했음

+ 난 개인적으로 Dynamic Height 이런거 일줄 알았는데 Pinterest Laytout이라 부른다 카더라..

아니 그냥 그렇다고

[코드 깃허브 주소]


우리의 목표를 이루기 위해서는 UICollectionViewLayout에 대해서 알아야한다.

추상 클래스인데 collectionview layout 정보를 만드는 클래스래.

자세한 것을 알고 싶다? 그럼 무조건 공식문서 하지만 여기는 요약글이니

빠르게 알아보자

  1. UICollectionView(이하 CollectionView)에서 UICollectionViewLayout(이하 Layout)의 prepare 함수를 호출한다.

그럼 prepare 함수에 뭐 작성해야함? 반환값도 없는데?

이후 호출되는 값 및 호출되는 함수 반환값을 준비해야함 왜냐고?

말 그대로 prepare: 준비하다. 준비하는 함수임.

  1. collectionViewContentSize 반환을 해야 함

모두 알다 싶이 CollectionViewScrollView의 SubClass임.

즉 그 말은 ContentSize를 적절하게 늘려줘야 화면에 보임. 그러니 contentSize를 적절하게 늘려줘야함

  1. layoutAttributesForElements를 호출해서 [UICollectionViewAttribute]를 반환해야 함

근데 UICollectionViewAttribute가 뭐임? -> 셀의 frame을 정해주는 값.

오호 그러면 저 셀의 위치를 하나하나 정해줘야겠네? -> ㅇㅇ 맞음


prepare()

class DynamicHeightLayout: UICollectionViewLayout {
    ... 중략 ...
    var cellWidth: CGFloat = 0
    private let numberOfColumns = 2
    private let cellPadding: CGFloat = 10
    private var cache: [UICollectionViewLayoutAttributes] = []

    override func prepare() {
        guard let collectionView = collectionView else { return }
        // 1
        let columnWidth = contentWidth / CGFloat(numberOfColumns)
        var xOffset: [CGFloat] = []
        for column in 0..<numberOfColumns {
            xOffset.append(CGFloat(column) * columnWidth)
        }
        cellWidth = columnWidth - cellPadding * 2
        var yOffset: [CGFloat] = .init(repeating: 0, count: numberOfColumns)

        // 2
        var column = 0
        for item in 0..<collectionView.numberOfItems(inSection: 0) {
            let indexPath = IndexPath(item: item, section: 0)

            let imageHeight = delegate?.collectionView(collectionView, heightForPhotoAtIndexPath: indexPath) ?? 0
            let height = cellPadding * 2 + imageHeight
            let frame = CGRect(x: xOffset[column], y: yOffset[column], width: columnWidth, height: height)
            let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)

            // 3
            let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
            attributes.frame = insetFrame
            cache.append(attributes)


            contentHeight = max(contentHeight, frame.maxY)
            yOffset[column] = yOffset[column] + height

            column = column < (numberOfColumns - 1) ? (column + 1) : 0
        }
        reloadedCount = collectionView.numberOfItems(inSection: 0)
    }
}
  1. 폭 너비에 대한 설정

    columnWidth: 열의 너비 설정

    xOffset: 셀의 열에 따른 x 위치

    cellWidth: 셀의 너비 설정

  1. 높이에 대한 설정

    여기서 눈여겨봐야 할 부분은 cell의 padding도 포함해서 넣는다.

  2. 가로 세로에 대한 정보를 저장한다.


layoutAttributesForElements

override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    var visibleLayoutAttributes: [UICollectionViewLayoutAttributes] = []

    for attributes in cache {
        if attributes.frame.intersects(rect) {
            visibleLayoutAttributes.append(attributes)
        }
    }
    return visibleLayoutAttributes
}

cache에 저장되어있는 attributes들이 rect(CollectionView의 CGSize)와 교차점이 있는지 확인 후에 교차점이 있는 Attribute만 return 한다.

collectionView의 frame과 bounds가 변할 때 마다 호출된다.

참고한 사이트

명확한 뜻을 담기 위해서 위 코드로 적었는데 조금 더 간략하게 작성 가능

override func layoutAttributesForElements(in rect: CGRect) -> [UICollect
    return cache.filter{ $0.frame.intersects(rect) }
}

layoutAttributesForItem

override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
    return cache[indexPath.item]
}

이건... 이건... 정말 잘 모르겠다..

아시는 분 Help... 언제 호출되는지 모르겠다. BreakPoint를 걸어도 멈추질 않는다.


아직 전체에 대한 궁금증이 안 갔을 것이다.

내가봐도 그럼 그렇기에 전체코드를 통해서 다시 해석하는 시간 가져볼까함

More from this blog

[CS] Https는 대칭키일까? 비대칭키일까?

결론: 둘 다 쓴다. 하지만 쓰는 타이밍은 다르다. 자세하기 알아보자. 혹시 모르니 용어는 정리해두자. 암호화 <-> 복호화 해독 = 복호화 비대칭키 해독은 대칭키 해독보다 오래 걸린다. 비대칭키를 쓰는 과정 💡 비대칭키는 대칭키를 만들기 위한 여정에 쓰인다. 뭔 소리지??? 대칭키를 만들기 위해서 비대칭키가 쓰인다고?? 비대칭키 해독은 대칭키 해독보다 오래걸린다. 1. Client가 Server에 접속을 요청하면 서버는 Secret ...

Jun 6, 202417

Xcode Cloud 사용 후기

애플에게 내는 친구비를 내며 Apple Developer 계정을 유지하려면 매년 애플에게 13만원의 친구비를 지불해야한다. 그리고 몇 개 친구 답례 상품을 주는데 그 중 하나가 Xcode Cloud 상품 주는데 안 쓸 이유는 없지 Xcode Cloud 개인에게는 과연? K-Spam 이라는 앱을 오랫동안 구상하고 드디어 출시를 했다. 생각보다 삽질을 많이 한 것 같다. 하지만 사이드 프로젝트의 진정한 의미는 삽질(?)하며 배우는 것이 아니겠나 개...

Jun 3, 202418

iOS Version 주의

앱 버전에는 상위버전이 출시한 상태에서 하위 버전을 아카이브 할 수 없음 예를 들어 출시한 버전이 1.3 이면 1.2로는 아카이브가 불가능함 단, 출시한 적이 없는 앱이라면 버전은 마음대로 지정 가능 App Store Connect의 버전 예를 들어 1.03 이랑 1.1 이랑 비교하면 앱 버전은 1.03 > 1.1 로 인식한다. . 단위로 잘라서 Int로 인식하는 것 같다. 그러니 0은 최대한 사용하지 않고 구분한다면 . 으로 구분해야겠다.

Jun 1, 20248

Toby의 iOS 블로그

34 posts

안녕하세요 세상을 더 편리하게 바꾸고 싶은 iOS 개발자입니다.

[UIKit] UICollection Pinterest Layout - 1