ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [iOS] UICollectionView와 UICollectionViewFlowLayout의 관계: 간단하게 2단리스트를 만들어보자!
    앱등이에게 살충제를 뿌린다./iOS 2017. 8. 3. 01:16

    Swift3기준으로 작성되었습니다.

    UICollectionView가 UICollectionViewFlowLayout와 함께 레이아웃을 만드는 과정을 간단히 알아보겠다.

    1. 샘플앱을 보고,
    2. 컬렉션뷰의 레이아웃 과정을 알아본 뒤,
    3. 샘플앱을 수정해보자.
    1. 샘플앱

    컬렉션뷰를 하나 만들고 셀에는 UIImageView하나만 추가되어 있다. 이 쯔위앱을 만들기 위해서는 아래코드가 필요하다.

    import UIKit

    // 편의상 전역변수

    let imagesCount = 21

    var images: [UIImage] { /*...*/ return images }


    class ViewController: UIViewController, UICollectionViewDataSource {    

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

            return images.count

        }

        

        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

            guard let cell = collectionView.dequeueReusableCell // Dequeueing생략

            

            cell.imageView.image = images[indexPath.row]

            

            return cell

        }

    }


    class CollectionViewCell: UICollectionViewCell {

        @IBOutlet weak var imageView: UIImageView!

    }


    class CollectionViewFlowLayout: UICollectionViewFlowLayout {

    // 여기를 수정할 예정

        override func prepare() {

            super.prepare()

        }

        

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

            return super.layoutAttributesForElements(in: rect)

        }

    }



    2. 코드를 수정하기에 앞서 몇 가지 내용을 짚어보자.

    아래 그림은 UICollectionView가 Layout객체와 대화하며 레이아웃을 만들어가는 과정을 보여준다.

    1. prepareLayout() swift3에서는 prepare(): 레이아웃관련 연산이 일어날 때마다 호출된다. 이 메소드에서 셀의 위치/크기 등을 계산하기 위한 사전처리를 할 수 있다.

    2. collectionViewContentSize() : 이 메소드에서는 컬렉션뷰의 크기를 리턴해야 한다. 0번째 셀부터 마지막셀까지를 담는 전체 영역을 알려주어야 하는 것이다.

    3.layoutAttributesForElementsInRect(_:)  : 이 메소드에서는 rect파라미터 안에 있는 모든 셀들의 속성들을 배열로 리턴해야한다. 이 속성은 UICollectionViewLayoutAttributes타입이고, frame, indexPath등의 값을 갖고 있다.



    3. 어떤식으로 override & implement해야할까?

    우리는 더 많은 공간에서 쯔위를 보길 원한다. 그래서 빈틈을 매워주는 핀터레스트 방식의 UI를 구현할 것이다.

    위 샘플코드에서 CollectionViewFlowLayout 클래스만 수정해보자.

    class CollectionViewFlowLayout: UICollectionViewFlowLayout {

        var layoutCache: [UICollectionViewLayoutAttributes]? = nil

        

        override func prepare() {

            super.prepare()

            

            let width = (collectionView?.bounds.size.width ?? 375) / 2 - 5

            

            // attribute 만드는 작업은 한번만 합니다.

            guard layoutCache == nil else { return }

            

            var attrsList: [UICollectionViewLayoutAttributes] = []

            for (index, image) in images.enumerated() {

                let isOdd = index % 2 == 0

                let attrs = UICollectionViewLayoutAttributes(forCellWith: IndexPath(item: index, section: 0))

                let ratio = image.size.height / image.size.width

                let height = width * ratio

                

                var frame = CGRect(x: isOdd ? 0 : width + 10, y: 0, width: width, height: height)

                if index > 1 {

                    let upperImage = attrsList[index-2]

                    frame.origin.y = upperImage.frame.origin.y + upperImage.frame.size.height + 10

                }

                attrs.frame = frame

                

                attrsList.append(attrs)

            }

            

            layoutCache = attrsList

        }

        

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

            guard let layoutCache = layoutCache else { return super.layoutAttributesForElements(in: rect) }

            

            var layoutAttributes = [UICollectionViewLayoutAttributes]()

            

            for attributes in layoutCache {

                if attributes.frame.intersects(rect) {

                    layoutAttributes.append(attributes)

                }

            }

            

            return layoutAttributes

        }

    }


    prepare메소드에서 모든 이미지에 대한 비율을 구하고, UICollectionViewLayoutAttributes타입의 배열을 생성해둔다.

    그럼 layoutAttributesForElementes(in:)메소드에서는 해당 rect안에 있는 attributes만 찾아내서 리턴해주면 되는 것이다.

    참으로 간단하다~!


    이 외에도 많은 메소드들이 상호작용한다.

    하지만 오늘은 이 정도만 알아두자. 

    참고 : https://www.raywenderlich.com/107439/uicollectionview-custom-layout-tutorial-pinterest





    화창한 여름날 슈퍼칼퇴잼


Designed by Tistory.