画像で魅せる開発現場について
2016年5月20日
Akitsugu Tamagawa
自己紹介
玉川 晃嗣
・Web系SIer出身
・社会人4年目
・2015年11月入社
【好きなこと】
・ロックバンドのライブへ行く
・コーヒーを淹れること
アプリ紹介
ジョインするまで
・アプリは世に出ていた
・グッドデザイン賞を受賞してた
・バグの監視するものがなかった
ジョインしてから
・Fabricの導入
・検索結果の追加機能(さらに絞り込み機能)
・画像読み込みの高速化
・UIの改善(リニューアル)
・ASOの最適化
今回のタイトル
画像で魅せる開発現場について
2016年5月20日
Akitsugu Tamagawa
画像で魅せるとは
ジョインしてから
・Fabricの導入
・検索結果の追加機能(さらに絞り込み機能)
・画像読み込みの高速化
・UIの改善(リニューアル)
・ASOの最適化
ジョインしてから
・Fabricの導入
・検索結果の追加機能(さらに絞り込み機能)
・画像読み込みの高速化
・UIの改善(リニューアル)
・ASOの最適化
画像読み込みの高速化とは
Demo動画
画像の読み込み
・読み込むスピードとスクロールのスピードに
追いついていない
・画像が表示されるまでの時間があるので
待たされている気分になる
Demo動画
なぜ遅かったのか
画像の読み込みが遅い原因
・表示する画像のサイズが大きい(WebP未使用)
・スクロール途中にcellの再利用が始まり
モタついてしまう
アプリで最適化するために
SDWebImageを使い倒す
SDWebImageを使う
SDWebImageManager *imageManager = [SDWebImageManager sharedManager];
[imageManager.imageCache queryDiskCacheForKey:url
done:^(UIImage *image, SDImageCacheType cacheType) {
if (image) {
self.image = image;
} else {
[self sd_setImageWithURL:url
placeholderImage:placeholderImage
options:1
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (!error) {
self.image = image;
} else {
self.image = placeholderImage;
}
}];
}}];
スクロールのスピードに
表示が追いつかない
やりたいこと
・画像を事前読み込んで表示までの時間を短縮したい
・ユーザーのストレスにならない動作を提供したい
ポイントは2つ
・画像を事前読み込みを行う
・UICollectionViewCellのリユースを使う
Point 1
画像を事前読み込み
SDWebImagePrefetcher.hを使う
Demo
配列で表示したい画像を渡すことで
読み込みを高速化させることができる
使ってみるには
#import <SDWebImage/SDWebImagePrefetcher.h>
SDWebImagePrefetcher.h
[Objective-C]
[[SDWebImagePrefetcher sharedImagePrefetcher] cancelPrefetching];
[[SDWebImagePrefetcher sharedImagePrefetcher] prefetchURLs:images];
[Swift]
SDWebImagePrefetcher.sharedImagePrefetcher().cancelPrefetching()
SDWebImagePrefetcher.sharedImagePrefetcher().prefetchURLs(images)
活用例:Objective-C
[Objective-C]
- (void)prefetchImages:(NSArray*)images {
[[SDWebImagePrefetcher sharedImagePrefetcher] cancelPrefetching];
[[SDWebImagePrefetcher sharedImagePrefetcher] prefetchURLs:images];
}
活用例:Swift
[Swift]
func prefetchImages(images:[String]) {
SDWebImagePrefetcher.sharedImagePrefetcher().cancelPrefetching()
SDWebImagePrefetcher.sharedImagePrefetcher().prefetchURLs(images)
}
Point 2
UICollectionViewCellの
リユースを使う
UICollectionViewのリユース
[Objective-C]
- (void)prepareForReuse
[Swift]
override func prepareForReuse()
UICollectionViewのリユース
[Objective-C]
- (void)prepareForReuse {
[super prepareForReuse];
[self.imageView sd_cancelCurrentImageLoad];
}
UICollectionViewのリユース
[Swift]
override func prepareForReuse() {
[super prepareForReuse];
imageView.sd_cancelCurrentImageLoad()
}
画像は大きいままでも、
読み込みを早くすることに成功
次に
ジョインしてから
・Fabricの導入
・検索結果の追加機能(さらに絞り込み機能)
・画像読み込みの高速化
・UIの改善(リニューアル)
・ASOの最適化
UIの改善(リニューアル)
スペースマーケットのアプリは
結構複雑です
Demo動画
一画面全て
UICollectionViewで表示
sectionによって
UICollectionViewCellを
使い分ける
スペース情報
オーナー情報
マップ情報
スペースの写真
etc..
UICollectionViewを
使い倒してます
今回のリニューアルでの
大きな変更点
縦スクロールと横スクロールが
可能なUICollectionView
Demo動画
カードっぽいやつ
しかも、左右チラ見せ
マージンを設定するには
前のCell 次のCell
表示するCellは
常に中央に表示したい
UICollectionViewFlowLayout
の出番です
UICollectionViewFlowLayoutとは
・UICollectionViewLayoutを継承した、
レイアウト処理コンポーネントの実装クラス
・各要素をタイル状に並べるレイアウトが可能
横スクロールが可能な
UICollectionViewについての
Tipsを少しだけ
CollectionViewFlowLayoutを紐付ける
マージンを設定するには
section.left
minimumLineSpacing
itemSize
awakeFromNibでCellのサイズ指定
- (void)awakeFromNib {
[super awakeFromNib];
self.itemSize = CGSizeMake(“CellのWidth”, “CellのHeight”);
self.minimumLineSpacing = 10;
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
CGFloat horizontalInset = ([UIScreen mainScreen].bounds.size.width - width) / 2;
self.sectionInset = UIEdgeInsetsMake(0, horizontalInset, 0, horizontalInset);
}
※section.topとsection.buttonの値は0
これだけでは
中央に表示する要件を満たさない
次に
スクロールした時に
中央へ表示させる
UICollectionViewFlowLayout
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
withScrollingVelocity:(CGPoint)velocity
{
CGFloat offsetAdjustment = MAXFLOAT;
CGFloat horizontalOffset = proposedContentOffset.x + (self.collectionView.frame.size.width - width) / 2;
CGRect targetRect = CGRectMake(proposedContentOffset.x,
0,
self.collectionView.bounds.size.width,
self.collectionView.bounds.size.height);
NSArray *array = [super layoutAttributesForElementsInRect:targetRect];
for (UICollectionViewLayoutAttributes *layoutAttributes in array) {
CGFloat itemOffset = layoutAttributes.frame.origin.x;
if (ABS(itemOffset - horizontalOffset) < ABS(offsetAdjustment)) {
offsetAdjustment = itemOffset - horizontalOffset;
}
}
return CGPointMake(proposedContentOffset.x + offsetAdjustment, proposedContentOffset.y);
}
手を離した瞬間に計算する
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
withScrollingVelocity:(CGPoint)velocity
proposedContentOffset:スクロールの速さから
推測される停止位置
Velocity:1秒あたりの移動距離
return:proposedContentOffsetの代わりのcontentOffset
まとめ
まとめ
・ライブラリの中身をじっくり中身を見て
使ってみるとめっちゃ楽しい
・効率を気にし過ぎると時間が足りなくなるので
取捨選択をすることが大事
・待ち時間の無いアプリを提供していきましょう
ご静聴ありがとうございました

20160520_ios

Editor's Notes

  • #2 こんばんは、
  • #4 初めまして、スペースマーケットエンジニアの玉川と申します キャリアとしては新卒でWeb系SIerの会社に入り、2年半ほど働いて2015年の11月にスペースマーケットにジョインしました。社会人4年目です。
  • #5 では、本題に戻りまして。スペースマーケットはWebだけではなくアプリも存在します
  • #6 こちらですね。iOSのアプリのみ現在提供しております。iOSアプリなのですが、WatchやiPadにも対応しております。
  • #7 少し時間をさかのぼりまして、私がジョインする前2015年11月までのアプリの状況というのは、「アプリは世に出ていた」「グッドデザイン賞を受賞してた」「バグの監視するものがなかった」といった状況でした
  • #8 そして、私がジョインしてから2015年11月以降に私がやったこととしては、「Fabricの導入」「検索結果の追加機能実装」「画像読み込みの高速化」「UIの改善(リニューアル)」「ASOの最適化」等々大きめの改修から、雑用まで行いました。
  • #10 「画像で魅せる開発現場について」とありますが、
  • #11 画像で魅せるとは何だということで、
  • #12 本日は先ほど述べた「画像読み込みの高速化」「UIの改善」の2点を中心に 今日から使えて覚えておくと便利なことや、実際にやってみたことをお話しようと思います。
  • #13 まず、画像読み込みの高速化についてお話しします
  • #14 画像読み込みの高速化とは何なのかといいますと、
  • #15 まずこちらのムービーを見て欲しいのですが、 これは少し前のアプリのトップ画面をムービーにしたのですが、見て分かる通り
  • #16 なぜ読み込みがこんなにも遅いのかというと、「読み込むスピードとスクロールのスピードに追いついていない」という実装上の問題点と「画像が表示されるまでの時間があるので待たされている気分になる」のユーザーの感じ方の問題の2点があります。それが、画像の読み込みの高速化を行うことによって、、
  • #17 まずこちらのムービーを見て欲しいのですが、 これは少し前のアプリのトップ画面をムービーにしたのですが、見て分かる通り
  • #19 現在、スペースマーケットのアプリで使用している画像というのはサイズがとても大きいのですが、WebPなどを使っていないためリサイズして表示しています。さらに、スクロールを制御していなかったので読み込みとスクロールがぶつかってしまうという現象が起きていました。
  • #20 そこで、アプリならではのやり方といいますか、最適化するために
  • #21 ライブラリであるSDWebImageを使い倒すことにしました。SDWebImageといえばWeb画像ローダーや画像キャッシュ等で有名なライブラリです。
  • #22 通常ではキャッシュにイメージがあればそれを使い、エラーが起きた時にはダミーデータを使うという一般的なメソッドなのですが、
  • #23 このキャッシュがない時に表示する画像が大きくて表示までに遅くなってしまうということがありました。
  • #24 私がやりたいこととしては2点のみです。「画像を事前読み込んで表示までの時間を短縮したい」そして、「ユーザーのストレスにならない動作を提供したい」この2点です。では、実際にこの2つを網羅するために何をやったのかといいますと
  • #25 ポイントは2つです。「画像を事前読み込みを行う」「UICollectionViewCellのリユースを使う」の2つです。かなり簡単なことでした。明日から使えるかもしれませんが、わかりません。
  • #26 まず1つ目
  • #27 画像を事前読み込み。とは何だ
  • #28 SDWebImagePrefetcherというSDWebImageのライブラリにあるクラスを使います。この中に事前読み込みのメソッドが入ってます。
  • #29 ちょっとデモを見てみてください
  • #30 ポイントとしては、表示したい画像の配列をSDWebImageのメソッドに渡すことによって画像をダウンロードし、表示のラグを減らすことが出来るということでした。
  • #31 実際に使ってみる際には、ものすごく簡単です、
  • #32 SDWebImageはObjective-Cのライブラリなので、 objective-cではheader.hに、SwiftではBridge-Header.hに追記します
  • #33 記載のように、SDWebImagePrefetcherのメソッドを読んで、一旦キャンセルして、読み込みたい配列を渡してあげるだけです。この書き方だけではなく、blockでダウンロードが終わった情報も受け取るメソッドも用意されています
  • #34 簡単な活用例としましては、記載の感じで終わります。 ただAPIから取得したImageのURLを配列にして渡したいという時は通信成功時に配列を作って渡してあげるといいかもしれません。
  • #35 Swiftの場合でも同様ですね。これだけではパッとしないのですが、こんな感じいけます。やっぱりSwiftはいいですね。見やすいですね。で、これで事前読み込みが出来たので高速化できたかなと思うのですが、まだです。
  • #36 もう一つのポイントを覚えていますでしょうか
  • #37 collectionViewCellの内容を構築している時に、同じ画像が表示されてしまうという現象が起きるかもしれません。その時に有効なのがリユースです。UICollectionViewCell,またはUITableViewCellでもいいのですが、リユースメソッドが提供されているので使っていきます。
  • #38 ご存知の方も多いと思いますが、 UICollectionViewCellやUITableViewCellにはprepareForReuseというメソッドが用意されています。これを使います
  • #39 画像が読み込んでいる状態であれば、そのロードをキャンセルします。もしくはimageViewをnilにしてしまうというのも一つの手段かなとも思います。これでCellの画像が違うという問題は解消されます。
  • #40 Swiftも同様ですね。いいですね。やっぱり
  • #41 今ご紹介した2つのポイントを使うことによって画像が大きいままでも読み込みを早くすることに成功しました。けどやっぱり通信環境とかを考えるとWebPとかを使って適材適所なやり方を今後やっていこうかなとひっそり考えております。
  • #43 UIの改善ということで、4月にアプリのリニューアルを行ったのですが、そのことについて少しお話ししたいなと思います。
  • #45 アプリを見ていただいた方ならばご存知かと思いますが、スペースマーケットのアプリは結構複雑です。パワープレイしている部分もあります。
  • #46 まずこちらのムービーを見て欲しいのですが、 これは少し前のアプリのトップ画面をムービーにしたのですが、見て分かる通り
  • #47 実は、これ全部CollectionViewです。まぁ当たり前といえば当たり前ですよね。
  • #48 表示したいことが多いのでSectionやHeader、Footerによって、表示の使い分けを行っております。例えば
  • #49 こちらはスペースの情報を表示するCellだったり
  • #50 こちらはスペースの管理を行うオーナーの情報
  • #51 スペースの場所を表す、マップ画像もImageに変換して表示しています
  • #52 こちらはスペースの写真を3枚だけ見せて、「すべての写真を見る」で全て見ることができます
  • #53 などなどこれだけではなく、いろいろな用途に分けて使い分けております。 つまり、
  • #55 そんなUICollectionViewを使い倒しているアプリなのに、4月に大幅なアップデートを行いました
  • #57 まずこちらのムービーを見て欲しいのですが、 これは少し前のアプリのトップ画面をムービーにしたのですが、見て分かる通り
  • #59 横にスクロールが出来ますよというのを分からせるUIを実現したいというデザイナーさんの要望もあります
  • #60 左右のCellをチラ見せというのは、横にスクロールが出来ますよというのを分からせるUIを実現したいというデザイナーさんの要望があり、画面いっぱいにCellを表示するのではなく、画面サイズから決められた、「前のCell」情報と「次のCell」情報を表示したいという要望もありましたし、さらには
  • #61 表示するcellは常に中央に表示したい。ページングしたいと言った要望がありました。軽く頭抱えました。頭抱えたという原因が
  • #62 UICollectionViewFlowLayoutを使う出番がやってきたからです。正直いうと、これとても苦手というかよく分からないなぁっていうのが印象です。
  • #63 UICollectionViewFlowLayoutについてですが、簡単に言うとCollectionViewのレイアウト処理を行うことができるクラスです。サブクラスを作ってオーバーライドしてしまえば、いろいろなレイアウトに対応できます。苦手ですが
  • #64 そこで、横スクロールが可能なUICollectionViewについてのTipsを少しだけお話ししたいと思います。
  • #65 まず横スクロールできるCellを作るためにXibファイルにCellを置いて、全面にCollectionViewを配置して、さらにCustomCollectionViewFlowLayoutのクラス定義します。
  • #66 UICollectionViewFlowLayoutクラスで何が定義できるのかというと、CollectionViewに表示するCellの位置を決められます。itemSizeでCellの全体のサイズを決めて、画面X座標0からの距離がSection.left、CellとCellの間のスペースをminimumLineSpacingとします。他にもsection.top,section.bottomがあるのですが割愛します
  • #67 次に、CustomFlowLayoutのファイルにコードを書いていきます。awakeFromNibクラスにCellの情報を書いていきます。itemSizeやminimumLineSpacing,さらにどちらにスクロールさせるのかを決めた後に、Cellの外枠であるSectionInsetを決めます。
  • #70 一番の鬼門だと思っていたCellの中央表示についてです。
  • #71 Objective-Cのコードでゴメンなさい。ユーザがスクロールして手を離した瞬間にこのメソッドが呼ばれます。そして、どの位置にCellを表示するのかということを計算してくれます。メソッドの中では、画面のサイズ・Cellのサイズをベースにして、どの位置で停止すればいいかということを計算します。
  • #72 メソッド内を簡単に説明すると手を離した瞬間にスクロールの速さから推測される停止位置が引数で入ってきて、さらに1秒あたりの移動距離も入ってくるというスピード狂のようなメソッドです。デバッグが結構大変でした それを元に、returnでスクロールの停止位置を計算し、表示するということをすれば中央に表示することができます。
  • #74 SDWebImageについては、ライブラリの仕様書とかコメントアウトを見るといろいろ構想が浮かび実装方法がいくつか出てくるので読んでみると楽しい気分になれます。 ただ画像などUIにまつわるものを極めようとするとほぼ100%の確率で時間が無くなるので取捨選択は大事です。納期も大事。 アプリ開発者だと、まぁこれでいっかーっていう妥協点を無くすことが僕の目標なのでユーザにストレスを与える機能は改善していきたいと思います