티스토리 뷰
안녕하세요.
오늘은 오랜만에 기본 알고리즘이 아닌 UI 내용을 다뤄볼까 합니다.
* 이번 포스트는 아래 링크를 참고하여 제작하였습니다.
- 상당히 자세히 설명되어있어서 읽어보시는걸 추천 드립니다.
https://jintaewoo.tistory.com/33
오늘 만들 거는 바로 이겁니다.
부르는 이름이 다양하긴 합니다.
외국에서는 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도 회전
}
}
최대한 나중에 봐도 이해하기 쉽게 정리해보았는데
생각보다 정리하는게 쉽지 않네요.
다른 분들에게도 조금이나마 도움이 되었으면 좋겠네요.
오늘도 파이팅입니다~
'iOS개발 > Swift 기본' 카테고리의 다른 글
Swift Date 타입을 활용해보자 (덧셈,뺄셈등) (0) | 2023.09.13 |
---|---|
swift UICollectionView 사이즈 지정시 주의 사항 (0) | 2023.09.08 |
swift 한글 받침 확인 하는 방법 (0) | 2023.08.03 |
swift 여백 클릭해서 창닫기 (0) | 2023.07.03 |
swift 현재 열린 UIViewController 전부 닫기 (0) | 2023.06.26 |