Swift

情弱だが巷のiOSアプリ初回起動時に出てくるスライドするヤツを作りたい

More than 3 years have passed since last update.

最近iOS開発もはじめてみました。
まあ慣れないのでちょっとしたことで躓きますね。

無駄に長いけど大したことは書いてないです。

UIPageViewControllerってどうやって使うの?

表題のようなことしたい。
ヤフーで調べるとUIPageViewControllerを使うよ、と言われました。
というわけでいろんなブログやら何やらを見たが、自分がアホすぎて全く頭に入ってこなかった。
なので手順をだらだらとメモ。

基本、Xcodeでサンプルが用意されてるので、それを見ればほぼ終わりです。
↓このPage-Based Applicationってヤツですね。

スクリーンショット 2014-12-09 22.11.15.png


まず、ストーリボードにUIPageViewControllerあるんだろうと見てみた。

ないわ。。

ストーリボードに置いてあるのはRootViewControllerとDataViewControllerの2つ。
2つともプレーンなUIViewControllerだった。

まず、RootViewControllerという親ViewControllerを見る。
中でUIPageViewControllerインスタンスを作って、addChildViewControllerしてた。
AndroidでいうところのFragmentっぽい感じ。
このやり方がポピュラーなのだろうか?

UIPageViewControllerはあくまで複数のViewControllerを表示するための枠。
なので、どんな内容のViewControllerを表示すべきか、という判断は別のオブジェクトに委譲するようになってる。
Androidではこういう系のオブジェクトはAdapterって呼ばれてる。iOSではDataSourceという名前みたい。

サンプルではModelControllerというデータソースオブジェクトが定義されてた。

ModelController.swift
class ModelController: NSObject, UIPageViewControllerDataSource {
    // blahblah...
}

↑単なるオブジェクト。
これにデータソースな振る舞いをさせるためにはUIPageViewControllerDataSourceプロトコルを実装する必要がある。

https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIPageViewControllerDataSourceProtocolRef/index.html

ModelControllerでは

pageViewController(_:viewControllerBeforeViewController:)

pageViewController(_:viewControllerAfterViewController:)

この2つを実装してる。

このメソッドの中で画面をスワイプしたときにどんなUIViewControllerを表示すべきか、という判断をして具体的なUIViewControllerを返す。

pageViewController
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
        var index = self.indexOfViewController(viewController as DataViewController)
        if (index == 0) || (index == NSNotFound) {
            return nil
        }

        index--
        return self.viewControllerAtIndex(index, storyboard: viewController.storyboard!)
}

viewControllerBeforeViewController,viewControllerAfterViewControllerってヤツが重要。
このviewControllerが何番目に表示されてるか、という情報を変数indexに格納して減算、その減算した位置に対応するUIViewControllerを作って返してる。

  • 現在地を取得してindexに格納
  • indexを加減算
  • indexに対応するUIViewControllerを取得してreturn

現在地を取得してindexに格納

indexself.indexOfViewControllerというメソッドで計算する。

indexOfViewController
func indexOfViewController(viewController: DataViewController) -> Int {
        // Return the index of the given data view controller.
        // For simplicity, this implementation uses a static array of model objects and the view controller stores the model object; you can therefore use the model object to identify the index.
        if let dataObject: AnyObject = viewController.dataObject {
            return self.pageData.indexOfObject(dataObject)
        } else {
            return NSNotFound
        }
}

与えられたDataViewControllerからdataObject(var dataOject: AnyObject?って定義されてた。)を取り出して、そのdataObjectがModelController自身が保持する配列pageDataの中の何番目に格納されてるか?という処理。

indexを加減算

index++index--

indexに対応するUIViewControllerを取得してreturn

pageViewControllerの最後にreturn self.viewControllerAtIndex(index, storyboard: viewController.storyboard!)って処理がある。
ここでUIViewControllerを生成してる。

viewControllerAtIndex
func viewControllerAtIndex(index: Int, storyboard: UIStoryboard) -> DataViewController? {
        // Return the data view controller for the given index.
        if (self.pageData.count == 0) || (index >= self.pageData.count) {
            return nil
        }

        // Create a new view controller and pass suitable data.
        let dataViewController = storyboard.instantiateViewControllerWithIdentifier("DataViewController") as DataViewController
        dataViewController.dataObject = self.pageData[index]
        return dataViewController
}

最初の文ではバリデーション的なことやってる。
pageDataが1件もなければ返すViewControllerは無いので、nilを返す。indexがpageDataのサイズより大きい場合も同様。

次の文では与えられたストーリボードからViewController(DataViewController)を生成、dataObjectをセットして返してる。

サンプルではpageDatainit()の中で定義されていた。
このpageDataがAPIを叩いて取得する場合はどうするのが良いのか、とか思う。RootViewControllerとかでAPIを叩いてその結果をデリゲートとかで受け取ってDataSourceのpageDataに突っ込むとかするのかな。。いったん放置で。

ちなみにpageDataは月の配列。

UIPageViewControllerの使い方はだいたい分かった。アプリ初回起動後に出てくるスライドするヤツ作りたいんだけど

まず、ModelController内であらかじめアプリ内に用意されてる画像を表示する場合は画像へのパスを配列として保持しておく。
で、サンプルでいうところのDataViewController内で描画すれば良さそうですね。

AssetCatalog(Image.xcassetsってヤツ)を利用してスライド表示する画像を用意する

何はなくとも画像です。

適当な画像を放り込むと解像度にあわせて画像を使い分けてくれます。
今回はhoge,huga,piyoというアセットを作りました。

DataViewControllerにUIImageViewを配置する

ストーリボードから配置してください。
元々置いてあったUILabelは削除しました。

ModelControllerのpageDataをアセットの配列に置き換える

ModelController.swift
var imageData = NSArray()

override init() {
    super.init()
    // Create the data model.
    imageData = ["hoge","huga","piyo"]
}

上のように元々あったvar pageDataimageDataに置き換えて、中身をアセット名を抱えた配列にしました。
残りのコードも適当にimageData向けに書き換えます。

DataViewControllerで画像を表示する

下記のように書き換えました。

DataViewController.swift
import UIKit

class DataViewController: UIViewController {

    @IBOutlet weak var dataImage: UIImageView!
    var imagePath: String?


    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        dataImage.contentMode = .ScaleAspectFit
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)

        if let path: String = imagePath {
            self.dataImage.image = UIImage(named: path)
        } else {
            self.dataImage.image = UIImage(named: "hoge")
        }
    }


}

大したことはやってなくて、dataObjectの中身をUILabelで表示するという元来の処理を、imagePathで指定された画像を表示するように変えただけです。

これで⌘+rしたら

こんな感じになるとおもいます。

Gyazo

勉強になりました、シェアさせてください。