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

 
こんばんは！

セプテーニ・オリジナルの寺坂です。

現在、エンジニア一年目。
勉強の日々でございます。

***

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

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

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


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

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

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

UIを指しています。


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


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

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

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


##1. UICollectionViewControllerを継承させる

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

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

**手順**

* StoryboardにCollectionViewを追加
* Layoutは「Flow」
* Scroll Directionは「Horizontal」
* Scrollingの「Paging Enabled」にチェック

* UICollectionViewControllerを継承したPageCollectionViewControllerを作成
* StoryboardのCollectionViewのコントローラーにPageCollectionViewControllerを指定



**実装**

```swift
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を作る

**実装**

```swift
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にはり付ける

**実装**

```swift
  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を指定

**実装**

```swift
	/// セルの大きさ
    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行。


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

### 追記
2015/01/15 - コメントで指摘頂いた内容を反映し、下記を追加

```swift
self.collectionView?.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: "PageCollectionViewCell")
```

**実装**

```swift

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()
        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
        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
    }
}

```