FLINTERS Engineer's Blog

FLINTERSのエンジニアによる技術ブログ

【swift】スワイプ移動できるページUIを50行で実装する


こんばんは!
セプテーニ・オリジナルの寺坂です。

現在、エンジニア一年目。

勉強の日々でございます。

いまの仕事で「iOS開発」に携わっているので、
今回は、Swiftネタを書きます。

つたない所があるかもしれませんが、
温かい目で最後までご一読いただけると嬉しいです!

それでは、よろしくお願いします。

スワイプ移動ページUIの実現

ここでの スワイプ移動ページUI とは、

「あらかじめ複数のページを横に並べておいて、
スワイプによって横スクロール式にページを切り替える」
UIを指しています。


たとえば、
トップにナビゲーションバーやセグメントを固定しておいて、
その下のページをスワイプで切り替えたい時

などを想定してます。

横スクロール型UIと言ってもいいかもしれませんね。

CollectionViewなら実装コードが少なくなる

このスワイプ移動ページUIを実現する方法はいくつかありますが、
PageViewとかScrollViewを使うと、実装の手間がかかりがちです。

そこで、今回はより簡単な方法を模索して、
UICollectionViewを使ったスワイプ移動ページUI
を実現したいと思います。

1. UICollectionViewControllerを継承させる

まず始めに、Storyboard上にUICollectionViewControllerを追加して、
コントローラーとひもづけましょう。

UICollectionView自体の解説は本筋から外れるので、
詳細は割愛してざっくり説明で進みます。

手順

  • StoryboardにCollectionViewを追加
  • Layoutは「Flow」
  • Scroll Directionは「Horizontal」
  • Scrollingの「Paging Enabled」にチェック
  • UICollectionViewControllerを継承したPageCollectionViewControllerを作成
  • StoryboardのUICollectionViewControllerのコントローラーにPageCollectionViewControllerを指定

実装

import UIKit

class PageCollectionViewController: UICollectionViewController {
    
    var pages:Pages = Pages(){
        didSet {
            self.collectionView?.reloadData()
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

2. データの準備とUICollectionViewDelegateの実装

最低限、セルの数とセルの種類を返すdelegateは実装しましょう。

手順

  • 構造体を定義
  • 組み込みたいページを追加
  • "PageCollectionViewCell"というIDでUICollectionViewCellを用意
  • 構造体に定義されている数だけ、CollectionViewCellを作る

実装

import UIKit

struct Pages {
    var viewControllers:[UIViewController] = []
}

class PageCollectionViewController: UICollectionViewController {
    
    var pages:Pages = Pages(){
        didSet {
            self.collectionView?.reloadData()
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let page1 = UIViewController()
        page1.view.backgroundColor = UIColor.yellowColor()
        self.pages.viewControllers.append(page1)
        
        let page2 = UIViewController()
        page2.view.backgroundColor = UIColor.blueColor()
        self.pages.viewControllers.append(page2)        
    }

    // MARK: - UICollectionViewDelegate
    
    override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

        return self.pages.viewControllers.count

    }
    
    override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {

        var cell = collectionView.dequeueReusableCellWithReuseIdentifier("PageCollectionViewCell", forIndexPath: indexPath) as UICollectionViewCell

        let view = self.pages.viewControllers[indexPath.row].view
                
        return cell
    }
    
}

3. セルにページをaddSubviewする

次の部分を編集して、
indexPathに対応するviewをセルに貼付けましょう。

手順

  • indexPathに対応するviewを取り出す
  • cell.contentViewにはり付ける

実装

  override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {

    var cell = collectionView.dequeueReusableCellWithReuseIdentifier("PageCollectionViewCell", forIndexPath: indexPath) as UICollectionViewCell
    
    let view = self.pages.viewControllers[indexPath.row].view
    cell.contentView.addSubview(view)

    return cell
  }

}

4.セルのサイズを動的に変える

ここまでの実装で、セルにページ情報を表示させることが出来ますが、
セルのサイズが小さいままです。

次は、セルサイズを画面全体の大きさに変動させましょう。

そのためには、作成したPageCollectionViewControllerに
UICollectionViewDelegateFlowLayoutのメソッドを実装します。

ここでは次のようにコードを追加します。

1つ目のメソッドで返すCGSizeがセルの大きさになり、
残りの2つは、それぞれ横のスペース・縦のスペースになります。
(後の2つはStoryboardからも設定可)

注意点

  • 状況によってはAutoLayoutとの兼ね合いに注意が必要です

手順

  • メソッド内で実現したいCGSizeを求めて返す
  • セルとセルの間スペースは、0を指定

実装

	/// セルの大きさ
    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
    
        var pageViewRect = self.view.bounds        
        
        return CGSize(width: pageViewRect.width, height: pageViewRect.height)
    }
     
    /// 横のスペース
    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat {

        return 0.0

    }
    
    /// 縦のスペース
    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat {

        return 0.0

    }

完成

以上です。

最後に完成したコードを載せておきます。
コードを整理してコンパクトにすると、ぴったり50行。

最後まで読んでくださり
ありがとうございました!

元記事は、こちらです。
[swift]スワイプ移動できるページUIを50行で実装する

実装

import UIKit

struct Pages {
    var viewControllers:[UIViewController] = []
}

class PageCollectionViewController: UICollectionViewController {
    
    var pages:Pages = Pages(){
        didSet { self.collectionView?.reloadData() }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        self.collectionView?.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: "PageCollectionViewCell")

        let page1 = UIViewController()
        page1.view.backgroundColor = UIColor.yellowColor()
        self.pages.viewControllers.append(page1)
        
        let page2 = UIViewController()
        self.pages.viewControllers.append(page2)
    }

    // MARK: - UICollectionViewDelegate
    
    override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return self.pages.viewControllers.count
    }
    
    override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        var cell = collectionView.dequeueReusableCellWithReuseIdentifier("PageCollectionViewCell", forIndexPath: indexPath) as UICollectionViewCell
        let view = self.pages.viewControllers[indexPath.row].view
        cell.contentView.addSubview(view)
        return cell
    }
    
    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
        var pageViewRect = self.view.bounds
        return CGSize(width: pageViewRect.width, height: pageViewRect.height)
    }
    
    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat {
        return 0.0
    }
    
    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat {
        return 0.0
    }
}