vol.1 起動画面(Launch Screen)を制作する – SwiftでiOS用のクイズアプリを作る!

iOS Simulator Screen Shot 2015.04.03 0.58.49

いよいよ今回からSwiftでiOS用のクイズアプリの制作をはじめていきます!

ポイント

今回のポイントとしては「サンプルクイズ」というタイトルと「これはiOSアプリ開発の習作です。」という説明文を天地左右(上下左右)に配置することです。

その際に基準点としてはタイトルと説明文をグループ化して捉えた場合の中心点が画面の天地左右にくるようにします。わかりにくいので図で説明すると以下のようなイメージです。

0

縦横に引かれた緑色の線がクロスする位置がタイトル文字と説明文を合わせた領域(B)の中心点にくるようにしたいと思います。

やり方の想定

  1. Aビューの上にBビューを作成
  2. Bビュー内にタイトルと説明文を表示するためのラベルを2つ作成
  3. タイトルのラベルはBビューに対して上寄せに、説明文はBビューに対して下寄せに配置する
  4. 最後にAビューに対してBビューが天地左右にくるようにして完成!

こんな感じでいけるのでは?というのが現在の私の知識での作成予想です。

つまりAビューが親で、その子要素にBビュー、さらにBビューの子要素としてタイトルと説明文を表示するためのラベルがあるということですね。

実際の作業

起動画面をコードから編集することはできない!

いきなり初回からコレで大きく躓いてしまいました^^;

起動画面(Launch Screen)はLaunchScreen.xibをストーリーボードと同じようにビジュアルでレイアウトしていくことはできますがコードから編集することはできないようです?多分。

どうやらそもそも起動画面は1枚画像を表示するのがこれまでのやり方で、LaunchScreen.xibというカタチで編集できるようになったのもXcode6からでつい最近からなんだそうです。

確かに画面構築を待つ間に表示する起動画面の構築に時間がかかっては本末転倒なのでこの仕様は正しいといえば正しい・・・。

さっそく今回の制作の趣旨である「コードのみで」というところに反してしまうのでどうしようか悩んだ末、考え方を変えてLauncScreen.xibを使わずにViewControllerを一定時間経過で強制遷移させることで擬似的に起動画面を再現するという方法を採用してみることにしました。

デフォルトの起動画面を表示しないようにする

スクリーンショット 2015-04-02 19.54.34

不要になったデフォルトの起動画面を表示しない方法がよくわからなかったので LaunchScreen.xibをいきなり削除してみましたが、なぜかシミュレーター上ではデフォルトの起動画面が表示されたままに。

そこでよく調べてみるとプロジェクトの設定画面の中に「Launch screen interface file base name」という項目があり、ここの初期値に「LaunchScreen」とあるので、これを空にしたら表示されなくなりました(上画像参照)。

起動画面を表示しない場合はこの方法だけで良いのですが、初期画面が構築されて描画するまで黒い画面が表示されてしまうのでこの方法はあくまで今回のみの緊急措置と思ってください。

そして私は削除してしまったLaunchScreen.xibをどうにか復活できないものかと小一時間ほどネットを彷徨ってしまったのでした;;

※この方法だとiTunesConnectに送信する際のValidateでエラーが出ることがわかりましたので今回以外ではこの方法は使用しないようにしてください。

メインスクリーンを着色

気をとり直して、これで起動画面を直接すっ飛ばしてViewControllerが表示されるようになったので、今回はこのデフォルトのViewControllerを起動画面にすることにしてコードを書いていきます。

まず、背景色が白のままではわかりにくいですしワイヤーフレームとの差別化も必要なのでメインスクリーン(ビューA)に色をつけることにします。

//メインスクリーン(ビューA)の背景色を青に設定
self.view.backgroundColor = UIColor(red:0.29,green:0.53,blue:0.91,alpha:1.0)

SwiftというかiOSでの色指定はこんな感じでやるそうです。

見慣れた#FFFFFFなどの方法が使えませんし、こんな色指定をするソフトなどを他でみたことがないので面くらいましたがこの形式での色抽出ができるサイトがあったのでそちらを利用することでなんとかなりました。

その際にカラーコードで色指定して変換できるようにするエクステンションも同時に発見しましたが今回は学習用途なので使わずにデフォルトであるこの方法で色指定しました。

ビューBを作成

//ビューBを作成
let viewB = UIView(frame:CGRectMake(0, 0, 0, 0))
viewB.setTranslatesAutoresizingMaskIntoConstraints(false)
self.view.addSubview(viewB)

画面中心(上下左右)に配置するためのビューBを作成しました。
サイズ等は後で説明するVFLで指定するのでここでは0のままで作成します。

ポイントとしては「viewB.setTranslatesAutoresizingMaskIntoConstraints(false)」という部分で、AutoLayoutをコードでする際はこの指定がないと制約をつけても反映されないので忘れないようにしましょう。

タイトルと説明文のラベルを作成

//タイトルのラベルを作成
let titleLabel = UILabel(frame:CGRectMake(0, 0, 0, 0))
titleLabel.text = "サンプルクイズ"
titleLabel.textColor = UIColor.whiteColor()
titleLabel.font = UIFont(name: "HiraKakuProN-W6", size: 24)
titleLabel.textAlignment = NSTextAlignment.Center
titleLabel.setTranslatesAutoresizingMaskIntoConstraints(false)
viewB.addSubview(titleLabel)
        
//説明文のラベルを作成
let descLabel = UILabel(frame:CGRectMake(0, 0, 0, 0))
descLabel.text = "これはiOSアプリ開発の習作です。"
descLabel.textColor = UIColor.whiteColor()
descLabel.font = UIFont(name: "HiraKakuProN-W3", size: 15)
descLabel.textAlignment = NSTextAlignment.Center
descLabel.setTranslatesAutoresizingMaskIntoConstraints(false)
viewB.addSubview(descLabel)

タイトルと説明文のラベル(文字を使うためのもの)を作成します。

「サンプルクイズ」の文字はタイトルなので太字にしてサイズも大きくし、説明文はタイトルよりは細くてサイズも小さく設定しました。

ここでも後からAutoLayoutで制約をつけるために「.setTranslatesAutoresizingMaskIntoConstraints(false)」を指定しているところがポイントです。

AutoLayoutの制約を各要素に設定

//①制約をつける要素名の辞書を作成
let vd = ["titleLabel":titleLabel, "descLabel":descLabel, "viewB":viewB,"sv":super.view]

//②タイトルのラベルに制約を設定
let titleLabelCn1:Array = NSLayoutConstraint.constraintsWithVisualFormat("|-0-[titleLabel]-0-|", options: NSLayoutFormatOptions(0), metrics: nil, views: vd)
let titleLabelCn2:Array = NSLayoutConstraint.constraintsWithVisualFormat("V:|-10-[titleLabel]", options: NSLayoutFormatOptions(0), metrics: nil, views: vd)
viewB.addConstraints(titleLabelCn1)
viewB.addConstraints(titleLabelCn2)

//③説明文のラベルに制約を設定
let descLabelCn1:Array = NSLayoutConstraint.constraintsWithVisualFormat("|-0-[descLabel]-0-|", options: NSLayoutFormatOptions(0), metrics: nil, views: vd)
let descLabelCn2:Array = NSLayoutConstraint.constraintsWithVisualFormat("V:[descLabel]-10-|", options: NSLayoutFormatOptions(0), metrics: nil, views: vd)
viewB.addConstraints(descLabelCn1)
viewB.addConstraints(descLabelCn2)

//④ビューBに制約を設定
let viewBCn1:Array = NSLayoutConstraint.constraintsWithVisualFormat("H:[sv]-(<=1)-[viewB(300)]",options: NSLayoutFormatOptions.AlignAllCenterY,metrics: nil,views: vd)
let viewBCn2:Array = NSLayoutConstraint.constraintsWithVisualFormat("V:[sv]-(<=1)-[viewB(80)]",options: NSLayoutFormatOptions.AlignAllCenterX,metrics: nil,views: vd)
super.view.addConstraints(viewBCn1)
super.view.addConstraints(viewBCn2)

各要素に制約を適用して画面をレイアウトします。

①は作成した要素にVFL(以下で説明)で使う名前を指定するための辞書を作成しています。
辞書の最後の要素の「”sv”:super.view」のように元の名前が長くなりがちな場合などはVFL内だけの名前をつけたほうがスッキリしますが、特に理由がなければ作成した要素名と同名をつけておくほうが後から改編する必要に迫られた時の後顧の憂いがなくて良いと思います。

②、③でタイトルと説明文のラベルに制約を設定しています。
各1行目は横幅の設定で、各二行目は親要素からの間隔を設定しています。

間隔は本来は不要なのですが今回作業をわかりやすく後で色分けしたいのであえて10ポイントほど間隔を空けておくことにしました。

④はビューBを画面中心に配置するための制約です。
カッコ内の数値はサイズを表しています。ここも横幅指定は後で色づけしてわかりやすくするために設定しておきます。
ここではあえて上のラベルには使わなかった横の指定を表す「H」を一行目に使っています。

VFLではHは「Horizontal」で横を表し、Vは「Vertical」で縦を表しています。何も指定しなければHが自動で選択されるようになっています。

ここがWeb出身者にとっては厄介なところで、Web界隈では「Wが横(幅)、Hは縦(高さ)」でまかり通っているのでHが真逆なことを意味してしまいどうにも慣れません(苦笑)。
実際、縦の指定には無意識的にHと書いてしまうので、できるだけ指定しなければ横と自動判別されることを活かしてHは書かない方針でいきたいと思います。

私の採用したこの指定方法は「VFL(Visual Format Language)」という一種の言語で例えば「V:|-10-[titleLabel]」という部分は「親要素から縦に10ポイント間隔を開けて配置する」という意味になります。

便利なのですが覚えるまでが少し大変らしく初心者にはVFLを使わずに制約を設定する方法もあるのでそちらがオススメとされていましたが、個人的にはWebなどでCSSの経験があったりするのであればVFLに慣れたほうが手っ取り早いかな、という気がします。

私はこの方法を採用すると他で数行書かなければ実現できないこともスタイリッシュに一行で済んだりするのが良かったのでこの方法を選ぶことにしました。

なんせiOS開発はhtml・css・Javascript・PHP・SQLの分量を同じところに一気に書くようなもので、コード量が膨大になりがちなのでこういった書き方ひとつで減らせるのであれば減らしたいな、と。
そう考えるとWebは上手い具合にコードを役割ベースで分離できるのでわかりやすかったんですね。

ただし、VFLはコンパイラでチェックすることができないそうので書き方が間違っていると実行時エラーでしか知ることができない点に注意が必要です。

以上をシミュレーターで実行すると以下のような結果になります。

iOS Simulator Screen Shot 2015.04.03 0.58.06

わかりやすいようにビューBに色をつけておきましたが、概ね私が最初に想定した方法で画面レイアウトを実現することができました。

あえてビューBに横幅を設定したり、各ラベルも親要素(ビューB)に対して間隔を設定しておいたのでわかりやすくなりましたね。

ビューBの着色を外せば、

iOS Simulator Screen Shot 2015.04.03 0.58.49

というわけで起動画面のレイアウトはこれにて完成です。

一定時間経過後に別画面へ遷移する

ただ、今回は実際の起動画面のように一定時間経過後に別画面へ自動的に遷移するようにしてみます(本来の起動画面はアプリの準備ができるまで表示されます)。

予め遷移先の画面として「genreViewController.swift」をプロジェクトに新規追加しておいてください。

var timer = NSTimer.scheduledTimerWithTimeInterval(3.0, target: self, selector: Selector("jump"), userInfo: nil, repeats: false)

これはNSTimerというJavascriptでいうところのsetTimeoutのような機能を使って「3秒後にjump関数を実行する」という意味になります。

func jump() {
    //遷移先のビューを指定
    let GenreViewController: UIViewController = GenreViewController()
    //アニメーションを設定
    GenreViewController.modalTransitionStyle = UIModalTransitionStyle.CrossDissolve
    //指定したビューへ遷移
    self.presentViewController(GenreViewController, animated: true, completion: nil)    
}

以上を実行すると3秒後に指定したGenreViewControllerへと画面遷移するのが確認できると思います。

ステータスバーを非表示に

ここまで作成してシミュレーターで見ていたらステータスバーが邪魔なことに気がついたので非表示にすることにします。

override func prefersStatusBarHidden() -> Bool {
    return true
}

コレをViewController内に追記すればステータスバーを非表示にすることができます。


念のために最後に今一度書いておきますが、今回は「起動画面をコードで作れない(涙)」という事態をうけて緊急措置的に対応しただけですので、この方法で実際のアプリを作ってしまうと起動前に黒い画面(起動準備中)がしばらく表示されてしまうことになるので要注意です。

あくまで学習目的のためにこのような方法をあえて採用しただけですのでその点に関しては誤解のないようにしてください。

今回はその点が想定外だったことを除けば比較的想定したままで実現できた感じでした。考え方としては間違っていないのかな、と。

今後、レイアウトの記述にVFLを採用したことが吉と出るか凶と出るか・・・

あと、AutoLayoutで各要素の位置やサイズを設定することを表現する言葉が定まっていない感もあっていまいち日本語で検索しづらいですね。
「制約」や「拘束」といった言葉がポピュラーなようなので当連載ではとりあえず「制約」でいきたいと思いますが。

AutoLayout周りを検索する時は英語で「constraint」を絡めて検索したほうが遥かに望む結果が得られやすいと思います。

こういったiOS開発の記事だと、予め用意されているクラス名やメソッド名が長すぎてブログの横幅にコードが収まりきれないことが多くて見難いですね。
これも今後の課題として考えていきたいと思います。

次回はクイズの「ジャンル選択画面」を作成していきます!