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
- Protocol
- ios
- map
- Observable
- ribs
- 클린 코드
- Human interface guide
- RxCocoa
- Clean Code
- uitableview
- swiftUI
- rxswift
- 스위프트
- 리팩토링
- 리펙터링
- MVVM
- Xcode
- collectionview
- swift documentation
- UICollectionView
- uiscrollview
- Refactoring
- 리펙토링
- tableView
- SWIFT
- HIG
- UITextView
- clean architecture
- combine
- 애니메이션
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] UI 컴포넌트 - ViewPager (뷰 페이저) (CollectionView + PageViewController) 본문
UI 컴포넌트 (swift)
[iOS - swift] UI 컴포넌트 - ViewPager (뷰 페이저) (CollectionView + PageViewController)
jake-kim 2021. 7. 21. 00:33구현 아이디어
- 상단에는 CollectionView를 통해 수평 스크롤뷰 생성
- 아래에는 PageViewController를 통해 다수의 ViewController 존재
- 두 인터렉션을 연결하여 하나의 ViewPager를 사용하는 경험을 주는 UI
사용되는 Component 참고
* PageViewController 참고: https://ios-development.tistory.com/623
주의) StackView + PageViewController로 구현하면 안되는 이유
- HorizontalStackView + PageViewController로도 디자인만 구현할 수있지만 인터렉션에서 어떤 cell이 눌렸는지 이벤트 처리하기 쉽지 않기 때문
CollectionView에 필요한 cell, model 정의
- CollectionViewCell에서 사용될 Model 정의
struct MyCollectionViewModel {
let title: Int
}
- CollectionViewCell 정의
- cell을 선택했을 때 이벤트가 발생하는게 아닌 새로 추가한 ContentsView에 이벤트 받도록 설정
- isSelected를 override하여 선택된 cell에 음영 효과가 있도록 설정
import UIKit
import SnapKit
import RxSwift
class MyCollectionViewCell: UICollectionViewCell {
static var id: String { NSStringFromClass(Self.self).components(separatedBy: ".").last ?? "" }
var bag = DisposeBag()
var model: MyCollectionViewModel? { didSet { bind() } }
lazy var contentsView: UIView = {
let view = UIView()
view.backgroundColor = .orange.withAlphaComponent(0.5)
return view
}()
lazy var titleLabel: UILabel = {
let label = UILabel()
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubviews()
configure()
}
override var isSelected: Bool {
didSet {
contentsView.backgroundColor = isSelected ? .orange : .orange.withAlphaComponent(0.5)
}
}
override func prepareForReuse() {
super.prepareForReuse()
bag = DisposeBag()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError()
}
private func addSubviews() {
addSubview(contentsView)
contentsView.addSubview(titleLabel)
}
private func configure() {
backgroundColor = .brown
contentsView.snp.makeConstraints { make in
make.top.bottom.equalToSuperview().inset(20)
make.leading.trailing.equalToSuperview()
}
titleLabel.snp.makeConstraints { make in
make.center.equalToSuperview()
}
}
private func bind() {
titleLabel.text = "\((model?.title ?? 0))"
}
}
collectionView 구현
- CollectionView를 이용하여 수평 스크롤 뷰 구현 방법 참고: https://ios-development.tistory.com/632
- collectionView를 사용할 ViewController 생성
import UIKit
import RxGesture // collectionView의 cell내부 contentsView tapGesture() rx 사용위해 import
import RxSwift
import RxCocoa
class ViewController: UIViewController {
}
- CollectionViewCell에 사용될 dataSource 정의
var dataSource: [MyCollectionViewModel] = []
// viewDidLoad()에서 호출
private func setupDataSource() {
for i in 0...10 {
let model = MyCollectionViewModel(title: i)
dataSource += [model]
}
}
- collectionView 초기화
lazy var collectionView: UICollectionView = {
let flowLayout = UICollectionViewFlowLayout()
flowLayout.minimumLineSpacing = 12
flowLayout.scrollDirection = .horizontal
let view = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
view.backgroundColor = .lightGray
return view
}()
- addSubviews()
// viewDidLoad에서 호출
private func addSubviews() {
view.addSubview(collectionView)
}
- layout 세팅
// viewDidLoad에서 호출
private func configure() {
collectionView.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide)
make.leading.trailing.equalToSuperview()
make.height.equalTo(96)
}
}
- delegate 설정, cell 등록
private func setupDelegate() {
collectionView.delegate = self
collectionView.dataSource = self
}
private func registerCell() {
collectionView.register(MyCollectionViewCell.self, forCellWithReuseIdentifier: MyCollectionViewCell.id)
}
- delegate 구현
// ViewController 내부
func didTapCell(at indexPath: IndexPath) {
// currentPage = indexPath.item
}
}
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataSource.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MyCollectionViewCell.id, for: indexPath)
if let cell = cell as? MyCollectionViewCell {
cell.model = dataSource[indexPath.item]
cell.contentsView.rx.tapGesture(configuration: .none)
.when(.recognized)
.asDriver { _ in .never() }
.drive(onNext: { [weak self] _ in
self?.didTapCell(at: indexPath)
}).disposed(by: cell.bag)
}
return cell
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 60, height: collectionView.frame.height)
}
}
- 수평 스크롤이 되도록height를 collectionView의 height와 동일하도록 설정
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 60, height: collectionView.frame.height)
}
}
PageViewController 추가
- pageViewController에 사용되는 dataSourceVC 정의
var dataSourceVC: [UIViewController] = []
// viewDidLoad에서 호출
private func setupViewControllers() {
var i = 0
dataSource.forEach { _ in
let vc = UIViewController()
let red = CGFloat(arc4random_uniform(256)) / 255
let green = CGFloat(arc4random_uniform(256)) / 255
let blue = CGFloat(arc4random_uniform(256)) / 255
vc.view.backgroundColor = UIColor(red: red, green: green, blue: blue, alpha: 1)
let label = UILabel()
label.text = "\(i)"
label.font = .systemFont(ofSize: 56, weight: .bold)
i += 1
vc.view.addSubview(label)
label.snp.makeConstraints { make in
make.center.equalToSuperview()
}
dataSourceVC += [vc]
}
}
- pageViewController 초기화
lazy var pageViewController: UIPageViewController = {
let vc = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
return vc
}()
- 레이아웃 설정
private func configure() {
// collectionView 레이아웃 (기존에 있던 코드)
collectionView.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide)
make.leading.trailing.equalToSuperview()
make.height.equalTo(96)
}
pageViewController.view.snp.makeConstraints { make in
make.top.equalTo(collectionView.snp.bottom)
make.leading.trailing.bottom.equalToSuperview()
}
pageViewController.didMove(toParent: self)
}
- delegate, dataSource 설정
private func setupDelegate() {
// collectionViewController 관련 코드 - 원래 있던 코드
collectionView.delegate = self
collectionView.dataSource = self
pageViewController.delegate = self
pageViewController.dataSource = self
}
- firstViewController 설정
// viewDidLoad에서 호출
private func setViewControllersInPageVC() {
if let firstVC = dataSourceVC.first {
pageViewController.setViewControllers([firstVC], direction: .forward, animated: true, completion: nil)
}
}
- delegate 구현
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 60, height: collectionView.frame.height)
}
}
extension ViewController: UIPageViewControllerDataSource, UIPageViewControllerDelegate {
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let index = dataSourceVC.firstIndex(of: viewController) else { return nil }
let previousIndex = index - 1
if previousIndex < 0 {
return nil
}
return dataSourceVC[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let index = dataSourceVC.firstIndex(of: viewController) else { return nil }
let nextIndex = index + 1
if nextIndex == dataSourceVC.count {
return nil
}
return dataSourceVC[nextIndex]
}
}
CollectionView와 PageViewController 연동
- currentPage 정의: 현재 페이지가 collectionView나 pageViewController 둘 중 하나로 바뀌면 싱크를 맞추는 옵저버 프로퍼티
var currentPage: Int = 0 {
didSet {
bind(oldValue: oldValue, newValue: currentPage)
}
}
private func bind(oldValue: Int, newValue: Int) {
// collectionView 에서 선택한 경우
let direction: UIPageViewController.NavigationDirection = oldValue < newValue ? .forward : .reverse
pageViewController.setViewControllers([dataSourceVC[currentPage]], direction: direction, animated: true, completion: nil)
// pageViewController에서 paging한 경우
collectionView.selectItem(at: IndexPath(item: currentPage, section: 0), animated: true, scrollPosition: .centeredHorizontally)
}
- viewDidAppear 시 0번 페이지가 선택되도록 설정
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
currentPage = 0
}
- ColelctionViewCell안에 contentsView가 tap될때 반응 이벤트 바인딩
func didTapCell(at indexPath: IndexPath) {
currentPage = indexPath.item
}
- pageViewController 애니메이션이 끝난 경우 호출되는 UICollectionViewDeleagteFlowLayout 델리게이트
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
guard let currentVC = pageViewController.viewControllers?.first,
let currentIndex = dataSourceVC.firstIndex(of: currentVC) else { return }
currentPage = currentIndex
}
* source code: https://github.com/JK0369/ViewPagerSample
'UI 컴포넌트 (swift)' 카테고리의 다른 글
[iOS - swift] UI 컴포넌트 - Horizontal Scroll View (수평 스크롤 뷰) (0) | 2021.09.29 |
---|---|
[iOS - swift] UI 컴포넌트 - Rating View (별점, 평점, tag 사용) (1) | 2021.07.23 |
[iOS - swift] UI 컴포넌트 - Pinterest(핀터레스트, 인스타그램) UI 만들기 (CollectionView) (0) | 2021.07.20 |
[iOS - swift] UI 컴포넌트 - GradientView (흐려지는 뷰) (0) | 2021.07.17 |
[iOS - swift] UI 컴포넌트 - 검색창 TextField (SearchTextField) (0) | 2021.07.17 |
Comments