はじめに
第1章 構造が複雑な写真表示UI
第2章 iOS13からの機能を使ったUI
第3章 Screen With Passcode Lock
第4章 Layout With SwiftUI
あとがき
まずは、本書を手に取っていただきまして、本当にありがとうございます。筆者は現在、主にiOSアプリ開発(使用言語はSwift/Objective-C)を行っており、特にその中でも、アプリのUI実装に関する部分に携わる機会が多くありました。
また、業務以外の場所でも、僭越ながら皆様の前で登壇する機会をいただき、UI実装に関することをお話ししたり、TIPSを投稿する等の活動を行ってきました。その中で、勉強会で同業のエンジニアの方々と交流するうちに、「Web制作ではUI実装に関する紹介書籍があるならば、もしかしたらiOSアプリ開発においても、UI実装に関する書籍の需要はあるのではないか?」と感じるようになりました。さらには、筆者のキャリアがデザイナーから始まり、そこからWebエンジニアを経てモバイルアプリエンジニアになった経緯もあったので、今でもUI実装に関する部分に一番関心が高いのです。
2018年10月8日に開催された技術書典5にて頒布致しました、「iOSアプリ開発 UI実装であると嬉しいレシピブック」では、UI実装においてView構造やアニメーション実装等を上手に活用して、少しの工夫を施すことで実現可能なアイデアや、実装例についてまとめています。また2019年7月27日に開催された技術書同人誌博覧会にて頒布致しました、「iOSアプリ開発 UI実装であると嬉しいレシピブック Vol.2」では、前回の続編として、さらにアプリにおけるUI表現に彩りを添えるためのテクニックとして、Githubで公開されているOSSのUIライブラリを活用するアイデアや実装例についてまとめています。
このように、これまでの書籍ではUI実装のアイデアや具体的な手法についてフォーカスを当てた書籍を2冊執筆してきましたが、どうしても誌面の分量の関係等もあって、見送ってしまったものもありました。本書では、Vol.1及びVol.2では惜しくも紹介できなかったUI実装に関する実装解説や、少しトリッキーな実装の形になってしまうけれどもUI実装の観点からみると面白い振る舞いをするサンプルを、簡単でありますが、まとめたものになります。また、iOS13以降で新しく登場した新機能を利用して実装したサンプルについて、少しだけ触れているものもあります。
iOS13からは、SwiftUI・Combine FrameWork・UICollectionViewCompositionalLayout等をはじめ、これからのUI実装を考えていく際に、今から押さえておきたいトピックスもたくさんあります。ますます楽しみである反面、これまでのUIKitとの共存をはじめとした、様々な課題も生まれつつあるのではないかと思います。そのような中でも、UIKitを利用したUI実装に関する表現方法を学ぶことは、まだまだ意義のあることだと考えています。ですので、このタイミングで再度、UIKitでの表現を活用した、UI実装のサンプル解説に関する書籍を執筆することにしました。
本書では、これまでの実務の中で培ってきた知識や知見に加えて、UI実装に関するサンプルを4点収録しております。一見するととても複雑に見えそうではあれども、実装時の少しの工夫やライブラリの有効活用をすることで実現することができる、UI実装に関するサンプルもあります。また、一般的なiOSアプリに対して利用可能なケースは限られてしまうかもしれませんが、アニメーションやインタラクションにひと工夫を加えることによって、見た目にも美しく、触っていて思わず楽しくなりそうな感じのUI実装に関するサンプルもあります。
ユーザーとの直接的な接点ともなりえるUI実装の中で、アニメーション・トランジションを盛り込んだ表現や、指の動きやスクロール伴って変化する美しい表現、そして機能と調和したいいアクセントとなるUI表現とその実装手段を考えていく際に、本書が少しでもお役に立つことができれば幸いです。
筆者自身も、モバイルアプリのUI/UX関連の最新技術やトレンド等へのキャッチアップを怠ることなく、目に見えない細かな配慮の部分にも気を配りながら、さらに開発技術や表現の幅を広げていきます。それとともに、新たな技術や知識、そして表現豊かなUI実装を分解して、紐解いていく過程の中で得られる体験や知見を吸収していき、より皆様が楽しむことができるような形で展開していけるよう、精進していく所存です。
本書の内容及び、紹介しているサンプルのコードに使用したバージョンにつきましては、下記の通りになります。また、XcodeやSwiftのバージョンが上がった際にはGithubリポジトリ等、何かしらの形でお伝えしていく予定です。
掲載しているサンプルに関しては、2020.05.05時点のものになります。
・macOS Big Sur 11.3
・Xcode 12.5
・Swift 5.4
・CocoaPods 1.10.1
【必要な前提知識】
・XcodeやSwiftに関する基本的な知識
・AutoLayoutやStoryboard・Xibに関する基本的な知識
・SwiftUIに関する基本的な知識
・CocoaPodsの導入や活用に関する基本的な知識
・iOSアプリ開発において、サードパーティー製ライブラリの導入や活用に関する基本的な知識
※ このサンプルの第2章では、node.jsを利用した仮のAPIサーバーを準備していますが、あくまでツールとしての利用なので、前述の前提知識としては特になくても大丈夫かと思います(実際に手元でサンプルを実行する場合には、node.jsのインストールが必要になるので、その点はご注意ください)。
本書で掲載している内容につきましては、誤りがないように細心の注意を払っておりますが、もし訂正等がございましたら、下記にありますメールアドレスや、GithubのissueやTwitter等を通してでも構いませんので、ご一報いただけますと幸いです。
・Mail: just1factory@gmail.com
書籍で紹介しているサンプルのリポジトリは、下記になります。
・写真素材: 写真AC様・Pixta様
【第1章: 実装サンプル】
・https://github.com/fumiyasac/meals_ios_ui_recipe_showcase/tree/master/02_MosaicPhotoGalleryLayouts/MosaicPhotoGalleryLayouts
【第2章: 実装サンプル】
・https://github.com/fumiyasac/meals_ios_ui_recipe_showcase/tree/master/03_UICollectionViewCompositionalLayoutExample
【第3章: 実装サンプル】
・https://github.com/fumiyasac/meals_2nd_ios_ui_recipe_showcase/tree/master/02_PasscodeLockLayout
【第4章: 実装サンプル】
・https://github.com/fumiyasac/meals_2nd_ios_ui_recipe_showcase/tree/master/03_SmallSwiftUILayout
サンプル実装の難易度: ★★★★★★★★★☆
・サンプルプロジェクト名: MosaicPhotoGalleryLayouts
サンプル実装の難易度: ★★★★★★★☆☆☆
・サンプルプロジェクト名: UICollectionViewCompositionalLayoutExample
サンプル実装の難易度: ★★★★★★★☆☆☆
・サンプルプロジェクト名: PasscodeLockLayout
サンプル実装の難易度: ★★★★★★★☆☆☆
・サンプルプロジェクト名: SmallSwiftUILayout
本書では、iOS14から追加されたUI実装に関する新機能や仕様変更のうち、
1.UINavigationController左上にある戻るボタンをロングタップした際に、一気に最初の画面に戻る機能
2.URLを外部ブラウザで開く処理において、Safari以外をデフォルトブラウザに設定している場合に、canOpenURLがfalseとなる点
の2点に関しては対応しております。
該当箇所に適用するコード例については、「リスト1」のような形となります。
// UI実装であると嬉しいレシピブック掲載サンプル変更点(iOS14対応箇所)
// 1. UINavigationController左上にある戻るボタンをロングタップした際に、一気に最初の画面に戻る機能への対応
// 本書の中では、UINavigationControllerExtension.swiftに該当箇所があります。
// 戻るボタンの「戻る」テキストを削除した状態にするメソッド
func removeBackButtonText() {
self.navigationController!.navigationBar.tintColor = UIColor.white
if #available(iOS 14.0, *) {
self.navigationItem.backButtonDisplayMode = .minimal
self.navigationItem.backButtonTitle = self.navigationItem.title
} else {
let backButtonItem = UIBarButtonItem(
title: "", style: .plain, target: nil, action: nil
)
self.navigationItem.backBarButtonItem = backButtonItem
}
}
// 2. URLを外部ブラウザで開く処理において、Safari以外をデフォルトブラウザに設定している場合にcanOpenURLがfalseとなる点への対応
// 本書の中では、当該処理に関する部分は下記のような形で実装しています。
private func showWebPage() {
if let url = URL(string: "http://www.example.com/") {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:])
} else {
showAlertWith(completionHandler: nil)
}
}
}
// MEMO: iOS14からSafari以外のブラウザをデフォルトに変更することが可能です。
// その場合には「LSApplicationQueriesSchemes」の設定をしないと、canOpenURLでfalseになってしまいます。
// ※ 詳細はInfo.plistを参照
// 確認したSafari以外のブラウザは下記の通りになります。
// 検証ブラウザ: Google Chrome / Opera / Microsoft Edge / Firefox
private func showAlertWith(completionHandler: (() -> ())? = nil) {
let alert = UIAlertController(
title: "リンクを開くことができませんでした。",
message: "アプリ内部の設定が誤っている可能性があります。",
preferredStyle: .alert
)
let okAction = UIAlertAction(title: "OK", style: .default, handler: { _ in
completionHandler?()
})
alert.addAction(okAction)
self.present(alert, animated: true, completion: nil)
}
iOS14以降で利用可能なアップデートにつきましては、UICollectionViewに関するレイアウト表現及び、実装に関するアップデートや、UIImagePickerControllerに取って代わるPHPickerViewControllerといった新しいAPIの登場による大きなアップデートだけではなく、前述したコードのように頻出な表現に関する、細かな部分のアップデートもありました。掲載しているサンプルコード内では、UI実装や表現上、最低限の対応としています。さらに細かな1こちらの資料や2こちらの技術ブログ記事等に掲載している内容を参考にしながら、変更点の概要と対処方法を知っておくと、より理解が深まるように思います。
今回例示した資料や技術ブログの他にも、様々なiOS14から利用できる新機能や、バージョンアップ時のノウハウをまとめてくださっているものもたくさんあります。多くの情報に触れながら機能開発を進めていくと、いいのではないかと感じております。
本サンプルでは下記の部分に関しては、今回は対応していませんのでご注意ください。
・DarkModeの無効化(現在は強制的にLightModeにしています)
・SceneDelegateの利用(現状は挙動に問題はありませんが非推奨です)
本章では、Instagram等の写真アプリでもよく見かける、ひとつの画面の中に多くの情報を整理した形の実装について解説をしていきます。たとえば、複雑なタイル上のレイアウトをするフォトギャラリーのようなUI表現や、同じカテゴリーに属する表示内容の一覧をタブ型にまとめてスクロールに伴って切り替えるUI表現を組み合わせた実装のことです。ここで紹介するUIは、ビジネスサイド等からの要望が高くなりやすい例ではあるものの、実装に際して全て自前で準備する際は、大変かつ細部の調整がシビアになりやすいものでもあるかと思います。メインとなるレイアウト構成や、画面構造といった部分に対して、上手にライブラリの活用と取捨選択をすることによって、実装工数の削減やコードの理解がしやすくなる場合もあります。多くのライブラリとその構造や設計に触れていくと、よりUI実装の幅が広がるかと思います。
こちらのサンプルをXcodeでビルドして、Simulatorやご自分のiPhone端末へインストールして挙動をご確認する場合には、収録サンプルのREADMEへ記載している内容を参考にして、準備をしていただければ幸いです。
本章で紹介しているサンプルは、
・タイル状に配置されたサムネイル画像を表示するフォトギャラリーのようなUI表現をする画面
・スクロール操作での切り替え可能なタブ型の記事一覧表示のようなUI表現をする画面
というふたつの全く性格が異なる画面を、カスタマイズされたUITabBar表示と合わせてひとつの画面として表示している一覧画面を起点として、フォトギャラリーの画面からは配置している写真が浮かび上がって詳細画面へ遷移するような形となっています。また、詳細画面についても、元の画面へ戻る際のアニメーションに関する表現をはじめ、長いテキスト表示部分をトグルで切り替えるような表現や、関連するタグ一覧のような表現も盛り込んだ形としています。
画面遷移については、図1.1のようにタイル状に配置されたサムネイル画像を押下すると、サムネイル画像が浮き上がって、詳細画面へ遷移するようなアニメーション処理を、カスタムトランジションを利用して実現する形にしています。詳細顔面から遷移元へ戻る際にも、基本的な構造をUIScrollViewで作成している詳細画面において、表示している状態から画面のY軸方向のオフセット値がマイナスになり、かつある程度のところまで到達した際には、その位置から画面を閉じる際も、サムネイル画像が元の位置に戻っていく表現を伴って戻るようにしています。
カスタムトランジションについては、本章のサンプルにおける本題とは少し外れてしまうトピックにはなります。ですが、実現のために適用が必要なプロトコルの実装方法や、画面遷移アニメーション実行時の位置調整に関する処理方法を押さえておくことで、様々な場面で応用できると思います。iOSアプリの機能とマッチしたアニメーションを伴うUI表現は、変化をより強く伝えるという効果に加えて、見え方や使用感・触り心地といった体感の部分を、利用するユーザーに対して印象付ける部分でもあるように思います。
Storyboardの構成や、それぞれの画面の役割に関するポイントについても、簡単な図解とともに紹介していきます。まずは、ライブラリでデザインをカスタマイズしたTabBar表示をしているふたつの画面についてです。画面を構成する部品となる個々のStoryboardはシンプルな構成となっていますが、それぞれの画面を組み合わせて、ひとつの画面を構成している形がポイントになります。また、画面遷移後に表示される詳細表示画面についても、UIScrollViewDeletegateを利用した表示サムネイルの状態に関するハンドリングや、配置しているView要素の配置、そしてライブラリを利用した表現における実装方法に関する点がポイントになります。
(1) タイル状に配置したサムネイル画像を表示する、フォトギャラリー状のUI表現をする画面
こちらの画面については、基本的にはUICollectionViewで構成している、奥行きを伴ったカルーセル状の動きをするセルの内部に、タイル状にサムネイル画像を並べて表示しているUICollectionViewをさらに表示することで、実現しています。また、このようなUI表現を実現する際に、後述する「AnimatedCollectionViewLayout」・「Blueprints」というふたつのライブラリを活用したUI実装についても、押さえておくといいかと思います。
(2) スクロール操作での切り替え可能なタブ型の記事一覧表示のようなUI表現をする画面
タブ型の記事一覧表示をするUI実装をライブラリを利用せずに構築する場合には、1UIScrollView&ContainerViewを組み合わせた構造や、UICollectionView&UIPageViewControllerを採用する場合が多いかと思います。このようなUI実装に不慣れな場合や、必要な要件次第では、なかなか骨の折れる場合もあります。このように、画面構造が複雑になりがちで、表示要素にあたる画面を分けて管理している場合には、後述する「Parchment」を活用した実装にすると、とてもいい感じにまとめることができます。
(3) カスタムトランジションを伴った画面遷移後に表示される詳細表示画面
この画面については、前章で紹介したサンプルと、少し似た形の画面構成になっています。サムネイル画像の下部がNavigationBarの高さ相当の位置に到達するまでは、ダミーのNavigationBar相当のViewを徐々に表示させるような振る舞いをします。それとともに、細かなUI表現部分について、そのままでは実装が難しそうな部分に局所的にライブラリを活用した形にしています。
全体的なUI実装に対して、ライブラリを利用する場合と、局所的に利用する場合の両方のアプローチがそれぞれあります。それぞれの画面におけるアプローチの仕方の違いにも注意しながらサンプルコードを紐解いていくと、より理解が深まるのではないかと思います。
本章で紹介しているサンプルでは、APIサーバーとの非同期通信処理や、画像キャッシュ読み込みに関する処理は含んでいません。ですが、iOSアプリのUI実装を考えていく場合には、デザイン的に一癖がありそうだ、と感じてしまうものではないかと思います。今回のようなレイアウト及びUI表現については、Webサイトのデザインでもよくお目にかかる機会がありそうなものなので、「iOSアプリでの実装もさほど難しくないのでは?」と思ってしまうかもしれません。ですが、いざ自前でライブラリを利用しない方針で実装や調整をしようとすると、かなり大変で、工数もかかってしまうものです。
このように、自前での実装が難しい場合や、スケジュールの兼ね合い等の関係で、まずは実現することの優先順位が高い場合においては、UI実装に関するライブラリを上手にかつ積極的に活用していくことで、工数の短縮や実装の簡略化が実現できる場合もあります。加えて、UI実装に関するライブラリを選択する際は、よく吟味した上で選択すると、よりよい選択ができると思います。判断材料としては、Githubリポジトリのスター数だけではなく、直近での改善度合いや、リポジトリにライブラリと一緒に紹介されているサンプル実装等があります。
本章のサンプルでは、カスタムトランジションに関する処理をはじめとする画面遷移の動きに関連する位置調整や、アニメーションに関する部分については自前で実装していますが、
・UICollectionViewを利用したリッチなスライドショーのような動き
・MosaicLayoutのような写真サムネイルの一覧表示
・タブ一覧表示に紐づいたデータの一覧をうまくまとめた形での表示
・長いテキスト表示部分に対して「もっと読む」ボタンを伴ったトグル表示
・Pinterestのように表示コンテンツの上に浮かび上がって見えるようなTabBar表示
・ブログ記事等で利用されているタグクラウドのようなキーワードの一覧表示
といった部分については、デザイン的な観点でも美しい見た目かつ、ライブラリで提供されている利用方法に則っていく方針であれば、シンプルに利用できるものをピックアップし、これらを組み合わせた形のUI実装をしてみました。
ここでは、このサンプルを作成する際に用いたライブラリについて、簡単に紹介していきます。
https://github.com/KelvinJin/AnimatedCollectionViewLayout
こちらは、筆者が以前に執筆した「iOSアプリ開発 UI実装であると嬉しいレシピブックVol.2」でも紹介したライブラリとなります。UICollectionViewにおける7種類のセルを切り替えるときのアニメーション表現をするためのレイアウト部分のみを提供する形です。導入と適用が簡単な点が、大きなメリットになります。本章のサンプルでは図1.5のように、サムネイル一覧を奥行きのあるカルーセルのような形の動きを実現するために利用しています。
このような奥行きを伴ったUI実装を自前で実現しようとする場合には、かなり難易度が高くシビアなコードになります。ですが、このライブラリを活用することで、手軽に実現することができるのは、大きなメリットではないかと思います。
https://github.com/zenangst/Blueprints
こちらも、AnimatedCollectionViewLayoutと同様に、UICollectionViewで複雑なレイアウトを実現するためのライブラリとなります。こちらは、Instagramの画面のような写真の並べ方をするレイアウトを実現する場合に、とても重宝するライブラリです。本章のサンプルでは、図1.6のように、表示するサムネイル画像の縦横比を維持した状態で、サイズが異なるものを規則性を保った状態で一覧表示するために利用しています。
このような表現を実現する場合についても、UICollectionViewLayoutを継承したクラスを利用して、配置するセルに付与されるレイアウト属性値を計算する処理を自前で実装して、要件を満たすようなレイアウトを実現するのは結構大変です。ですが、このライブラリを活用することで、UICollectionViewを利用して、表示データをタイル状に配置するレイアウトをわかりやすい形で実現することができます。そのため、とても重宝するライブラリのひとつではないかと思います。
https://github.com/rechsteiner/Parchment
こちらは、複数の表示画面を上手にタブ型のUIにまとめた形で表現する場合に、頭の片隅に置いておくとよさそうなライブラリのひとつだと、個人的に感じています。本章のサンプルでは、図1.7のように一覧画面を表示するための画面を、このライブラリを利用して配置する形の実装をしています。
このような画面を実装する際に、筆者はStoryboardを利用した実装を利用することが多いです。コードで実装をまとめたいときや、スクロールを伴ったタブ表示の部分をある程度でき上がっている状態で利用したいとき等では、そのよさを生かすことができると思います。
https://github.com/instacart/Nantes
スマートフォン対応がされているWebサイトで、記事コンテンツのようなページに対して「もっと読む」ボタンのように、本文の途中までを予め表示しておき、ボタンを押下すると全文が表示されるテキストのトグル表示についてです。iOSアプリでの実装を考える場合、デザイン次第ではUITextViewを利用したUI実装をする必要があったり、トグル操作を発火するためのボタンエリアとの兼ね合いが難しくなりやすい表現かと思います。本章のサンプルでは、図1.8のように、トグル表現をする部分をライブラリの実装に合わせています。
このライブラリは、Objective-Cがメインだった時代に作られた、有名なライブラリ「TTTAttributedLabel」をSwiftへforkして作られているライブラリになります。また、本章のサンプルで利用しているテキストに対するトグル表示の実現だけなく、Webサイトのリンクテキストのような表現をする場合でも、活用できます。Webサイトでは簡単に実現できるテキストに関するデザインや振る舞いが、iOSアプリでは一手間かけた実装をする必要があるものになってしまうことは多いです。地味な部分ではありますが、この点に注意しておくといいと思います。
https://github.com/ElaWorkshop/TagListView
ブログサイトやポータルサイト等、多数の属性の異なるコンテンツが混在するWebサイトで設けられているタグクラウドやハッシュタグでの検索画面のような表現についても、UICollectionViewLayoutを利用して実装することができます。ですが、デザインやテキスト表示等のデザイン調整や表示・内容の追加・削除といった状態管理までを考えると、なかなか難しさを感じる実装になります。本章のサンプルでは、図1.9のように、デザイン表現や押下時のアクションを考慮したタグ一覧表示をする部分を、このライブラリで実現しています。
こちらもWebサイトではよく見かけるものの、iOSアプリで実現することを考えると、やはり手間のかかるUI実装の事例ともいえます。こちらは、InterfaceBuilderでの調整も考慮されています。なので、UICollectionViewLayoutを利用した実現方法と比べても、より柔軟にかつ簡単に取り扱うことができる点は、嬉しい部分ではないかと思います。
https://github.com/hussc/PTCardTabBar
UITabBar関連の部分については、今回選択したライブラリをそのまま利用する方針です。本章のサンプルでは、図1.10のように、既存のUITabBarの表示とは一味違う、表示コンテンツの上に浮き上がって見えるような表現をInterfaceBuilderだけで実現できるような形にしています(※この実装はiOS13以降でSceneDelegate.swiftを利用している場合の実装になります)。
UITabBarの表示デザインを、抜本的にカスタマイズするような場面は少ないかもしれません。ですが、この部分に関するUI表現を自前でカスタマイズしようとすると大変な場合もあるので、表現に応じて、その他のライブラリ等も参考にしながら検討していくといいかと思います。