概要
iOSアプリのウォークスルーでよく使用されている、RxSwiftとUIScrollViewを使ったページングの実装についてメモを書いていきます。
最近作ったゴミアプリという個人プロジェクトのアプリで実際に使用したコードを基にこの記事は書かれています。
スクリーンショット
Code
import UIKit
import RxSwift
import RxCocoa
import SnapKit
class ViewController: UIViewController {
@IBOutlet weak private var pageControl: UIPageControl!
@IBOutlet weak private var scrollView: UIScrollView!
private let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
let pageWidth = UIScreen.main.bounds.width
let pageHeight = scrollView.bounds.height
let colors: [UIColor] = [
.red, .green, .blue
]
scrollView.contentSize = CGSize(width: pageWidth * CGFloat(colors.count), height: pageHeight)
scrollView.isPagingEnabled = true
scrollView.showsHorizontalScrollIndicator = false
colors.enumerated().forEach { index, color in
let page = UIView()
let offset = CGFloat(index) * pageWidth
scrollView.addSubview(page)
page.backgroundColor = color
// SnapKitを使用しAutoLayoutの制約をコードで記述
page.snp.makeConstraints { make in
make.top.equalTo(scrollView)
make.left.equalTo(scrollView).offset(offset)
make.height.equalTo(scrollView)
make.width.equalTo(pageWidth)
}
}
Observable.just(colors.count)
.bind(to: pageControl.rx.numberOfPages)
.disposed(by: bag)
scrollView.rx.didScroll
.withLatestFrom(scrollView.rx.contentOffset)
.map { Int(round($0.x / pageWidth)) }
.bind(to: pageControl.rx.currentPage)
.disposed(by: bag)
}
}
解説
UIScrollView
のページング自体は特に実装側でやることはありません。気を使うのはcontentSize
とサブビューのサイズをあわせることぐらいでしょう。
基本的にやっていることはUIPageControl
に対し、UIScrollView
のcontentOffset
に基づいた数値を加工して渡しているだけです。その加工と受け渡し部分でRxSwift(RxCocoa)を使っているのでコンパクトでわかりやすい、というだけです。
Observable.just(colors.count)
.bind(to: pageControl.rx.numberOfPages)
.disposed(by: bag)
colors
が動的に変化する仕様ではないためRxSwiftを使った書き方するメリットは皆無なのですが、サンプルなので敢えて書いてみました。colors
のカウント(ここでは3
)を基にObservableを生成し、pageControl
のnumberOfPages
にbindしています。
scrollView.rx.didScroll
.withLatestFrom(scrollView.rx.contentOffset)
.map { Int(round($0.x / pageWidth)) }
.bind(to: pageControl.rx.currentPage)
.disposed(by: bag)
流れとしては以下のようになります。
-
scrollView.rx.didScroll
でスクロールのイベントを取得 -
withLatestFrom
で、そのタイミングでscrollView.rx.contentOffset
に変換しスクロール位置を取得 -
UIScrollView
の仕様ではスクロール位置がページの真ん中を超えた場合にページの切り替えだ行われます。その仕様に従い、contentOffset.x
を1ページの横幅であるpageWidth
でわり、四捨五入をページとしてmapで渡します。 -
pageControl.rx.currentPage
にbind
して、ページコントロールのドット位置が変わります。
page.snp.makeConstraints { make in
make.top.equalTo(scrollView)
make.left.equalTo(scrollView).offset(offset)
make.height.equalTo(scrollView)
make.width.equalTo(pageWidth)
}
また細かい点ですが、このコードの場合Storyboardを使用したAutoLayoutでViewController
のUIを記述しているため、サブビューとして生成するUIView
もCGRectなどは使わずAutoLayoutで記述します(ここではSnapKit
というライブラリを使って記述しています)。
以上です。
こちらの記事のサンプルコードはGithubに上げました。higan96/RXSwiftScrollView
またゴミアプリという個人プロジェクトのアプリをリリースしました。
よかったらダウンロードしてみてください。