Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- 리펙토링
- RxCocoa
- 리펙터링
- Observable
- ribs
- 클린 코드
- uiscrollview
- 리팩토링
- swift documentation
- rxswift
- uitableview
- swiftUI
- ios
- combine
- Human interface guide
- Clean Code
- map
- UICollectionView
- 애니메이션
- MVVM
- clean architecture
- Protocol
- UITextView
- 스위프트
- tableView
- Xcode
- HIG
- Refactoring
- collectionview
- SWIFT
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] Infinite Carousel (무한 스크롤 뷰) 구현 방법 본문
구현 아이디어
- 수평 스크롤을 위해서 UIScrollView를 이용해도 되지만, 데이터 소스 입력 편의를 위해 UICollectionView 사용
- 무한 스크롤 원리 (데이터가 1,2,3 이렇게 있을 경우,)
- 왼쪽에서 오른쪽으로 무한 스크롤:
- 데이터 세팅: 1, 2, 3, 1 (앞에있는걸 마지막에 붙이기)
- scrollViewDidEndDecelerating에서 스크롤 된 크기를 알 수 있는 conttentOffset.x를 이용하여 1,2,3,1로 놓고 4번째 1에 도달했을때, 애니메이션 없이 다시 1로 돌아가도록 설정
- 오른쪽에서 왼쪽으로 무한 스크롤: 마찬가지로 conttentOffset.x를 이용하여 1,2,3,1로 놓고 1번째 1에 도달했을때, 애니메이션 없이 다시 1로 돌아가도록 설정
- 데이터 세팅: 3, 1, 2, 3 (뒤에있는걸 첫번째에 붙이기)
- scrollViewDidEndDecelerating에서 스크롤 된 크기를 알 수 있는 conttentOffset.x를 이용하여 4,1,2,3로 놓고 1번째 3에 도달했을때, 애니메이션 없이 다시 3로 돌아가도록 설정
- 양방향으로 구현해야하므로 첫번째와 마지막 아이템을 양쪽끝에 붙여서 구현 3,1,2,3,1
- 주의) 델리게이트 메소드중 scrollViewDidEndDecelerating는 바로 불리지 않으므로 뷰가 보이자마자collectionView.setContentOffset을 4번으로 초기화 필요
- 왼쪽에서 오른쪽으로 무한 스크롤:
Infinite Carousel 구현
- UICollectionViewCell 구현
- label 하나와 view 하나가 존재
final class CollectionViewCell: UICollectionViewCell {
// MARK: UI
private let someView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let label: UILabel = {
let label = UILabel()
label.textColor = .white
label.font = .systemFont(ofSize: 32)
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
// MARK: Initializer
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(someView)
contentView.addSubview(label)
NSLayoutConstraint.activate([
someView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
someView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
someView.topAnchor.constraint(equalTo: contentView.topAnchor),
someView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
])
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: someView.leadingAnchor),
label.trailingAnchor.constraint(equalTo: someView.trailingAnchor),
label.topAnchor.constraint(equalTo: someView.topAnchor),
label.bottomAnchor.constraint(equalTo: someView.bottomAnchor)
])
}
override func prepareForReuse() {
super.prepareForReuse()
prepare(nil, nil)
}
func prepare(_ color: UIColor?, _ text: String?) {
someView.backgroundColor = color
label.text = text
}
}
- ViewController 준비
import UIKit
class ViewController: UIViewController {
}
- 아이템 준비
- 1,2,3이 있고 랜덤 색상 하나로 튜플 형태의 아이템
private var items = (1...3)
.map { (String($0), [UIColor.gray, .red, .blue, .orange, .black].randomElement()) }
- collectionView 준비
- collectionView의 isScrollEnabled = true로 설정
enum Metric {
static let collectionViewHeight = 120.0
static let cellWidth = UIScreen.main.bounds.width
}
private var items = (1...3)
.map { (String($0), [UIColor.gray, .red, .blue, .orange, .black].randomElement()) }
private let collectionViewFlowLayout: UICollectionViewFlowLayout = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.itemSize = .init(width: Metric.cellWidth, height: Metric.collectionViewHeight)
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
return layout
}()
private lazy var collectionView: UICollectionView = {
let view = UICollectionView(frame: .zero, collectionViewLayout: self.collectionViewFlowLayout)
view.isScrollEnabled = true
view.showsHorizontalScrollIndicator = false
view.showsVerticalScrollIndicator = true
view.contentInset = .zero
view.backgroundColor = .clear
view.clipsToBounds = true
view.register(CollectionViewCell.self, forCellWithReuseIdentifier: "cell")
view.translatesAutoresizingMaskIntoConstraints = false
view.isPagingEnabled = true
return view
}()
- viewDidLoad에서 아이템 변경 및 레이아웃
- 아이템 변경 (1,2,3) -> (3,1,2,3,1)
override func viewDidLoad() {
super.viewDidLoad()
// as -is: 1 2 3
// to -be: 3 1 2 3 1
items.insert(items[items.count-1], at: 0)
items.append(items[1])
view.addSubview(collectionView)
NSLayoutConstraint.activate([
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
collectionView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
collectionView.heightAnchor.constraint(equalToConstant: Metric.collectionViewHeight)
])
collectionView.dataSource = self
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
items.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
cell.prepare(items[indexPath.item].1, items[indexPath.item].0)
return cell
}
}
- 뷰가 보여질때 setContentOffset 값 변경 (현재 3,1,2,3,1이라 3의 화면을 보여지고 있으므로)
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView.setContentOffset(.init(x: Metric.cellWidth, y: collectionView.contentOffset.y), animated: false)
}
- scrollViewDidEndDecelerating에서 스크롤 처리
- 3,1,2,3,1 데이터가 있을때,
- 첫번째 3일이 보일땐 네번째 3으로 이동 (왼쪽에서 오른쪽으로 스크롤 헀을때 2가 나와야하므로)
- 마지막 1이 보일땐 첫번째 1로 이동 (오른쪽으로 계속 스크롤 되는것처럼 보이기)
- 3,1,2,3,1 데이터가 있을때,
collectionView.delegate = self
extension ViewController: UICollectionViewDelegate {
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let count = items.count
if scrollView.contentOffset.x == 0 {
scrollView.setContentOffset(.init(x: Metric.cellWidth * Double(count-2), y: scrollView.contentOffset.y), animated: false)
}
if scrollView.contentOffset.x == Double(count-1) * Metric.cellWidth {
scrollView.setContentOffset(.init(x: Metric.cellWidth, y: scrollView.contentOffset.y), animated: false)
}
}
}
* 전체 코드: https://github.com/JK0369/ExInfiniteCarousel
* 참고
'iOS 응용 (swift)' 카테고리의 다른 글
Comments