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
- Clean Code
- 리펙토링
- uiscrollview
- clean architecture
- MVVM
- Human interface guide
- SWIFT
- 애니메이션
- UICollectionView
- map
- Xcode
- rxswift
- Refactoring
- tableView
- UITextView
- RxCocoa
- 클린 코드
- HIG
- swiftUI
- Protocol
- ribs
- 스위프트
- 리펙터링
- swift documentation
- combine
- collectionview
- ios
- uitableview
- 리팩토링
- Observable
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] 수평 스크롤 뷰와 커스텀 Indicator View (쿠팡 수평 스크롤 뷰, 스크롤 IndicatorView) 본문
UI 컴포넌트 (swift)
[iOS - swift] 수평 스크롤 뷰와 커스텀 Indicator View (쿠팡 수평 스크롤 뷰, 스크롤 IndicatorView)
jake-kim 2022. 6. 9. 22:30
쉽게 레이아웃을 구현하기 위해 사용한 프레임워크
구현 아이디어
- UICollectionView를 사용하여 수평 스크롤 뷰 구현
- 하단에 사용할 IndicatorView는 UIView를 서브클래싱하여 커스텀으로 구현
- IndicatorView는 trackView와 trackTintView 두 개의 UIView로 구현
- trackView - IndicatorView에서의 배경 UI
- trackTintView - 스크롤 될 때 표시될 진행사항 UI
final class IndicatorView: UIView {
// MARK: UI
private let trackView = UIView().then {
$0.backgroundColor = .lightGray.withAlphaComponent(0.3)
}
private let trackTintView = UIView().then {
$0.backgroundColor = .gray
}
...
}
- trackView는 외부에서 autolayout을 정의해주는 크기대로 적용
- trackTintView는 외부에서 collectionView의 크기에 따라 width가 변하도록 구현
IndicatorView의 길이는 외부에서 동적으로 사용가능하도록 구현
IndicatorView 구현
- UIView를 서브클래싱하여 정의
import UIKit
import SnapKit
import Then
final class IndicatorView: UIView {
// MARK: UI
private let trackView = UIView().then {
$0.backgroundColor = .lightGray.withAlphaComponent(0.3)
}
private let trackTintView = UIView().then {
$0.backgroundColor = .gray
}
}
- 레이아웃
- trackTintView는 외부에서 offset을 넣어주면 그에따라 이동되게 해야하므로 left에 관한 constraint가 계속 update되어야 하므로 전역변수에 leftInsetConstraint를 선언해놓고 사용
- trackTintView는 trackView의 왼쪽과 오른쪽을 넘어가면 안되므로, left.greaterThanOrEqualToSuperview와 right.lessThenOrEqualToSuperview()로 정의해놓고, left.eqaulToSuperview()에는 priority를 작게 선언
private var leftInsetConstraint: Constraint?
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(self.trackView)
self.trackView.addSubview(self.trackTintView)
self.trackView.snp.makeConstraints {
$0.edges.equalToSuperview()
}
self.trackTintView.snp.makeConstraints {
$0.top.bottom.equalToSuperview()
$0.width.equalToSuperview().multipliedBy(1.0/5.0)
$0.left.greaterThanOrEqualToSuperview()
$0.right.lessThanOrEqualToSuperview()
self.leftInsetConstraint = $0.left.equalToSuperview().priority(999).constraint
}
}
- 핵심 - 외부에서 widthRatio와 leftOffsetRatio을 받아서 trackTintView의 width값과 leftOffset을 업데이트할 수 있도록 프로핕를 선언
// MARK: Properties
var widthRatio: Double? {
didSet {
guard let widthRatio = self.widthRatio else { return }
self.trackTintView.snp.remakeConstraints {
$0.top.bottom.equalToSuperview()
$0.width.equalToSuperview().multipliedBy(widthRatio)
$0.left.greaterThanOrEqualToSuperview()
$0.right.lessThanOrEqualToSuperview()
self.leftInsetConstraint = $0.left.equalToSuperview().priority(999).constraint
}
}
}
var leftOffsetRatio: Double? {
didSet {
guard let leftOffsetRatio = self.leftOffsetRatio else { return }
self.leftInsetConstraint?.update(inset: leftOffsetRatio * self.bounds.width)
}
}
사용하는 쪽
- UI 선언
class ViewController: UIViewController {
private let collectionView = UICollectionView().then { ... }
private let indicatorView = IndicatorView()
}
- Constraint
self.collectionView.snp.makeConstraints {
$0.centerY.left.right.equalToSuperview()
$0.height.equalTo(Constant.collectionViewHeight)
}
self.indicatorView.snp.makeConstraints {
$0.top.equalTo(self.collectionView.snp.bottom).offset(4)
$0.left.right.equalTo(self.collectionView).inset(100)
$0.height.equalTo(4)
}
- 핵심1)
- collectionView의 contentSize가 결정되는 viewDidAppear에서, contentSize를 이용하여 indicatorView의 width값을 업데이트
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let allWidth = self.collectionView.contentSize.width + self.collectionView.contentInset.left + self.collectionView.contentInset.right
let showingWidth = self.collectionView.bounds.width
self.indicatorView.widthRatio = showingWidth / allWidth
self.indicatorView.layoutIfNeeded()
}
* 참고) contentOffset, contentInset, contentSize
- contentOffset: 스크롤한 길이
- contentInset: collectionView의 테두리 부분과의 여백 (4곳만 존재)
- contentSize: 스크롤 가능한 콘텐츠 사이즈 (주의 - contentInset 값을 합해야, collectionView 전체 콘텐트 사이즈)
- 핵심2)
- 스크롤 될때마다 trackTintView의 위치를 변경해주어야 하므로, scrollViewDidScroll(_:)에서 스크롤 정보 업데이트
extension ViewController: UICollectionViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let scroll = scrollView.contentOffset.x + scrollView.contentInset.left
let width = scrollView.contentSize.width + scrollView.contentInset.left + scrollView.contentInset.right
let scrollRatio = scroll / width
self.indicatorView.leftOffsetRatio = scrollRatio
}
}
* 전체 코드: https://github.com/JK0369/ExCollectionViewWithIndicator
'UI 컴포넌트 (swift)' 카테고리의 다른 글
Comments