Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

あのアプリの動きをUIKitのみでDIYしてみる(part2)

1,380 views

Published on

雑兵MeetUp #8 LT大会 〜伝えたい事が、あるんだ〜 の登壇資料になります。あのアプリ(Facebook / Twitter)のUIで再現できそうな部分をトレースしてDIYした際の実装ポイントをまとめました。
特に今回は、サムネイル画像を枚数に応じて段違いのタイル状に配置する部分を中心に解説しています。

※ 2017/06/23: 更新
iOS UI実装勉強会 ~Storyboardを積極的に使うべきか、あるいは消極的に使うべきか~でも、こちらの資料で登壇します。

Storyboardを活用していく上で検討を行った方が良い部分についてと、AutoLayoutの制約をコードで変更するアプローチ以外にどのような、アイデアがあるかに関しても追記しました。

Published in: Technology

あのアプリの動きをUIKitのみでDIYしてみる(part2)

  1. 1. あのアプリの動きをUIKitのみで DIYしてみる(Part2) 雑兵Meetup #8 LT大会 〜伝えたい事が、あるんだ〜 2016/12/22 Fumiya Sakai 再演:iOS UI実装勉強会 〜Storyboardを積極的に使うべきか、あるいは消極的に使うべきか〜 @Sansan株式会社 2017/06/24 Fumiya Sakai
  2. 2. 自己紹介と簡単な経歴など ✦ 今までの仕事履歴(本業) 石川県金沢市生まれ 前はサーバーサイドのプログラマ ※Rails&PHP使い 26歳〜32歳: Webプログラマ(PHP & Rubyがキャリア長い) 23歳〜25歳: Webデザイナー兼ディレクター 本業は今はiOSのネイティブ開発者 趣味:シルバーアクセサリー集め・スイーツ作り・アプリ開発 これまでも女子向け・グルメ・エンタメ関連のお仕事が多かった Qiita : http://qiita.com/fumiyasac@github Github : https://github.com/fumiyasac ✦ 酒井文也(さかい ふみや) 東京(大塚)住まいの32歳 こんな格好を普段からしているので 遊び人に見られますがエンジニアです。 文系卒に思われますが 実は数学科で理系卒です。 めっちゃお酒好きそうに見えますが ビール苦手でお酒も超弱いです。 今でもたまにUIまわりとか触りたく なることがあったりなかったり 今年の6月から女性向けのアプリ つくってます@ ever sense, Inc. 最近のはまっている食べ物は カボチャと担々麺と甘栗です。 最近はSwift以外ではRailsやLaravel・CakePHP・ReactNative
  3. 3. これまでに作ったもの(ネイティブアプリ) ① 簡易家計簿アプリ「Coffre」 ② ゲームアプリ「10秒虫食い算」 ・カレンダーを自作しています ・シンプルなお小遣い帳感覚で支出管理できます ・全問正解者ほとんどいません… ・不定期ですがコラムも書いています ・サーバーサイドはRuby on Railsを使用 http://www.coffre.me/ ・デザインにもこだわってみました(特にグラフ) ・実はちょっとバグがあります。 ・問題は今後追加予定(現在110問収録) 個人的にはなりますが、他にもアプリ・Webサービスなど開発中です(2016年も宜しくお願いします) ・サイト等は次回のアップデートで公開予定 http://blog.just1factory.net/services/284 ・若干の中毒性を含みます
  4. 4. カレンダーが好きでライブラリを作りました 日本の祝祭日を計算で出してくれる ・カレンダーアプリ等での活用を想定 ・シルバーウィーク・ゴールデンウィークも対応 ・ハッピーマンデー法の施行も対応 ・春分の日・秋分の日にも対応 ・過去の祝祭日もおおむね考慮はしている みなさまのStarや温かい応援のお言葉もありまして無事に1年間運用することができました!激しく感謝! CocoaPods & Carthage & 手動で導入可能 ・HTTP(HTTPS)通信は不要 ★CalculateCalendarLogic ver0.1.3 【2017年】Swift3系への正式対応をしていますので是非README等を参考に導入してみてください! ・Github: https://github.com/fumiyasac/handMadeCalendarAdvance ・実装解説: http://qiita.com/fumiyasac@github/items/33bfc07ad36dfffcdf8f ・Github: https://github.com/fumiyasac/handMadeCalendarOfSwift 新機能や運用保守は継続しています
  5. 5. ✦ iOS開発を始めた時や書籍でも最初に触れる場所 Storyboard = コードとデザインの接点 アプリの画面数やパーツ数が多くなるとViewControllerのStoryboardやView(Xib)に綺麗に分割を! 一番最初に慣れ親しむ部分ではありますが、AutoLayoutやInterfaceBuilderの扱いはなかなか奥が深いとこ。 ★遷移の関係性やその画面で使用する部品がわかりやすい ・実は以前にFlashで開発をしていたこともあり個人的には馴染みやすかった 【注意するべきポイント】 重なり方やそれぞれのパーツの関係性を保ったままの状態でコードの実装を考え易いのは良い点 画面遷移に関しても管理をしやすいようにStoryboard ReferenceやStoryboardからインスタンス化する ★初見殺しのAutoLayoutの設定には最初は泣かされました ・UIScrollViewまわりの設定をAutoLayoutで行う場合 ・AutoLayoutで制約を貼ったところから複雑なアニメーションをする場合 View設計で生まれるジレンマ ・こだわるとどうしても調整事項増える? ・AutoLayoutとコードの共存をどうする? AutoLayoutで疲れそうなところ ・できるだけController側でさばかない ・細かな調整やアニメーションはコードで調節
  6. 6. ✦ UITableViewとUICollectionViewの使い方が重要になる FacebookやTwitterの画像表示をDIYする InterfaceBuilderとコードの合わせ技を使う UITableViewやUICollectionViewに関してはどのアプリでも良く使う部分なのでその表現は腕の見せ所 FacebookやTwitterで使用されているUIに関しては、アプリの中でも試してみたい表現の一つであるかも… ★タイル状の画像の配置について ・AutoLayoutの制約と優先度の設定がポイント ・基本は3行×2列の画像の配置がベース ★カード状の配置やフォトライブラリ表現について UICollectionViewをUITableViewCellの中に入れる ・UICollectionViewをUITableViewの中に埋め込む ・UICollectionViewFlowLayoutクラスを利用して位置の調整 【類似のライブラリもありますがここはあえてDIYしてみる】 ・Photos.frameworkを利用した画像の表示 ・サムネイル画像の枚数に応じて制約をコードで調整 UICollectionView in UITableViewCell InterfaceBuilder & Code (Swift)
  7. 7. ✦ 画像のタイル表示での別解になりえる実装アプローチのアイデア この場合に他に考えられるアプローチ 今回は素直にAutoLayoutの制約を変えるアプローチを選定しましたが、設計次第では別解でも良いと思う。 ★UITableViewCell内にUICollectionViewを展開(別解1) ★UIStackViewを入れ子構造にしてレイアウトをする(別解2) JFYI: AutoLayoutでScrollViewを使ったコンテンツ 切り替えロジックを設定をした事例 ・どちらもメリットやデメリットがある ・コーディングルールや設計によっても選定基準は変わる 【どちらが正解とは一概には言い切れない部分】 iPhoneアプリでUIを作るためのTipsとContainerView・ UIPageViewControllerを使ったサンプル紹介 https://goo.gl/2vU5XD ・どのあたりまでコードで調整するの? ・レイアウトのための分岐処理が煩雑にならない? ・Storyboard複雑にならない? 😇
  8. 8. ✦ 今回のサンプルで実装時に留意する点は下記の4つ ハマりやすいポイント 今回は特にUIまわりのライブラリを使うことなく、UIKitのみで実装することができる表現になりました。 ★UITableViewCellから画面遷移をさせる部分の処理 ★配置したUIImageViewの幅・高さを変更する ★Y座標の位置が動的に変わるUIImageViewの上にボタンを配置する ★UICollectionViewをスクロールした際に中央に持っていきたい 解決方法: そのままUITableViewCell内に設置したGestureRecognizerのアクションやボタンアクションではNG 解決方法: そのままconstantプロパティに計算で出した幅・高さを代入するとAutoLayoutの警告が出る 今回はサムネイル画像用のUIImageViewの高さが動的に変わるので工夫が必要 スクロールの終了位置をスクロールの速さを予測して動かすような処理等の記載も合わせて必要 クロージャーを利用して処理の中身はUITableViewの値設定時に追加する 幅・高さの制約の優先度(priorityプロパティ)を1000→750へ変更する 解決方法: Interface上でボタンの大きさと位置を6枚目の画像の大きさに合わせるように設定 解決方法: UICollectionViewFlowLayoutクラスを継承したクラスを作成し該当メソッドをOverride
  9. 9. ✦ 詳細解説に関しても下記の文章にまとめていますので併せてどうぞ 今回のサンプルと詳細解説はこちら 画像サムネイルの配置に関する実装コードに関しましてはQiita記事内でより詳細に解説を行なっています。 詳細解説及びサンプルはこちら Github:https://github.com/fumiyasac/FacebookLikeUI 【サンプルはSwift3.0で作成しています】 今回のサンプルについて FacebookやTwitterのアプリで気になった表現を自分なりにトレースした際の実装ポイントまとめ http://qiita.com/fumiyasac@github/items/eb5b17ab90f5aa27b793 フォトライブラリの内容表示部分をフォームに応用した例: http://qiita.com/fumiyasac@github/items/3218c35de5e59f3bfafa 画像の枚数に応じてタイル表示部分の大きさを変更するレイアウトと画面遷移関連 自分のフォトライブラリーに登録されている画像の一覧を取得してUICollectionViewに表示する 横方向にカード型のUICollectionViewを配置しスクロールすると表示対象のセルが中央に表示される 上記に挙げたUI上で綺麗に動かす&表現する上での参考になればとても嬉しく思います。 Firebaseとそのデータを扱いやすくするライブラリ(Salada)を用いたToDoリストの簡易サンプル
  10. 10. ✦ 全体的なレイアウトの初期配置に関してはInterfaceBuilderで行う 全体的なセルの配置に関して 今回ではテキスト部分と画像サムネイルの高さは動的に変化する部分なので注意して制約をつけること。
  11. 11. 1. 6枚のUIImageViewへの制約設定のポイント それぞれの配置に関する設定に関してはコードで変更がないものに関してはInterfaceBuilder上で配置する。 ★ポイントその1: それぞれのUIImageViewの隙間 に関する制約と幅・高さの制約 をつける。 (優先度やサイズはコードで) ★ポイントその2: InterfaceBuilderを使用して、 下記設定にも同様に設定を行う ・UIImageViewの番号識別tag値 ・ContentMode = Aspect Fill ・Clip to Boundsにチェック ※各マージンの制約については Qiitaやサンプルコードを参照。
  12. 12. 2. 6枚目のUIImageViewの上に重ねるボタン配置 コードでの処理とInterfaceBuilderでの設定をうまく使い分けることでコードの節約と見通しをよくする。 ★ポイントその1: このボタンの大きさを6枚目の UIImageViewの大きさと合わせて その後に「Ctrl + ドラッグ」を 6枚目のUIImageViewに向けて制約 を設定する。 「6枚目のUIImageViewと合わせる」 ・水平&垂直方向中心位置 ・幅&高さ ★ポイントその2: 6枚目以降の数を表示するラベル は任意の大きさでその後に「Ctrl + ドラッグ」をボタンに向けて 制約を設定する。 「ボタンと合わせる」 ・水平&垂直方向中心位置 ★UIImageViewの幅や高さ・優先度に関する設定 ★マージンに関する制約や識別用タグの値等の設定 IBoutletでコード上に切り出しておき、それぞれの値はコードで処理 今回変更する必要がないものに関してはInterfacebuilder上で済ませる
  13. 13. 3. XCode上(InterfaceBuilder)での見え方について 任意のUI要素について、ある要素と位置や大きさを合わせるような場合は配置の仕方や設定に注意すること。 ★ポイントその1: このボタンの大きさを6枚目の UIImageViewの大きさと合わせて その後に「Ctrl + ドラッグ」を 6枚目のUIImageViewに向けて制約 を設定する。 「6枚目のUIImageViewと合わせる」 ・水平&垂直方向中心位置 ・幅&高さ ★ポイントその2: 6枚目以降の数を表示するラベル は任意の大きさでその後に「Ctrl + ドラッグ」をボタンに向けて 制約を設定する。 「ボタンと合わせる」 ・水平&垂直方向中心位置
  14. 14. ✦ コードを使って幅と高さの計算を行う&優先度を下げておく サムネイル画像の枚数に合わせてレイアウトを決めるのは、setLayoutConstraintSettingsメソッドで行う。 ★画像データの配列を受け取り要素の個数に合わせてレイアウトを決める 4. サムネイル画像の枚数に応じてレイアウト変更 //該当番号のImageView(サムネイル)のセットを行う func setImageViews(images: [UIImage]) { //画像枚数に応じて制約を決定する setLayoutConstraintSetting(count: images.count) let targetImageViewSet: [UIImageView] = [ contentsImage1, contentsImage2, contentsImage3, contentsImage4, contentsImage5, contentsImage6 ] //12/21:【修正】全てのサムネイル格納用UIImageViewのUIimageプロパティに画像をセットする前にnilを入れて初期化する for (_, contentImageView) in targetImageViewSet.enumerated() { contentImageView.image = nil } //渡された画像をUIImageViewに入れてTapGestureを付与する for (index, targetImage) in images.enumerated() { if index < ImageConfig.maxPhotoCount { let imageTap = UITapGestureRecognizer(target: self, action: #selector(MainContentsCell.tapGesture(sender:))) targetImageViewSet[index].image = targetImage targetImageViewSet[index].addGestureRecognizer(imageTap) } } } 枚数に応じた幅・高さの決定と 優先度の設定を行う タップ時のGestureRecognizerで 発動する処理との関連付けを行う
  15. 15. ✦ クロージャー変数をUITableViewのクラスに用意して処理をする クロージャーを用いて「処理の実態:表示側」に「処理のトリガー:セル側」に分けてつなぐイメージです。 ★セル側に定義するクロージャー変数:var transitionClosure: ((Int?) -> ())? 5. セル内のアクションで遷移する場所を変える //MainContentsCell側に設定したクロージャーの内部の処理を記載する cell.transitionClosure = { [weak self] num in if num != nil { � //MainContentsCell側の画像がタップがされた場合は該当画像の表示をポップアップで行う let toVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ImageController") as! ImageController toVC.targetImageList = imageList toVC.targetImageCount = num! self?.present(toVC, animated: false, completion: nil) } else { //MainContentsCell側のボタンがタップがされた場合は画像一覧の表示を行う let toVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "DetailController") as! DetailController toVC.targetImageList = imageList self?.navigationController?.pushViewController(toVC, animated: true) } } UITableView側の処理では クロージャー内の処理を記載 UITableViewCell側では 各アクションで実行させる //「もっと他の画像を見る」ボタンのアクション @IBAction func moreImageAction(_ sender: UIButton) { transitionClosure!(nil) } //サムネイル画像のTapGesture発動時に実行されるメソッド func tapGesture(sender: UITapGestureRecognizer) { let targetNumber: Int = (sender.view?.tag)! transitionClosure!(targetNumber) }
  16. 16. ✦ よく見るアプリのUIは真似をしてみると面白い部分がたくさん 今回のまとめ ご清聴ありがとうございました!またこのような機会があった際には是非ともよろしくお願い致します! ★ライブラリを用いなくともUIKitのみで実現できるケースも多い クラスの継承やオーバーライド・UIKitの組み合わせ方を工夫することで実現可能な場合も多い ★AutoLayoutの扱いやInterfaceBuilderでの設定とコードでの設定の使い分け 今回の例では「値に応じて変化がない部分」についてはInterfaceBuilderで設定しそれ以外はコードで ★よくあるアプリのUIは結構応用できる局面も多いかもしれない 要素ごとに細かく分けて「どのパーツを組み合わせているか?」の仮説を立てる ★自分ルール(今回守れなくってすみません…) 【良いアウトプットのために】 発表・登壇時はこの中のいずれか2つを 絶対に準備するルールを設けています!

×