Swiftで遊ぼう! on Hatena

あしたさぬきblogでやってた初心者オヤジのiOSプログラミング奮闘記がHatenaに来ました

Swiftで遊ぼう! - 365 - Developing iOS Apps (Swift) Build a Basic UI

2016年12月24日:Swift 3に対応*1

チュートリアル索引に戻る→

iOS Developer LibraryにStart Developing iOS Apps (Swift)という素晴らしいチュートリアルを見つけました。

developer.apple.com

色々なチュートリアルに今までに挑戦してきました。iTunes Uのポール先生の講義Beginning iPhone Development with Swift: Exploring the iOS SDKを試してみましたが、私のような超初心者には、それぞれに不満があり中断しています(^^;)

iTunes Uのポール先生の講義は、対象がコンピューターサイエンス専攻の学生さんなんで、基本的な事項(私にはちんぷんかんぷん)をサラリと流してしまい戸惑うことが多いんです。

Beginning iPhone Development with Swift: Exploring the iOS SDKは、素晴らしい本だと思います。しかし、この本の初版は、iOS6向けに書かれていて、iOSアップデイトに合わせて改訂していますが、内容はObjective-Cスタイルなコーディングが目立ち、Swiftスタイルの私には理解しがたい部分が気になります。

色々な角度からiOSフレームワークの理解は重要と考え、本家Appleが用意したSwiftのチュートリアルにも取り組みます。

Build a Basic UI

Xcodeという開発環境には豊富なテンプレート(ゲーム、テーブル・ビューアプリケーション、タブ・ビューアブレーションなど)が含まれていて、この事が初心者プログラマーに混乱を招いていると私は思っています。というのもテンプレートを使うと、必要な機能がビルトインされてしまって、理解していないと使いこなせないからです。必要な機能を理解しているベテランプログラマーにとってテンプレートを使うことで開発時間を短縮させることができますが、何も知らない初心者は、テンプレートで何がどこまでビルトインされているか分からない事が多いので、最初っから自分の手でコーディングしていくと勉強になるでしょう。このアップルのチュートリアルは食事を記録するシンプルなテーブルビュースタイルのアプリケーションとしてフードトラッカー「FoodTracker」を作ります。

f:id:yataiblue:20151231144735j:plain 新規プロジェクト作成手順

Xcode 8で、Xcodeプロジェクトを選び、「Single View Application」を選択します。

f:id:yataiblue:20161224163839j:plain

「Next」ボタンを選択すると次のダイアログが出てきます。「Product Name:」に「FoodTracker」と入力して、Language:は「Swift」にして、Devices:を「Universal」にします。

f:id:yataiblue:20161224163913j:plain

「Use Core Data」と「Include UI Test」のマークは無し、「Include Unit Test」のマークは入れます。

「Next」ボタンを選択して保存先を選びます。「Create Git repository on」の選択肢は外します*2

f:id:yataiblue:20161224163939j:plain

「Create」ボタンを作成するとSingleViewApplicationのテンプレートができあがります。

f:id:yataiblue:20161224164100j:plain

// BASIC 新規プロジェクトの作成手順はここまで

これでアプリの骨格ができました。まずXcode 8の概要理解のために、ショートカットページで言葉に慣れましょう。プロジェクト・ナビゲータ*3を選択します。

プロジェクト・ナビゲータの中でまず最初に言及しているのが、AppDelegate.swiftファイルです。今までのチュートリアルで説明の無かった部分です。

f:id:yataiblue:20161224165355j:plain

AppDelegateには、2つの主要な機能が定義されています。

  1. ユーザーのAppDelegateクラスを定義していて、app delegateは、appの内容を表示するためのwindowを作り出し、appの状態変化に反応する環境を提供します。
  2. アプリにentry pointやユーザーの入力イベントを提供するrun loopも作ります。この機能は、UIApplicationMainの属性(@UIApplicationMain)によって実行されルのですが、ファイルの上部にあります。

UIApplicationMani属性を使うことは、UIApplicationMainのメソッドを呼ぶことと、delegateクラスの名前としてAppDelegateクラスの名前をわたす事と同等です。これに応じて、システムは、applicationオブジェクトを作ります。アプリケーションオブジェクトは、システムにおけるappのライフサイクルを維持するために重要な役割を果たします。システムもAppDelegateクラスインスタンスを生成して、アプリケーションオブジェクトに登録する事で、appが起動するんです。

AppDelegateクラスは、たった1つのプロパティを持っています。

var window: UIWindow?

このプロパティは、appのwindowのリファレンスを保持しています。widowはapp描画のルートに位置していて、コンテンツすべてを描画する場所になります。widowプロパティはオプショナルなんで、あるポイントでnilになりえるってことです。

AppDelegateクラスは、UIAppleicationDelegateプロトコールを持つため、準拠するメソッドがいくつかあります。

func application(_ application: UIApplication, 
            didFinishLaunchingWithOptions launchOptions: 
            [UIApplicationLaunchOptionsKey: Any]?) -> Bool
func applicationWillResignActive(_ application: UIApplication)
func applicationDidEnterBackground(_ application: UIApplication)
func applicationWillEnterForeground(_ application: UIApplication)
func applicationDidBecomeActive(_ application: UIApplication)
func applicationWillTerminate(_ application: UIApplication)

これらのメソッドは、app delegateを使って、アプリケーションのオブジェクト間でコミュニケーションを可能にさせます。appの状態が変化している間、例えば、appの起動時、バックグラウンドに移るとき、appが終了する時など、アプリケーションオブジェクトは、それに応じたデリゲート・メソッドを呼んで、appに反応する機会を提供しています。そのため、正しいタイミングでメソッドを呼ぶことに注意をする必要はありません。アプリケーションがすべて代わりにしてくれるからです <- 非常に便利です。

このチュートリアルでは、app delegateコードに変更は加えません。AppDelegte.swiftファイルに何も変更を加える必要は無いです。

ビュー・コントローラ

プロジェクト・ナビゲータで「ViewController.swift」ファイルを選択してコードをみます。

f:id:yataiblue:20161224200116j:plain

もう何度も繰り返し扱っているので、詳しく説明はしませんが、ここで定義しているViewControllerクラスは、UIViewControllerクラスを継承しています。ということはUIViewControllerクラスの持っているメソッドを書き換えることができます。「override」のキーワードをつけて既存のメソッドを書き換えることができます。

用意されているメソッドは「viewDidLoad()」と「didReceiveMemoryWarning() 」ですが、このチュートリアルで「didReceiveMemoryWarning()」は消去します。

import UIKit

class ViewController: UIViewController {

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

}

ストーリーボード

storyboardの説明に入ります。ユーザーインターフェイスをビジュアルに組み立てて、コードと連携をすることができます。プロジェクト・ナビゲータから「Main.storyboad」を選択します。

f:id:yataiblue:20161224202212j:plain

インターフェース・ビルダーにstoryboardが開き、appのコンテンツを表示する1つのsceneが見え、この背景はcanvasと呼ばれ、ユーザーインターフェイスの要素を並べることができます。canvasにあるsceneの左側に矢印が見えるのがentry pointです。アプリが立ち上がった時に見えるsceneです。

次にシュミレータの画面とcanvasのレイアウトの相違点が説明され、adaptive interfaceの話に入って、オートレイアウトの説明に入ります。

全て画面に見える物は、UIViewのオブジェクトやサブクラスのオブジェクトという説明もあります。

テキスト・フィールドをsceneに加える

メニューの「Editor -> Canvas -> Show Bounds Rectangles」が選択されていることを確認します。この設定はインターフェイス・ビルダに設置する背景が透けているオブジェクトに青い境界線を描画させレイアウトの確認をしやすくします。

f:id:yataiblue:20161225152226j:plain

ユーティリティ・エリアにあるオブジェクト・ライブラリのフィルタ・フィールドにtext fieldとタイプすると「Text Field(UITextField)」が選ばれるため、storyboardのビュー(スーパー・ビュー)の上にドラッグ&ドロップします。

f:id:yataiblue:20161225152504j:plain
オブジェクトの周囲にあるリサイズ・ハンドル(resize handles)を使ってリサイズしてHIG(Human Interface Guidline)に準拠した位置に設置します。青いドットラインが出現するまで右の辺を広げます。

このままでは空欄のテキストフィールドなので、ユーザーに意図を明確にするために説明文を加えます。

テキストフィールドのアトリビュート・インスペクタを開きます。

f:id:yataiblue:20161225153503j:plain

  1. Placeholderに「Enter meal name」と入力します。
  2. このままアトリビュート・インスペクタの中で、テキストフィールドのキーボードの扱いを設定していきます。ラベル「Return Key」のメニューから「Done」を選びます。こうすることで、キーボードのリターンキーが「Done」キーに変化します。
  3. さらに下にある「Auto-enable Return Keyチェックボックスを選択します。これでテキストフィールドを空欄で放置することができなくなります。

ここまでのテキスト・フィールドのアトリビュート(属性)は次の様になります。

f:id:yataiblue:20161225154526j:plain

ラベルをSceneに加える

同様にオブジェクト・ライブラリのフィルタ・フィールドにlabelとタイプしてLabel(UILabel)オブジェクトをテキスト・フィールドの上にドラッグ&ドロップします。HIG(Human Interface Guidline)に準拠して設置するのも同様です。

ラベルをダブルクリックして「Meal Name」に名前を変更します。

ボタンをSceneに加える

オブジェクト・ライブラかのフィルタ・フィールドに今度は、buttonとタイプしてButton(UIButton)オブジェクトをテキストフィールドの下に設置します。

ボタンをダブルクリックして、名前を「Set Default Label Text」に変更します。

ここまででsceneは次のようになっています。

f:id:yataiblue:20161225155944j:plain

sceneに加えたオブジェクトが実際どのように配置されているか確認できるのがアウトライン・ビューです。

アウトライン・ビュー

アウトライン・ビュー・トグルを使ってアウトライン・ビューを出したり消したりできます。

f:id:yataiblue:20161225161214j:plain

アウトライン・ビューcanvasに設置されているUIエレメントの階層性を確認していきます。テキスト・フィールド、ラベル、ボタンはすべてUIViewの派生オブジェクトで、これが1つの「View」の下に並んでいるのが分かります。

f:id:yataiblue:20161225163509j:plain

この最上位のViewがcontent viewとなる「スーパービュー(superview)」で、そのサブビューとしてのラベルやテキストフィールドが加えられています。

プレビュー

実際に並べたUIエレメントが実機でどのように見えるか確認する機能があります。それがプレビューです。まずアシスタント・エディタを開く必要があるのでツールバーの右に位置するアシスタント・エディタ・ボタンを選択します。

f:id:yataiblue:20161225165430j:plain

アシスタント・エディタの上部にエディタ・セレクタ・バーがあります。

f:id:yataiblue:20161225165924j:plain

ここの「Automatic」を選択して、「preview」を選びます。

f:id:yataiblue:20161225170043j:plain

ここでiPhone 7のポートレイトが表示されます。これはcanvasのsceneの表示と同じなので特に問題はないように見えます。

f:id:yataiblue:20161225171107j:plain

プレビューにカーソルを持っていくと株にローテート・ボタンが出てきます。

f:id:yataiblue:20161225171233j:plain

このボタンを使ってランドスケープのプレビューに切り替えます。

f:id:yataiblue:20161225171609j:plain

どうでしょう? ランドスケープで見ると、テキスト・フィールドの右辺が右橋のHIGのラインまで伸びていません。少しアンバランスになっています。実はオリジナルのチュートリアルでは、ここでiPhone SEのプレビューを加えてポートレイトに戻して、テキスト・フィールドが崩れてしまっているところを見せています。

これを解決するのがオートレイアウトです。

オートレイアウト

私がiOS開発を勉強し始めたのがiOS8からです。実はiOS8からかなりオートレイアウト機能は強化され、その基本は「Swiftで遊ぼう! - 204 - フィリングを使ってレイアウト調整(Auto Layoutのまとめ) - Swiftで遊ぼう! on Hatena」で説明しています。iOS9から更に「Stack View」が加わり機能強化されました。このチュートリアルでは、Stackの説明をしていますが。少なからずiOS10でもアートレイアウトに新しい機能が追加されました。「Incrementally Adopting Auto Layout」という便利な機能です。ストリーボードに設置するUIエレメントにいちいちコンストレイントをを加えなくても、Xcodeのサイズ・インスペクタで設定できるようになりました。

例えば、ここで設置したテキスト・フィールドですが、ランドスケープで右辺が右端まで広がりません。実は、コンストレイントを設置していない状態では、トップ、レフト、幅、高さに「Pin」された状態がデフォルトだからです。

f:id:yataiblue:20161225182916j:plain

個別にコンストレイントを設定する手間を省くことができます。テキスト・フィールドが選択された状態でサイズ・インスペクタを選択するとオートリサイジングの設定ができるんです。右コンストレイントを設定してHorizontalに幅を設定することもできます。

f:id:yataiblue:20161225193400j:plain

しかし、この機能は簡易的なレイアウト調整機能です。複雑なレイアウト調整のためにStack Viewが便利でしょう。「Label」「Text Field」「Button」を「Shift + クリック」で選択してからエディタ・エリアの右下端にある「スタック(Stack)」ボタンを押します。

f:id:yataiblue:20161225193754j:plain

3つのオブジェクトがまとまり、アウトライン・ビューのView階層下に「Stack View」が作られます。

f:id:yataiblue:20151017155148j:plain

アウトライン・ビュー上で「Stack View」が選択されている状態で、アトリビュート・インスペクタを開いて、Spacing」を「8」に変更して感覚を広げるとcanvasが次のようになります。

f:id:yataiblue:20161225200218j:plain

次に「Stack View」が選ばれた状態で、右下端の「コンストレイント」ボタンを押して、上部(20)、左(0)右(0)のマージンのルール(コンストレイント)を決定します。

f:id:yataiblue:20161225203158j:plain

しかし、この状態では、「Stack View」の中の「Text Field」は画面一杯に広がっていません。

f:id:yataiblue:20161225205154j:plain

スタック・ビュー内でコンストレイントを設定をする必要があるので、「Text Field」を選んで、再び「ピン(Pin)」ボタンを選びます。左右の赤い点線をクリックして実線に変えてから右のマージンを「0」に変更して、下部の「Add 1 Constraint」ボタンをクリックします。オブジェクトがズレているとオレンジラインが現れますが、そういう場合は、右下端にある「Resolve Auto Layout Issues」ボタンから「Update Frames」を選ぶとオレンジラインは消えます。

最後に「Text Fiel」を少し操作するので選択された状態で、サイズ・インスペクタを選びます。色々ある項目の中で「Intrinsic Size」の値を「Placefolder」に変更します。この本質的なサイズというのは、Placefolderに設定したStringサイズを最小単位とするということです。実際のサイズはこれより大きくなるからPlacefolderを最小単位に設定しました。

f:id:yataiblue:20151017195029j:plain

これが最初のセクションが終了です。

次に進む→Connect the UI to Code / ユーザー・インターフェイスとコードを繋げる

*1:2015年10月15日改訂:環境環境:Xcode7.1Swift2.1

*2:まだ私も理解できていないので言及できませんが、理解した暁にリンクボタンが作成されます(^^;

*3:Xcodeの使用法に慣れる必要があります。何度も言いますが -> Xcodeのショートカットページ