8
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

RXSwiftを使ったUIScrollViewのページング

Last updated at Posted at 2019-01-15

概要

iOSアプリのウォークスルーでよく使用されている、RxSwiftとUIScrollViewを使ったページングの実装についてメモを書いていきます。
最近作ったゴミアプリという個人プロジェクトのアプリで実際に使用したコードを基にこの記事は書かれています。

スクリーンショット

Code

ViewController
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に対し、UIScrollViewcontentOffsetに基づいた数値を加工して渡しているだけです。その加工と受け渡し部分でRxSwift(RxCocoa)を使っているのでコンパクトでわかりやすい、というだけです。

Observable.just(colors.count)
  .bind(to: pageControl.rx.numberOfPages)
  .disposed(by: bag)

colorsが動的に変化する仕様ではないためRxSwiftを使った書き方するメリットは皆無なのですが、サンプルなので敢えて書いてみました。colorsのカウント(ここでは3)を基にObservableを生成し、pageControlnumberOfPagesにbindしています。

scrollView.rx.didScroll
  .withLatestFrom(scrollView.rx.contentOffset)
  .map { Int(round($0.x / pageWidth)) }
  .bind(to: pageControl.rx.currentPage)
  .disposed(by: bag)

流れとしては以下のようになります。

  1. scrollView.rx.didScrollでスクロールのイベントを取得
  2. withLatestFromで、そのタイミングでscrollView.rx.contentOffsetに変換しスクロール位置を取得
  3. UIScrollViewの仕様ではスクロール位置がページの真ん中を超えた場合にページの切り替えだ行われます。その仕様に従い、contentOffset.xを1ページの横幅であるpageWidthでわり、四捨五入をページとしてmapで渡します。
  4. pageControl.rx.currentPagebindして、ページコントロールのドット位置が変わります。
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

またゴミアプリという個人プロジェクトのアプリをリリースしました。
よかったらダウンロードしてみてください。

8
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?