できるだけUI系のライブラリを用いないアニメーション
を盛り込んだサンプル実装まとめ(追加版)
Roppongi.swift #2 @ VISITS Technology様
2018 / 04 / 03
Fumiya Sakai
自己紹介
・酒井 文也 (Fumiya Sakai)
・ever sense. inc App Engineer
・Designer → ServerSide Engineer → AppDeveloper
Accounts
・Facebook: https://www.facebook.com/fumiya.sakai.37
・Twitter: https://twitter.com/fumiyasac
・Github: https://github.com/fumiyasac
・Qiita: https://qiita.com/fumiyasac@github
Who are you?
Library (Personal)
Products (ever sense. inc)
下記2つの新規アプリの立ち上げ(iOS側)に携わっ
ています。現在も継続して運用中です。
このトピックスを選んだ理由
UI構築やアニメーション等の実装に携わる機会が多かった
こだわり1. 不自然にならない心地よいタイミングの動き
こだわり2. 動きの肝心なところはできる限りDIYする
こだわり3. 内部の構成も同様に気配りをする
参考資料とアプリ:
UIの中に随所に自然な形で散りばめられた心地の良いアニメーション
・AWA - 自然さを追求した本質体験のためのUX
https://www.slideshare.net/KokiTogashi/awa-coockpad-tech-kitchen-
20170913
今回はアプリ内の下記の2つの画面の動き
に近いものをDIYしてみました。
今回のサンプル概要
・Githubサンプル
https://github.com/fumiyasac/InteractiveUISample
・詳細解説Qiita
https://qiita.com/fumiyasac@github/items/d1b56ffc6d7d46c0a616
今回のサンプル概要
・Githubサンプル
https://github.com/fumiyasac/InteractiveUISample
・詳細解説Qiita
https://qiita.com/fumiyasac@github/items/b694f9859cbb61c95c1a
【Chapter1】2つのコンテンツが切り替わるタブメニュー
UIScrollViewを活用するタブ&コンテンツの動きに関する設計
1番目のコンテンツ
(ContainerView)
(1) まずはタブ用とコンテンツ用のScrollViewを配置する。
<コンテンツ用ScrollViewのレイアウトを当てる手順>
2番目のコンテンツ
(ContainerView)
タブ用のScrollView コンテンツ用
ScrollView
(2) [Simulated Metrics] でFreeformを選び、[Simulated ViewController]
でサイズを750pxにする。
(3) ScrollViewの内部に、Widthを375px・Height=コンテンツ用ScrollView
の大きさのContainerViewを隣り合わせに2つ配置する。
(4) 1番目は上・左・下方向に0の制約を、2番目は上・右・下方向に0の制約
をそれぞれつける。
(5) ContainerViewからコンテンツ用のScrollViewへ [Ctrl + ドラッグ] して
Equal WidthとEqual Heightの制約をそれぞれつける
(6) [Simulated ViewController]をFixedへ戻す。
※ iPhone8の見た目の場合
【Chapter1】補足資料:制約の図解
・コンテンツ用のScrollViewをスワイプすると、動くバーと一緒に切替
・タブ用のScrollViewにあるボタンを押下後、0.26秒間のスライドをして切替
2つのScrollViewの動きについて
【Chapter2】UIScrollView & UIStackViewの構成
部品として切り出したUIViewの配置とAutoLayoutの制約に関するもの
(1) ContainerViewの高さをContainerViewにつな
がれているViewControllerのプロパティにする
UIStackViewを内包した
UIScrollView
<高さが可変するViewへの制約>
2番目のコンテンツ
(ContainerView)
高さが可変するView
高さが可変するView
+
セル高さが可変する
UITableViewを設置
Notificationを送信
して高さを調節する
height ≧ 0
priority = 1000
height = ●●
priority = 250
<StackView内のContainerViewへの制約>
UIView
UIView
ContainerView
(2) ViewControllerにNotificationを設定しておく
(3) viewDidLayoutSubviewsでtableViewの内部
コンテンツ高さとtableViewの高さを合わせる
(4) viewDidAppearでNotificationを送信する
【Chapter3】視差効果とCoreAnimationを組み合わせる
一覧表示部分のスクロールに合わせたアニメーションの効果を演出する
(1) UITableViewのスクロールに合わせてサムネイル画像がパララックスする
scrollViewDidScroll(_ scrollView: UIScrollView) でスクロール検知時に
セル側で設定したサムネイル画像の上下につけた制約を変更する。
利用するDelegate : UIScrollViewDelegate
<2つの性質の異なるアニメーション共存させる>
(2) スクロールでセルが現れる/消える際にフェードイン/アウトが入る
利用するDelegate : UITableViewDelegate
tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell,
forRowAt indexPath: IndexPath) でセルが表示されるタイミングになったら
「CoreAnimation」で設定したフェード処理を実行する。
【Chapter4】記事詳細を表示する想定の画面設計
ヘッダー上部のNavigationとアイキャッチ画像がスクロールに伴って動く
このViewControllerのポイント
(2) このViewControllerのrootに
UINavigationControllerを設置する
コンテンツ用表示
UITableView
NavigationBar
GradientHeaderView
ArticleHeaderView
ArticleHeaderViewは、
配置されたtableViewの
.headerにセットする
UITableView
(3) MainViewControllerから
ArticleViewControllerの画面遷移は、
ArticleCustomTransition.swiftを利用
※ 上の制約のみSuperview.Topにつけること
(1) UITableViewの制約:上下左右0
・GradientHeaderView
・ArticleHeaderView
ダミーのヘッダーになるView
スクロールで伸縮する画像のView
スクロール量に応じてArticleHeaderViewに配置した画像とGradientHeaderがアニメーションが行われる。
【Chapter4】補足資料:挙動に関するイメージ
動きの概要
y < 0 y > 0 ScrollViewDidScrollで制約を更新する
コードでConstraintを加えて、引数を
UIScrollViewとする、
「setParallaxEffectToHeaderView」
で制約を更新して動きを出す。
全体の流れと解説
(1) NavigationBarに内包するヘッダー
クラス名: GradientHeaderView.swift
(2) 画像を表示するヘッダー
クラス名: ArticleHeaderView.swift
記事を表示するために下へスクロールすると画像が隠れる
タイミングに合わせてNavigationBarが出現する。
y = 0
背景のアルファとダミーヘッダーの上方向の
制約を、
「setHeaderNavigationTopConstraint」
で更新して動きを出す。
【Chapter6】iPhoneXの画面に関する考慮
ヘッダーViewの高さをデバイスのサイズによってうまく調節する
端末の縦横サイズで場合分けを行う
デフォルトのNavigationBarと高さが
揃うように調節を加えると綺麗になる。
高さ調整を加える
(1) ヘッダーの高さ
クラス名: GradientHeaderView.swift
iPhoneX: 88.5, それ以外: 64.0
(2) 画像を表示するヘッダーの高さ
クラス名: ArticleHeaderView.swift
iPhoneX: 244, それ以外: 200
【Chapter7】 数値が0から該当の数まで変化する表現部分
UILabelを拡張したクラスとTimerやCoreAnimationを活用した表現の実装
・実装例: AnimationCounterSample
https://github.com/fumiyasac/AnimationCounterSample
<この実装のサンプルリポジトリはこちら>
・実装例1: 該当の設定値に近づくと変化スピードが緩やかになる
・実装例2: 値の変化があった場合にドラムロールの様に変化する
マスクで動きを含む見える範囲を制限
拡張したUILabelのクラス内には、
CoreAnimationを利用して、
値が上またが下から出るようにする。
拡張したUILabel
マスク用UIView
拡張したUILabelのクラス内には、Timerが仕込んであり、
選択された値との差分を計算し、EaseIn / EaseOut表現をする。
【Chapter8】 UITableViewのアコーディオンの様なアニメーション実装
sectionごとに表示する値を配置しセクションヘッダーのTapGestureを通して開閉する
UITableViewはGroupedに設定する
(1)表示するデータの構造は[(extended: Bool, genre: [Genre])]の形
extendedは現在のセクションのセル開閉状態を指し示すためのもの。
<この実装のポイントになる部分>
(2) セクションヘッダーのViewにTapGestureRecognizerを付与する
TapGestureでのアクションをトリガーにしてextendedの値を変える。
(3) セクションごとに格納されているセルの更新を行う。
storyRelatedTableView.reloadSections(NSIndexSet(index: section) as
IndexSet, with: .automatic) で該当のセクションの状態を更新する
ヘッダーが引っ付かないようにする
extendedの状態でnumberOfRowsInSectionの値を変える形にする
【Chapter9】 カスタムトランジションと左端のスワイプで画面遷移をする
3D回転の動きをカスタムトランジションはCATransform3Dを利用して作成する
<このアニメーションの実装にあたり>
(2) 90° (π/2) 回転するように設定
var perspectiveTransform = CATransform3DIdentity
perspectiveTransform.m34 = -0.002
containerView.layer.sublayerTransform =
perspectiveTransform
(1) 適用するパースペクティブの設定をする
Present
Dismiss
SwipeでDismissする
Flip
Animation
snapshotView.layer.transform =
CATransform3DMakeRotation(CGFloat(Double.pi /
2), 0.0, 1.0, 0.0)
※ containerViewやsnapshotViewの位置関係と回転
� アニメーションをうまく利用して遷移を実行する。
UIPercentDrivenInteractiveTransitionを継承したクラスを、
Dismissのスワイプ時にだけ適用する。
【補足1】Storyboard設計(1)
【補足2】Storyboard設計(2)
【補足3】View - Model - PresenterパターンとUI
データとViewとの繋がりをできるだけパターン化して整理する
ViewController Presenter Model
データ構造の定義UI表示とデータ取得の仲介データを伴うUIの表示
API通信を伴う部分は、
SwiftyJSONを利用して初期化
→ データ構造の作成
※APIでの非同期通信
Alamofireをラッピングした
APIRequestManager.swiftを利用
(1) ViewController側で実行する
プロトコルの定義
(2) Modelの形に則ったデータの
作成やAPI通信のハンドリング
Presenter側で定義した
プロトコルの具体的な実装をする
データを取得 → UIの更新
の流れをつくる
Viewとデータの関係を密接にする
【補足4】UIに関するクラスが多くなる場合の整理ルール(1)
【補足5】UIに関するクラスが多くなる場合の整理ルール(2)
よく使うView関連のクラスはClassに切り出す or プロトコルを定義する工夫をする
@marty-suzukiさんよりご指摘頂いた例
※1: 初期化
func tableView(_ tableView: UITableView, cellForRowAt
indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCustomCell(with:
SampleTableViewCell.self)
����・・・(以下処理が続く)・・・
}
例. UITableViewCellの共通化
sampleRelatedTableView.registerCustomCell(SampleTab
leViewCell.self)
※2 セルをインスタンス化する
※ UICollectionViewもほぼ同様な方法で共通化ができます。
類似した処理や記載をパターン化できる礎を予め準備する
実務においても活かすことができること
ライブラリを使わないUIアニメーション実装は小さな処理の集合体の組み合わせ
1. あえてDIYをすることで見えてくるもの
2. UIの作り込みだけではなく設計や部品化する部分についてのテクニック
・Animationの組み合わせ(UIView.animateとCoreAnimation)で表現するワンポイント表現
・よくあるポピュラーそうなUIやライブラリの挙動をさらに深く知ることで得られた知識と知見
・データとの連携をするような局面ではできるだけ流れやパターンにしておくと後からの機能追加も楽になる
・最初にあらかたの設計や構成にあたりをつけておくと実際のUI構築が捗る (自分の場合は手書きで残す)
・共通で使用する値や頻出のViewクラスを上手に使い回す&簡素化してまとめるための工夫をする
・複雑なUI表現を作り込む場合の組み合わせ方やまとめ方に関する戦略と解法
エバーセンスよりメッセージ
弊社ではiOS / Androidエンジニア募集しております!
https://www.wantedly.com/projects/158484

できるだけUI系のライブラリを用いないアニメーションを盛り込んだサンプル実装まとめ(追加版)