JavaScriptプログラマがSwift iOSアプリを2週間で作って公開してみた〜その19 Tutorial Page〜


JavaScriptプログラマー(JSer)がSwiftデビューして、ただ作りたいアプリを作ってみたシリーズ第19回目です。

前回は保存したデータをNSDateを使った日付の比較をして消す処理についてでした。今回はアプリを初めて起動した時に表示されるよくあるチュートリアルのページのUIScrollViewを使った実装について紹介します。

TwitStockerを初めて起動すると、以下のようなスワイプ可能な画像が並んだチュートリアルページが最初に表示されます。

スクリーンショット 2015-03-14 13.10.26

左スワイプをしていくと一枚ずつ流れていよくみるタイプのチュートリアル画面ですが、swiftではCoreのフレームワークにこれを容易に実現できる部品が提供されています。UIScrollViewというクラスです。

チュートリアル画像の用意

TwitStockerでは米国向けApp Storeにも提供するためにチュートリアル画像を各言語で用意しています。
画像ファイルは以下のようにintro_{n}_{country}と名前をつけています。(例: intro_1_en, intro_3_jaとか)

スクリーンショット 2015-03-14 13.25.11

画像のサイズは普通のサイズが475×327, Retina用が950×654です。事前にmacのプレビューなどで作成してxcodeのプロジェクトに置いておきます。(画像の設置などについては第5回目記事のアイコンの設定あたりで解説しています。)

UIScrollViewを使ったチュートリアルの実装

UIScrollViewを使ってチュートリアルページの実装をしているswiftファイルはこの連載の第4回目記事Login with Twitter Fabricで紹介したLoginViewController.swiftを拡張していきます。

LoginViewControllerはAppDelegateから起動時にログイン時のセッションがない場合に表示されるViewControllerです。(ログインセッションがある場合はスキップされる)

以下がTwitStockerの実装です。

import Foundation
import TwitterKit

class LoginViewController: UIViewController {

    var alert : UIAlertController?
    var pageControl: UIPageControl!
    var scrollView: UIScrollView!
    
    // チュートリアルページの数
    let pageSize = 4
    // 各ページ上部に表示されるラベルの高さ
    let titleH: CGFloat = 70.0
    // チュートリアルページのページを表すindicatorの高さ
    let controlH: CGFloat = 20.0
    // 画面下部のtwitter loginボタンの高さ
    let twBtnH: CGFloat = 80.0
    // 埋め込む画像のの高さと
    let imageW: CGFloat = 475
    let imageH: CGFloat = 327
    let paddingTop: CGFloat = 20.0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let width = self.view.frame.maxX, height = self.view.frame.maxY
        self.view.backgroundColor = Constants.Theme.concept()
        // swipeしてページをめくるViewの生成
        scrollView = UIScrollView(frame: self.view.frame)
        scrollView.showsHorizontalScrollIndicator = false;
        scrollView.showsVerticalScrollIndicator = false
        scrollView.pagingEnabled = true
        scrollView.delegate = self
        scrollView.contentSize = CGSizeMake(CGFloat(self.pageSize) * width, 0)
        self.view.addSubview(scrollView)
        for var i = 0; i < self.pageSize; i++ {
            let x = CGFloat(i) * width
            let margin = height - imageH - twBtnH - paddingTop * 2 - controlH
            // 各ページ上部のラベル
            let introTitle:UILabel = UILabel(frame: CGRectMake(x + 10, paddingTop * 2 + controlH + (margin - titleH) / 2, width - 20, titleH))
            introTitle.textColor = UIColor.whiteColor()
            introTitle.textAlignment = NSTextAlignment.Center
            introTitle.text = NSLocalizedString("tutorial_title_" + String(i + 1), comment: "")
            introTitle.numberOfLines = 0
            introTitle.lineBreakMode = NSLineBreakMode.ByWordWrapping
            scrollView.addSubview(introTitle)
            // 表示する画像の位置調整
            let diffW = (width - imageW) / 2
            let imageView: UIImageView = UIImageView(frame: CGRectMake(x + diffW, height - imageH - twBtnH, imageW, imageH))
            // 国際化対応するためにNSLocalizedStringを使って画像ファイルを指定する
            let image = UIImage(named: NSLocalizedString("tutorial_image_" + String(i + 1), comment: ""))
            imageView.image = image
            scrollView.addSubview(imageView)
        }
        // チュートリアルのページindicatorの表示
        pageControl = UIPageControl(frame: CGRectMake(0, paddingTop * 2, width, controlH))
        pageControl.numberOfPages = self.pageSize
        pageControl.currentPage = 0
        pageControl.userInteractionEnabled = false
        self.view.addSubview(pageControl)
        // ログインボタンの設置
        let logInButton = TWTRLogInButton(logInCompletion: {
            (session: TWTRSession!, error: NSError!) in
            if session != nil {
                UIApplication.sharedApplication().keyWindow?.rootViewController = MainTabViewController()
            }
        })
        logInButton.frame = CGRectMake(0, self.view.frame.maxY - twBtnH, width, twBtnH)
        self.view.addSubview(logInButton)
    }
}

extension LoginViewController: UIScrollViewDelegate {
    func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
        if fmod(scrollView.contentOffset.x, scrollView.frame.maxX) == 0 {
            pageControl.currentPage = Int(scrollView.contentOffset.x / scrollView.frame.maxX)
        }
    }
}

各部品の配置をそれぞれスクリーンサイズなどを取って指定しているので、少し煩雑なコードになっています。本当はstoryboardとかを使うと綺麗になるのかもしれませんが使い方が未だによくわからないのでコードで位置指定する方針を一貫して取っていますw

基本的にはスクリーンの数だけforループでUILabelとUIImageを作成してscrollViewに追加していきます。あとはScrollViewのdelegateをextensionに記述します(ここはちょっとしたおまじない)。

国際化のためのポイントはNSLocalizedString(“tutorial_image_” + String(i + 1)この部分で各言語のlabelリソースファイルに記述されているイメージファイルを取りに行っているところです。
例えば日本語のLocalizable.stringsには以下のように記述されています。

"tutorial_image_1" = "intro_1_ja";
"tutorial_image_2" = "intro_2_ja";
"tutorial_image_3" = "intro_3_ja";
"tutorial_image_4" = "intro_4_ja";

国際化については次回の記事で詳しく説明する予定です。

こんな感じで配置のための位置計算の部分が少し煩雑ですが、それ以外の部分はフレームワークに提供される部品をただ使えば簡単に実装できました!
以上、今回はチュートリアルページの実装についてでした。次回は今回の記事でも少してきたNSLocalizedStringあたりの国際化について解説します。

TwitStockerのダウンロードはこちらから。
https://itunes.apple.com/en/app/twitstocker/id958798898?l=ja&ls=1&mt=8