티스토리 뷰

안녕하세요. 

오늘은 오랜만에 기본 알고리즘이 아닌 UI 내용을 다뤄볼까 합니다. 

 

* 이번 포스트는 아래 링크를 참고하여 제작하였습니다.

- 상당히 자세히 설명되어있어서 읽어보시는걸 추천 드립니다. 

https://jintaewoo.tistory.com/33

 

collectionView paging 해보기!

collectionView에 간단히 paging을 설정할 수 있는 프로퍼티가 있다. collectionView.isPagingEnabled = true 위 코드와 같이 간단히 가능하다. 하지만 이 코드로는 상하좌우에 여백이 생기면 스크롤 시 어그러지

jintaewoo.tistory.com

오늘 만들 거는 바로 이겁니다. 

한페이지씩 넘어가는 CollectionView

 

부르는 이름이 다양하긴 합니다. 

외국에서는 Carousel View (회전 목마 뷰) 라고 부르기도 하고요 

우리나라에서는 스크롤 뷰 페이저 이런식으로 부르기도 합니다. 

(이름이 굉장히 많아요;;;)

 

정확하게 이게 뭐다 라고 하기 애매하긴 하네요. 

일단 원리부터 말씀드리면 CollectionView를 응용해 만드는 방식입니다. 

 

저는 UICollectionView 부터 만들어 볼께요. 

각 영역이 구분이 잘 되도록 하트(빨강) / 클로버(초록) / 다이아(노랑) / 스페이드(파랑) 로 만들어 볼께요.

 

1. 화면에 UICollectionView를 넣고 원하시는 모양의 Cell 을 만들어주세요. 

- 보라색 영역UIViewController이고 노란색 영역이 실제 UICollectionView 입니다.

- 하얀색 영역이 제가 만든 Cell 입니다.

- 배치는 취향에 맞게 배치해주세요. 

저는 트럼프 카드 모양 처럼 만들어볼께요

2. UICollectionCell 코드를 작성해줄께요. 

- 저는 카드 모양으로 만들고 싶어서 기본 도형을 사용해 위처럼 만들었어요.

- Cell 을 만들었으면 Cell 의 Class 도 만들어 줘야겠죠?

- 저는 CardCollectionCell 이라고 이름 지어볼께요.

class CardCollectionCell: UICollectionViewCell {
    
    @IBOutlet weak var cardBackView: UIView!
    
    @IBOutlet weak var topIv: UIImageView! // 상단 좌측 이미지
    @IBOutlet weak var centerIv: UIImageView! // 가운데 중앙 이미지
    @IBOutlet weak var bottomIv: UIImageView! // 하단 우측 이미지
    
    let imageNameArray = ["heart.fill","suit.club.fill","diamond.fill","suit.spade.fill"]
    let colorArray = [UIColor.red,UIColor.systemGreen,UIColor.orange,UIColor.systemIndigo]
    
    func setCardCustom(_ index : Int){
        self.cardBackView.layer.cornerRadius = 20 // 코너 둥글게 
        
        self.topIv.image = UIImage(systemName: imageNameArray[index])
        self.topIv.tintColor = colorArray[index]
        
        self.centerIv.image = UIImage(systemName: imageNameArray[index])
        self.centerIv.tintColor = colorArray[index]
        
        self.bottomIv.image = UIImage(systemName: imageNameArray[index])
        self.bottomIv.tintColor = colorArray[index]
        self.bottomIv.transform = self.bottomIv.transform.rotated(by: .pi) // 하단 이미지는 180도 회전
                
    }
}

 

3. StoryBoard 에서 UICollectionView 의 설정을 바꿔주세요. 

- 저희가 만들고 싶은 View는 스크롤이 위 아래가 아니라 좌우 스크롤 입니다. 

- 따라서 설정을 조금 바꿔줄께요. (Scroll Direction 설정을 바꿔주세요)

- 그리고 저희는 코드로 Cell 을 설정할거기 때문에 추가로 아래도 변경해줄께요. 

- Estimate Size None 으로 바꿔주세요. 

* 안하면 코드가 적용이 안되는 경우가 많습니다.

4. 기본적인 UICollectionView 에 대한 코드를 UIViewController 에 추가해주세요. 

class CardsViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {
    
    // 카드 콜렉션 
    @IBOutlet weak var cardsCollectCv: UICollectionView!
    
    // 카드 가로 길이 비율 
    let cellRatio = 0.8
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.setCollectionView()
    }
    func setCollectionView(){
        self.cardsCollectCv.delegate = self
        self.cardsCollectCv.dataSource = self
        self.cardsCollectCv.decelerationRate = UIScrollViewDecelerationRateFast
    }
	//MARK: CollectionCell delegate
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 4 // 카드 총 4개
    }
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        let cellWidth = floor(view.frame.width * cellRatio) // 전체 가로 * 비율 

        // 좌우 inset value 설정
        let insetX = (view.bounds.width - cellWidth) / 2.0 // (전체 가로 - Cell 가로)/2
        return UIEdgeInsets(top: 0, left: insetX, bottom: 0, right: insetX)
    }
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        // width, height 설정
        let cellWidth = floor(view.frame.width * cellRatio) //cell 가로 길이
        let cellHeight = self.cardsCollectCv.frame.height//세로는 CollectionView의 세로와 동일
        return CGSize(width: cellWidth, height: cellHeight)
    }
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CardCollectionCell", for: indexPath) as! CardCollectionCell
        cell.setCardCustom(indexPath.item) // 각 Cell 모양 설정
        return cell
    }
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1 //컬렉션뷰의 섹션의 개수
    }

}

 

* 참고로 주의하실 부분이 있는데 insetForSectionAt 이라는 부분에서 넣어준 값은 아래 그림의 회색 공간을 의미합니다.

* Cell 간의 간격을 의미하는게 아니라는 뜻입니다!!

이거 주의하세요!!

* 여기까지 하면 저희가 원하는 Carousel Effect 는 없는 일반 UICollectionView 가 만들어집니다. 

여기까지 하면 이렇게 됩니다.

5. 한페이지씩 넘겨지는 이펙트를 추가해봅시다. 

- 원리는 한번 스크롤을 할때 지정된 지점까지만 넘어가게 하는 방식입니다.

- 즉 저희는 지금까지 사용한 사이즈를 이용해 계산해주면 됩니다. 

- 참고 링크의 코드를 참고해서 제작된 버전 입니다. 원본은 참고 링크를 참고해주세요. 

var currentCard :CGFloat = 0

- 위 변수를 먼저 선언해 주시고 아래 함수를 추가해 줍니다. 

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {

    let cellWidth = floor(view.frame.width * cellRatio)
    let cellMinimumSpace : Double = 10.0 // cell 최소간격 (Storyboard에 설정된 간격)

    let offset = targetContentOffset.pointee // 스크롤 포인트

    let index = offset.x / (cellWidth+cellMinimumSpace) // 스크롤에 따른 Index 추출

    var roundedIndex = index
    // 손가락 방향에 따라 정의
    if scrollView.contentOffset.x > targetContentOffset.pointee.x { // ->
        roundedIndex = floor(index)
    } else if scrollView.contentOffset.x < targetContentOffset.pointee.x { // <- 손가락 방향
        roundedIndex = ceil(index)
    } else { // 정지
        roundedIndex = round(index)
    }

    if currentCard > roundedIndex {
        currentCard -= 1
        roundedIndex = currentCard
    } else if currentCard < roundedIndex {
        currentCard += 1
        roundedIndex = currentCard
    }
    //스크롤이 멈추는 부분
    let targetX = (cellWidth + cellMinimumSpace) * roundedIndex
    targetContentOffset.pointee = CGPoint(x: targetX, y: 0)
}

* 여기까지 하면 아래처럼 되게 됩니다.

카드가 스크롤 할때마다 중앙으로 오는게 보이시나요?

* 참고로 Cell 사이 간격은 아래 Min Spacing 으로 확인할수있습니다. (기본 10)

조절을 원하시는 분들은 참고하세요

6. pageController 와 배경색을 다듬어 주어 완성합니다. 

- 이제 아래 pageController 와 배경색을 다듬어 줍니다.

override func viewDidLoad() {
    super.viewDidLoad()
	....
	self.pageControl.numberOfPages = 4
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    ....
    ....
    ....
    pageControl.currentPage = Int(currentCard)
}

이렇게 다듬어 주면 아래처럼 완성하게 됩니다.

완성!

7. 전체 코드 (CardsViewController &CardCollectionCell)

 

#CardsViewController

import Foundation
import UIKit

class CardsViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {
    
    // 카드 콜렉션
    @IBOutlet weak var cardsCollectCv: UICollectionView!
    
    @IBOutlet weak var pageControl: UIPageControl!
    let cellRatio = 0.8
    var currentCard :CGFloat = 0
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.setCollectionView()
        self.pageControl.numberOfPages = 4
    }
    
    func setCollectionView(){
        
        self.cardsCollectCv.delegate = self
        self.cardsCollectCv.dataSource = self
        
        self.cardsCollectCv.decelerationRate = UIScrollViewDecelerationRateFast
    }
    //MARK: CollectionCell delegate
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 4
    }
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        let cellWidth = floor(view.frame.width * cellRatio)

        // 상하, 좌우 inset value 설정
        let insetX = (view.bounds.width - cellWidth) / 2.0
        return UIEdgeInsets(top: 0, left: insetX, bottom: 0, right: insetX)
    }
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        // width, height 설정
        let cellWidth = floor(view.frame.width * cellRatio)
        
        let cellHeight = self.cardsCollectCv.frame.height//floor(view.frame.height * cellRatio)
        return CGSize(width: cellWidth, height: cellHeight)
    }
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CardCollectionCell", for: indexPath) as! CardCollectionCell
        cell.setCardCustom(indexPath.item)
        return cell
    }
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }
    

    func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {

        let cellWidth = floor(view.frame.width * cellRatio)
        let cellMinimumSpace : Double = 10.0 // cell 최소간격 (Storyboard에 설정된 간격)
        
        let offset = targetContentOffset.pointee // 스크롤 포인트
        
        let index = offset.x / (cellWidth+cellMinimumSpace) // 스크롤에 따른 Index 추출

        var roundedIndex = index
        // 손가락 방향에 따라 정의
        if scrollView.contentOffset.x > targetContentOffset.pointee.x { // ->
            roundedIndex = floor(index)
        } else if scrollView.contentOffset.x < targetContentOffset.pointee.x { // <- 손가락 방향
            roundedIndex = ceil(index)
        } else { // 정지
            roundedIndex = round(index)
        }

        if currentCard > roundedIndex {
            currentCard -= 1
            roundedIndex = currentCard
        } else if currentCard < roundedIndex {
            currentCard += 1
            roundedIndex = currentCard
        }
        pageControl.currentPage = Int(currentCard)// 뷰페이저 연결
        //스크롤이 멈추는 부분

        let targetX = (cellWidth + cellMinimumSpace) * roundedIndex
        targetContentOffset.pointee = CGPoint(x: targetX, y: 0)
        
        
    }
}

# CardCollectionCell

 

class CardCollectionCell: UICollectionViewCell {
    
    @IBOutlet weak var cardBackView: UIView!
    
    @IBOutlet weak var topIv: UIImageView! // 상단 좌측 이미지
    @IBOutlet weak var centerIv: UIImageView! // 가운데 중앙 이미지
    @IBOutlet weak var bottomIv: UIImageView! // 하단 우측 이미지
    
    let imageNameArray = ["heart.fill","suit.club.fill","diamond.fill","suit.spade.fill"]
    let colorArray = [UIColor.red,UIColor.systemGreen,UIColor.orange,UIColor.systemIndigo]
    
    func setCardCustom(_ index : Int){
        self.cardBackView.layer.cornerRadius = 20

        self.topIv.image = UIImage(systemName: imageNameArray[index])
        self.topIv.tintColor = colorArray[index]

        self.centerIv.image = UIImage(systemName: imageNameArray[index])
        self.centerIv.tintColor = colorArray[index]

        self.bottomIv.image = UIImage(systemName: imageNameArray[index])
        self.bottomIv.tintColor = colorArray[index]
        self.bottomIv.transform = self.bottomIv.transform.rotated(by: .pi) // 하단 이미지는 180도 회전
                
    }
}

 

최대한 나중에 봐도 이해하기 쉽게 정리해보았는데 

생각보다 정리하는게 쉽지 않네요.

 

다른 분들에게도 조금이나마 도움이 되었으면 좋겠네요. 

 

오늘도 파이팅입니다~ 

댓글