[iOS] CollectionView Carousel 3D Effect

2020. 12. 13. 23:39iOS

반응형

저번 시간에 UICollectionViewLayout을 잡는 방법에 대해 포스팅을 했었는데, 오늘은 이를 활용해 Carousel을 만드는 연습을 해볼게요.

 

우선 오늘 만들어 볼 화면을 볼게요!!

다음과 같이 스크롤을 넘기면서 넘어갈 때, 옆의 Cell들이 가운데에 맞추어서 커지는 효과⁉️

 

이를 구현하는 방법은 더 있을건데, 저는 UICollectionViewFlowLayout 객체를 커스텀해서 만들어보았습니다.

우선 초기에 설정해줄 것은 ViewController에서 CollectionView에 대해 선언하는 것입니다!

class ViewController: UIViewController {
    @IBOutlet weak var collectionView: UICollectionView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        let customLayout = CustomFlowLayout()
        collectionView.collectionViewLayout = customLayout
        
        collectionView.dataSource = self
    }
}

extension ViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 10
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let customCell = collectionView.dequeueReusableCell(withReuseIdentifier: CustomCell.identifier, for: indexPath) as? CustomCell else { return UICollectionViewCell() }
        let ratio: CGFloat = CGFloat(indexPath.row+1)/CGFloat(10+1)
        
        customCell.contentView.backgroundColor = UIColor(red: 255*ratio/255,
                                                         green: 50/255,
                                                         blue: 255*ratio/255,
                                                         alpha: 1.0)
        return customCell
    }
}

 

이렇게 생성을 해주었는데, 여기서 저는 CustomFlowLayout()이라는 객체를 생성했는데, 이후에 생성시켜줄 것이기 때문에 빨간줄이 떠도 됩니다.

CustomCell은 여러분이 별도로 아무렇게나 만들어주시면 됩니다~~ (색상도 변하는 것을 확인할려고 선언한 것입니다. 안하셔도되요!!)

 

이렇게하면, 컬렉션뷰의 셀을 10개 사용한다는 것이 정의 되었죠?

그럼 이제, CustomFlowLayout() 를 만들러가볼게요~~

 

우선 저번 포스팅에 작성했던 UICollectionViewFlowLayout의 주요 메소드들이 있는데, 이것들을 이용해서 활용할거에요!

 

[iOS] UICollectionViewLayout 설정

UICollectionView을 자주 사용하실 것 같은데 UITableView와 가장 크게 다른 점은 커스텀하게 Layout을 설정할 수 있는 프로퍼티가 있다는 것 같아요. 기본적으로 2가지 방법으로 Layout을 설정할 수 있는데,

dongminyoon.tistory.com

보고와도 좋을 것 같습니다!

 

3가지 메소드를 활용할 건데,

  • prepare() : CollectionView의 Layout을 설정하기 위해 준비작업들을 하는 메소드
  • shouldInvalidateLayout(forBoundsChange:) -> Bool : CollectionView의 bounds가 변화할 때마다 각 메소드들을 호출할 것인지 결정하는 메소드
  • layoutAttributesForElements(in:) -> [UICollectionViewLayoutAttributes]? : 현재 보이는 CGRect에서 Cell의 요소들을 받아오는 메소드

 

이렇게 3가지에요.

 

이제 생성해서 CollectionView의 초기 설정을 해볼게요~

class CustomFlowLayout: UICollectionViewFlowLayout {
    
    private var isInit: Bool = false
    
    override func prepare() {
        super.prepare()
        guard !isInit else { return }
        
        guard let collectionView = self.collectionView else { return }
        
        let collectionViewSize = collectionView.bounds
        itemSize = CGSize(width: collectionViewSize.width-50*2, height: 150)
        
        let xInset = (collectionViewSize.width-itemSize.width) / 2
        self.sectionInset = UIEdgeInsets(top: 0, left: xInset, bottom: 0, right: xInset)
        
        scrollDirection = .horizontal
        
        minimumLineSpacing = 10 - (itemSize.width - itemSize.width*0.7)/2
        
        isInit = true
    }
    
    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
        return true
    }
}

prepare()에서 CollectionView의 Layout에 관한 초기 설정을 해주었습니다.

 

여기서 isInit이라는 변수를 쓰는 이유는 shoudInvalidateLayout에서 true로 선언을 하면 CollectionView의 bounds가 변화할 때마다 메소드가 호출되기 때문에, 초기에 CollectionView의 Layout을 설정할 때만 사용하기 위해서입니다.

 

그리고 다른 부분들은 CollectionView의 Layout을 설정하는 것인데, 여기서 의아한 것은 minimumLineSpacing을 설정하는 부분이겠죠⁉️

 

저 부분은 이제 CollectionView의 Cell들이 scale이 조정되는데 그에 맞게 기본 minimumLineSpacing = 10에서 조절할 수 있게 계산해준 것입니다.

Cell이 작아지면 minumumLineSpacing이 있음에도 불구하고 그만큼 더 멀리 있는 것 처럼보이게 되죠?
그래서 그 작아진 크기만큼, minimumLineSpacing을 가까이 만들어준거에요!

 

이렇게 하면, 초기 설정은 완료되었습니다.

이제, 어떻게 각 변수들을 변화시키는지 확인해볼게요~

class CustomFlowLayout: UICollectionViewFlowLayout {
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let superAttributes = super.layoutAttributesForElements(in: rect)
        
        superAttributes?.forEach { attributes in
            guard let collectionView = self.collectionView else { return }
            
            let collectionViewCenter = collectionView.frame.size.width / 2
            let offsetX = collectionView.contentOffset.x
            let center = attributes.center.x - offsetX
            
            let maxDis = self.itemSize.width + self.minimumLineSpacing
            let dis = min(abs(collectionViewCenter-center), maxDis)
            
            let ratio = (maxDis - dis)/maxDis
            let scale = ratio * (1-0.7) + 0.7
            
            attributes.transform = CGAffineTransform(scaleX: scale, y: scale)
        }
        
        return superAttributes
   }
    
}

이제 이 부분에서 각 Cell의 모든 Layout 요소들을 가져와서 조정을 하게 됩니다!

 

각 요소들을 볼게요.

 

1️⃣ center

attributes.center.x의 경우 초기에 cell이 로드될 때, cell의 x값을 가지고 있습니다. 여기서 offsetX을 빼주는 이유는 cell의 center를 동적으로 가져오기 위해서 계산해주었습니다.

2️⃣ maxDis

이 경우는 각 셀이 가장 먼 위치에 있을 때의 경우입니다. 즉, cell의 width와 거기에 minimumLineSpacing을 더한 값이겠죠?

3️⃣ dis

dis는 현재 CollectionView의 가운데에서 cell의 가운데 값을 빼서 가운데 0을 기준으로 1까지 계산하기 위해 계산하는 값입니다.

4️⃣ ratio

가장 큰 값에서 현재 거리를 빼서 비율을 구하기 위한 값입니다. 이 비율을 이용해서 cell의 scale을 조정해주고 animation효과를 냅니다.

5️⃣ scale

여기서 아까 ratio의 값을 이용하고 여기에서 초기값을 설정해서 원한는 효과를 구현할 수 있습니다!

 

이제 이렇게 선언해주면, 정말 간단한 CollectinView의 Carousel 효과를 볼 수 있습니다.

 

 

원래 핀터레스트의 뷰를 만들어보려고 했는데, 우연히 프로젝트에서 이런 뷰를 그리게되서, 먼저 작성해보았습니다~~!

 

틀린 점이나 더 좋은 방법있으면 알려주세요!

반응형