はじめに
第1章 サイドナビゲーション型のUI
第2章 写真を拡大する画面遷移UI
第3章 Tinder風のUI
第4章 入力フォームの実装例
あとがき
本書を手に取って頂きましてありがとうございます。筆者は現在、主にiOSアプリ開発(使用言語はSwift/Objective-C)を行っています。特にその中でも、アプリのUI実装に関する部分に携わる多くの機会がありました。
また業務以外の場所でも、登壇などでUI実装に関することをお話したり、TIPSを投稿する等の活動をこれまでも行ってきました。その他勉強会など同業のエンジニアの方々との交流の中で、「Web制作ではUI実装に関する紹介書籍があるのなら、iOSアプリ開発においてもUI実装に関する書籍の需要はあるのでは?」と感じることがありました。そもそも筆者は、キャリアのはじめがデザイナーから始まり、そこからWebエンジニアを経てモバイルアプリエンジニアになった経緯もあったので、今でもUI実装に関する部分に一番関心をもっています。
iOSアプリのUIに関しては、「Human Interface Guidelines」1というiOSアプリ開発において極めて大切なドキュメントがあります。しかし、アプリ個々のUIを見ると、OSのバージョンアップやトレンドの変化はもちろん、プロダクトのデザインによってそれぞれの構成や実装方法、選択しているアーキテクチャーによっても変わります。
この点を踏まえ、本書ではいくつかのまとまったサンプル実装を例に、UI構築をする上で重要な実装ポイントやアイデアを紹介します。一手間を加えた実装を加えたり、サードパーティーのライブラリーを上手に活用することで、既存のUIに素敵な彩りを添えることが実現可能なものがあります。本書で紹介している例は、筆者が業務でのアプリ開発で利用したTIPSや勉強会での登壇の際に作成したサンプルで利用したものを掲載しています。
基本的な理解ができるようになり、これからiOSアプリを本格的に開発していこうと考えている方や、UI実装や表現に関する部分にさらなる磨きをかけていきたい方にとって、本書が少しでも役立つことができれば幸いです。筆者自身も現状に甘んじることなく、自分の開発技術や表現の幅を広げる努力を惜しまずに歩み続けていこうと思います。
本書の内容及び紹介しているサンプルのコードに使用したバージョンにつきましては、次の通りになります。またXcodeやSwiftのバージョンが上がった際には、Githubリポジトリー等でお伝えしていく予定です。
掲載しているサンプルに関しては、2018.11.25(初版改訂時)のものになります。以前の2018.08.04(初版執筆時)のコードについてもリポジトリー内に残しております。
・macOS Mojave 10.14
・Xcode 10.1
・Swift 4.2
・CocoaPods 1.5.3
・XcodeやSwiftに関する基本的な知識
・AutoLayoutやStoryboard・Xibに関する基本的な知識
書籍で紹介しているサンプルコードは次のリポジトリーで公開しています。
・Github: https://github.com/fumiyasac/ios_ui_recipe_showcase/tree/convert_xcode10
また、使用した写真素材は「写真AC(https://www.photo-ac.com/)」によるものです。
※ masterブランチに格納しているものは、Xcode9.4.1 & Swift4.1で実装したものになります。
本書で掲載している内容につきましては、誤りがないように細心の注意を払っておりますが、もし訂正等がございましたら次のメールアドレスやGithubのissue、Twitter等を通してご一報頂けますと幸いです。
・Mail: just1factory@gmail.com
・Twitter: https://twitter.com/YuxBeta
・サンプル実装の難易度: ★★★★☆☆☆☆☆☆
─サンプルプロジェクト名: MenuContentsExample
・サンプル実装の難易度: ★★★★★★★★★☆
─サンプルプロジェクト名: InteractiveUIExample
・サンプル実装の難易度: ★★★★★★★☆☆☆
─サンプルプロジェクト名: LikeTinderExample
・サンプル実装の難易度: ★★★★★★☆☆☆☆
─サンプルプロジェクト名: ReservationFormExample
本書に記載された内容は、情報の提供のみを目的としています。したがって、本書を用いた開発、製作、運用は、必ずご自身の責任と判断によって行ってください。これらの情報による開発、製作、運用の結果について、著者はいかなる責任も負いません。
本書に記載されている会社名、製品名などは、一般に各社の登録商標または商標、商品名です。会社名、製品名については、本文中では©、®、™マークなどは表示していません。
本書籍は、技術系同人誌即売会「技術書典5」で頒布されたものを底本としています。
この章では、複数のContainerViewを組み合わせて、スライドメニューのようなViewとコンテンツの切り替えを行う部分に関して解説します。メニューの開閉や現在表示している画面からの切り替えに関する部分は、コンテンツの量が多いアプリでよく目にする部分ですが、いざ自前で実装する時には大変な部分のひとつです。
ContainerViewは、その名の通り「ViewControllerを表示するための入れ物となるView」です。
InterfaceBuilderからContainerと書いてあるView要素を引っ張って来たときに、UIViewControllerが「Embed Segue」でつながれたViewが表示されます。ここで大事のは、Embed Segueで繋がっているViewControllerと、ContainerViewを配置したViewControllerは親子関係を持つことです。
この親子関係をうまく活用することで、任意のViewController内で親子関係を持つ別のViewControllerを表示したり、親子関係を利用した処理を組み立てることができます(※Notificationを活用するという方針も考えられますが、画面遷移やアプリの動き方が複雑な場合、実装時の考慮漏れが起きやすくなります)。
もちろん、ContainerViewだけをあらかじめ配置し、その中に表示したいViewControllerをコードやInterfaceBuilderから接続したり、Segueの名前からContainerViewに繋がるViewControllerのインスタンスを取得することもできます。そのためStoryboardと相性がよく、画面遷移を直感的に作成できたり、それぞれの画面ごとにViewControllerを分割・整理できる点は大きなメリットです。
ContainerViewの特性を活用したUIを考えていく上のポイントは次の3点です。
1.全体的な画面遷移やContaierViewで、Embed Segueで紐付けされているViewControllerの関係
2.異なるController間でプロトコルを用いて処理や値を橋渡しをする部分
3.親ないしは子のViewControllerのインスタンスを活用する部分
これらに留意した上で実装を行えば、良い実装になるでしょう。
前述したとおり、ContainerViewをInterfaceBuilderで表示した際には、すでにViewControllerがつながった状態で表示されます。ここからは、ContainerViewの性質に関するポイントをいくつか紹介します。
図1.1は、InterfaceBuilderでの見え方です。今回紹介するサンプルでは、LayoutをStoryboardないしはXibを活用しているためにGUIとの連携している部分が多くなっています。サンプルを見て、設定している場所を確認しながら進めばより理解が深まるでしょう。
またEmbed Segueについては普通のViewControllerだけでなく、NavigationControllerやStoryboard Referenceとつなぐことも可能です。作成したいiOSアプリの構造やUIに合わせて活用してみてください。
次にContainerViewを取り扱う上でポイントとなる、配置されているViewControllerとの親子関係について解説します。親子間での処理の受け渡しについては、次のふたつの方針が考えられます。
1.親ないしは子のViewControllerのインスタンスを作成して、任意の処理を実行する
2.親ないしは子のViewControllerにプロトコルを定義して、任意の処理を実行する
これらを実行したい処理に応じて使い分けますが、ここでは1.の手法を紹介します。
リスト1.1は、ContainerViewを配置したViewControllerで、親子関係を利用して子のViewControllerで親のクラスのインスタンスを取得するコードの例です。
1: // ※ 任意の親のさらに親をたどる場合は self.parent?.parent ... となる
2: if let vc = self.parent {
3: let parentViewController = vc as! ParentViewController
4: parentViewController.doSomething()
5: }
逆にリスト1.2は、親のViewControllerから子のViewControllerで親のクラスのインスタンスを取得するコードの例です。
1: // ※ 子のViewController達は[ViewController]型で格納されているので、indexはその順番の値
2: let childViewController = self.childViewControllers[index] as! ChildViewController
3: childViewController.doSomething()
4:
5: // ※ 次のようなログを出力して該当のインデックス値を調べるのも方法の一つ
6: self.childViewControllers.forEach { vc in
7: print(vc)
8: }
1: <プロジェクト名.indexが0のコントローラー名: xxxx…>
2: <プロジェクト名.indexが1のコントローラー名: xxxx…>
3: <プロジェクト名.indexが2のコントローラー名: xxxx…>
4: ...
配置した任意のContainerViewに、Enbed Segueで接続されているViewControllerのインスタンスをSegue Identifierから取得する方法も紹介します。ここでは、従来のSegueでの画面遷移に置いて遷移先に何かしら値を引き渡す際と同様に、リスト1.4」の様にprepareメソッドを利用します。
1: // 接続されている「Segue Identifier」から該当のViewControllerを取得します
2: // MEMO: SideNavigationViewControllerの名前をこのようにする
3: // →「Embed Segue: connectSideNavigationContainer」
4: override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
5: if segue.identifier == "connectSideNavigationContainer" {
6: let sideNavigationViewController
7: = segue.destination as! SideNavigationViewController
8: sideNavigationViewController.delegate = self
9: }
10: }
InterfaceBuilderとコードの合わせ技でのContainerViewを活用したUI実装は、アプリレイアウトの基盤部分に関わるものになる事もあります。実装をマスターしておくと助かる事が多いのではないでしょうか。
今回のサンプルでは、まずナビゲーションバーの左上部にあるボタンをタップする、ないしは画面の左端からドラッグした際に、コンテンツの下部に隠れているサイドナビゲーションを表示します。同時に、メインのコンテンツ表示部分をタップできなくして、逆向きにドラッグするか移動したコンテンツ表示部分に触れるとサイドナビゲーションを隠します。この一連の動きをContainerViewを用いて実装しました。
様々なアプリでよく見かける、今や定番となっている動きですが、今回はこの動きをライブラリーに任せてしまうのではなく、GestureRecognizerやTouchEventを用いて実現する事例を解説します。
今回の実装にあたって、ContainerViewの特性やプロトコルを活用することで、単純に適切なタイミングでのコンテンツ切り替えを動作させるだけでなく、より一層動きに彩りを添えるためにアニメーションも加えました。
この手のGestureRecognizerやTouchEventを伴い、画面の状態が意図通りのタイミングで処理がなされているかを検証する際は、正常な動作だけではなく、次のような意図しない動作や行動に関する検証も忘れないようにしてください。
・例1. 1本指でドラッグするのが期待される動作だが、2本指での操作でも不具合がないか
・例2. 開く途中の状態で、さらにもう一方の手でドラッグをした際に不具合がないか
実際に触ってのUI検証はなかなか面倒ですが、これを怠ることで意図しない動作をした際にフリーズしたり、最悪の場合クラッシュを引き起こしやすいので注意しましょう。