14
あのアプリの動きをUIKitのみで DIYしてみる(Part2) 雑兵Meetup #8 LT大会 〜伝えたい事が、あるんだ〜 2016/12/22 Fumiya Sakai

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

Embed Size (px)

Citation preview

あのアプリの動きをUIKitのみで

DIYしてみる(Part2)

雑兵Meetup#8LT大会〜伝えたい事が、あるんだ〜

2016/12/22FumiyaSakai

自己紹介と簡単な経歴など

✦ 今までの仕事履歴(本業)

石川県金沢市生まれ

本業はサーバーサイドのプログラマ※Rails&PHP使い

26歳〜32歳: Webプログラマ(PHP&Rubyがキャリア長い)

23歳〜25歳: Webデザイナー兼ディレクター

チャンスがあればiOSアプリ開発も絶賛やってみたい!

趣味:シルバーアクセサリー集め・スイーツ作り・アプリ開発

女子向け・グルメ・エンタメ関連のお仕事が多い

Qiita:http://qiita.com/fumiyasac@github

Github:https://github.com/fumiyasac

✦ 酒井文也(さかいふみや)

東京(大塚)住まいの32歳

こんな格好を普段からしているので遊び人に見られますがエンジニアです。

文系卒に思われますが実は数学科で理系卒です。

めっちゃお酒好きそうに見えますがビール苦手でお酒も超弱いです。

今でもたまにUIまわりとか触りたくなることがあったりなかったり

今年の4月からフリーランスです。(割とお堅い感じの会社にいます)

最近のはまっている食べ物はカボチャと担々麺と甘栗です。

最近はSwift以外ではRailsやLaravel・CakePHP・Node.jsなんかも

これまでに作ったもの(ネイティブアプリ)

①簡易家計簿アプリ「Coffre」

②ゲームアプリ「10秒虫食い算」

・カレンダーを自作しています

・シンプルなお小遣い帳感覚で支出管理できます

・全問正解者ほとんどいません…

・不定期ですがコラムも書いています

・サーバーサイドはRubyonRailsを使用

http://www.coffre.me/

・デザインにもこだわってみました(特にグラフ)

・実はちょっとバグがあります。

・問題は今後追加予定(現在110問収録)

個人的にはなりますが、他にもアプリ・Webサービスなど開発中です(2016年も宜しくお願いします)

・サイト等は次回のアップデートで公開予定

http://blog.just1factory.net/services/284

・若干の中毒性を含みます

カレンダーが好きでライブラリを作りました

日本の祝祭日を計算で出してくれる

・カレンダーアプリ等での活用を想定

・シルバーウィーク・ゴールデンウィークも対応

・ハッピーマンデー法の施行も対応

・春分の日・秋分の日にも対応

・過去の祝祭日もおおむね考慮はしている

構想や基本実装は僕ですが、他に3名のContributorのお力添えがあり実運用できるレベルになりました!

職人の手作業で計算しております!

・HTTP(HTTPS)通信は不要

★CalculateCalendarLogicver0.0.2

【だが本業は今もサーバーサイドです】PHP(メインのframeworkはSymfony)&Ruby(Sinatraに近いもの?)

・Github:https://github.com/fumiyasac/handMadeCalendarAdvance

・実装解説:http://qiita.com/fumiyasac@github/items/33bfc07ad36dfffcdf8f

・Github:https://github.com/fumiyasac/handMadeCalendarOfSwift

✦ 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)

✦ 今回のサンプルで実装時に留意する点は下記の4つ

ハマりやすいポイント

今回は特にUIまわりのライブラリを使うことなく、UIKitのみで実装することができる表現になりました。

★UITableViewCellから画面遷移をさせる部分の処理

★配置したUIImageViewの幅・高さを変更する

★Y座標の位置が動的に変わるUIImageViewの上にボタンを配置する

★UICollectionViewをスクロールした際に中央に持っていきたい

解決方法:

そのままUITableViewCell内に設置したGestureRecognizerのアクションやボタンアクションではNG

解決方法:

そのままconstantプロパティに計算で出した幅・高さを代入するとAutoLayoutの警告が出る

今回はサムネイル画像用のUIImageViewの高さが動的に変わるので工夫が必要

スクロールの終了位置をスクロールの速さを予測して動かすような処理等の記載も合わせて必要

クロージャーを利用して処理の中身はUITableViewの値設定時に追加する

幅・高さの制約の優先度(priorityプロパティ)を1000→750へ変更する

解決方法: Interface上でボタンの大きさと位置を6枚目の画像の大きさに合わせるように設定

解決方法: UICollectionViewFlowLayoutクラスを継承したクラスを作成し該当メソッドをOverride

✦ 詳細解説に関しても下記の文章にまとめていますので併せてどうぞ

今回のサンプルと詳細解説はこちら

画像サムネイルの配置に関する実装コードに関しましては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リストの簡易サンプル

✦ 全体的なレイアウトの初期配置に関してはInterfaceBuilderで行う

全体的なセルの配置に関して

今回ではテキスト部分と画像サムネイルの高さは動的に変化する部分なので注意して制約をつけること。

1.6枚のUIImageViewへの制約設定のポイント

それぞれの配置に関する設定に関してはコードで変更がないものに関してはInterfaceBuilder上で配置する。

★ポイントその1:

それぞれのUIImageViewの隙間

に関する制約と幅・高さの制約

をつける。

(優先度やサイズはコードで)

★ポイントその2:

InterfaceBuilderを使用して、

下記設定にも同様に設定を行う

・UIImageViewの番号識別tag値

・ContentMode=AspectFill

・CliptoBoundsにチェック

※各マージンの制約についてはQiitaやサンプルコードを参照。

2.6枚目のUIImageViewの上に重ねるボタン配置

コードでの処理とInterfaceBuilderでの設定をうまく使い分けることでコードの節約と見通しをよくする。

★ポイントその1:

このボタンの大きさを6枚目の

UIImageViewの大きさと合わせて

その後に「Ctrl+ドラッグ」を

6枚目のUIImageViewに向けて制約

を設定する。

「6枚目のUIImageViewと合わせる」

・水平&垂直方向中心位置

・幅&高さ

★ポイントその2:

6枚目以降の数を表示するラベル

は任意の大きさでその後に「Ctrl

+ドラッグ」をボタンに向けて

制約を設定する。

「ボタンと合わせる」

・水平&垂直方向中心位置

★UIImageViewの幅や高さ・優先度に関する設定

★マージンに関する制約や識別用タグの値等の設定

IBoutletでコード上に切り出しておき、それぞれの値はコードで処理

今回変更する必要がないものに関してはInterfacebuilder上で済ませる

3.XCode上(InterfaceBuilder)での見え方について

任意のUI要素について、ある要素と位置や大きさを合わせるような場合は配置の仕方や設定に注意すること。

★ポイントその1:

このボタンの大きさを6枚目の

UIImageViewの大きさと合わせて

その後に「Ctrl+ドラッグ」を

6枚目のUIImageViewに向けて制約

を設定する。

「6枚目のUIImageViewと合わせる」

・水平&垂直方向中心位置

・幅&高さ

★ポイントその2:

6枚目以降の数を表示するラベル

は任意の大きさでその後に「Ctrl

+ドラッグ」をボタンに向けて

制約を設定する。

「ボタンと合わせる」

・水平&垂直方向中心位置

✦ コードを使って幅と高さの計算を行う&優先度を下げておく

サムネイル画像の枚数に合わせてレイアウトを決めるのは、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で

発動する処理との関連付けを行う

✦ クロージャー変数をUITableViewのクラスに用意して処理をする

クロージャーを用いて「処理の実態:表示側」に「処理のトリガー:セル側」に分けてつなぐイメージです。

★セル側に定義するクロージャー変数:vartransitionClosure:((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) }

✦ よく見るアプリのUIは真似をしてみると面白い部分がたくさん

今回のまとめ

ご清聴ありがとうございました!またこのような機会があった際には是非ともよろしくお願い致します!

★ライブラリを用いなくともUIKitのみで実現できるケースも多い

クラスの継承やオーバーライド・UIKitの組み合わせ方を工夫することで実現可能な場合も多い

★AutoLayoutの扱いやInterfaceBuilderでの設定とコードでの設定の使い分け

今回の例では「値に応じて変化がない部分」についてはInterfaceBuilderで設定しそれ以外はコードで

★よくあるアプリのUIは結構応用できる局面も多いかもしれない

要素ごとに細かく分けて「どのパーツを組み合わせているか?」の仮説を立てる

★自分ルール

【良いアウトプットのために】

発表・登壇時はこの中のいずれか2つを

絶対に準備するルールを設けています!