Help us understand the problem. What is going on with this article?

UICollectionView+UIPageControlをRxで書いてみた

はじめに

本業では、Rxをほぼ使わないので、Rx使いたい衝動から表題のようなものを作ってみた。
アプリ界隈の初回起動チュートリアルとかでよく使うあれですね。
最近のアプリのエンジニアなら誰しもが作ったことあるんじゃないかという、UIScrollView+UIPageControllですね。
調べるとめちゃくちゃでてきますが、今回記事に書くのは一番簡単そうに書ける方法かつ、カスタマイズ性もあるものかなと自分は思います。

どんなの?

今回はUIScrollViewの代わりにUICollectionViewを採用しています。
UICollectionViewだと横スクロールのコレクションもcell投入も見慣れた書き方ができるので、綺麗なコードに仕上がりますね。
他にはUIScrollViewの内部にUIStackView+UIView(複数)を採用しているのも見受けますね。
両者の差といえば、reuseが走るかどうかにあると思います。
アプリ内埋め込みで再利用が必要にないやつであれば、UIStackView+UIView(複数)を採用すると良いでしょう。

成果物

見栄え的に色変えるのも寂しいのでlottieのデモを入れてみました。

uicollectionview_uipagecontroll.gif

UIScrollView+UIPageControllのバインド

https://qiita.com/higan96/items/a449b4515e06b0f586a9

上記の記事が大変参考になりました。(丸パクリですがw)
これだけでスクロール位置に対してのページbindができます。
nextButtonの処理以外は綺麗だ!!

final class ViewController: UIViewController {

    @IBOutlet private weak var pageControll: UIPageControl!

    @IBOutlet private weak var collectionView: UICollectionView! {
        willSet {
            newValue.register(cellType: PageItemCell.self)
        }
    }

    @IBOutlet private weak var nextButton: UIButton!

    private let disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()

        Observable.just(Const.lotties.count)                    // ページ数
            .bind(to: pageControll.rx.numberOfPages)            // ページコントロールにbind
            .disposed(by: disposeBag)

        collectionView.rx.didScroll                             // スクロール位置変更を流す
            .withLatestFrom(collectionView.rx.contentOffset)    // 最新値contentOffset
            .map { Int($0.x / UIScreen.main.bounds.width) }     // ページ番号に加工
            .bind(to: pageControll.rx.currentPage)              // ページコントロールの現在ページにバインド
            .disposed(by: disposeBag)

        nextButton.rx.tap
            .filter { [unowned self] in
                self.pageControll.currentPage < (Const.lotties.count - 1) // ページ数超えないようfilter
            }
            .subscribe(onNext: { [unowned self] _ in
                self.collectionView.setContentOffset(CGPoint(x: self.collectionView.contentOffset.x + UIScreen.main.bounds.width, y: 0), animated: true)  // スクロール移動
            })
            .disposed(by: disposeBag)
    }
}

extension ViewController {

    struct Const {
        static let lotties = [
            "test0",
            "test1",
            "test2"
        ]
    }
}

UICollectionViewDelegate

xib系のloadをtype-safeに扱えるReusableを採用しています。
かなり軽量ですので、自作してもいいところですが、今回はプライベートなので、ガンガンライブラリ扱います。

// MARK: - UICollectionViewDataSource
extension ViewController: UICollectionViewDataSource {

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return Const.lotties.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell: PageItemCell =  collectionView.dequeueReusableCell(for: indexPath)
        cell.setData(lottieFile: Const.lotties[indexPath.item])
        return cell
    }
}

// MARK: - UICollectionViewDelegateFlowLayout
extension ViewController: UICollectionViewDelegateFlowLayout {

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return collectionView.frame.size
    }
}

まとめ

めちゃくちゃ短く、綺麗に書けましたね。
実際、delegateに頼った書き方でもこれよりそこまで長く書くことにはなりませんがw
私のRx書きたい欲が少し収まりました!

いろんなアプリで必要になるこのコンポーネント、是非参考にしながら実装していただけると幸いです。

下記がgithubのソースコードです。
https://github.com/doihei/UIScrollView-UIPageControll

doi_daihei
現在、株式会社andfactoryに所属しております。 前職ではフリーランス。 基本的には常駐しておりまして、並行して以下の会社のエンジニアとして所属しておりまして、複数のプロジェクトに参画。 https://ks-rogers.co.jp/ バックエンドからフロントエンド、チームリーダー、プログラミング講師などエンジニアとして幅広く経験をする。
https://andfactory.co.jp/
andfactory
Smartphone Idea Companyとして、人々の生活に「&(アンド)」を届ける。
https://andfactory.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away