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





    화창한 여름날 슈퍼칼퇴잼


    댓글 3

    • 왕초보 2017.08.09 22:31

      안녕하세요 혹시

      overide하다랑 implment하다라는게 스위프트용어로

      우리말로 어떤뜻인가요?

      • 고무망치 2017.08.09 23:54 신고

        안녕하세요~
        override는 객체지향에서 자주 쓰이는 용어라 보통 오버라이드한다고 합니다.
        부모클래스의 메소드를 재정의할 때 사용하죠.

        implement는 '구현'이라고 보시면 됩니다.
        스위프트에서는 protocol의 메소드를 구현할 때 implement라고도 많이 사용합니다.

    • 궁그미 2021.09.09 21:25

      안녕하세요.
      페이지네이션처럼 만약 데이터를 더 불러오는 상황이 생기는 경우는 layoutCache를 더 만들어 주어야 하는 상황이 올텐데 이런 경우는 어떻게 하나요? layoutCache를 다 제거해주고 다시 만들어주는게 나을까요?

Designed by Tistory.