Kazoo04Day 5

UITableViewの軽量化

More than 3 years have passed since last update.

くろてい君がadvent calendarを作ったので、デザインと密接に関わる軽量化とUIUXに関する記事を書きます。

Kazoo04アドベントカレンダーなのにKazoo04関係無かったわ…。


はじめに

UItableViewの軽量化を目的としたエントリです。

ここでの軽量化の定義は数値的に処理速度を高速化することはもちろん、感覚的にストレスを感じさせないUIUXの部分も含みます。

何故ならUITableViewの軽量化は、ユーザーが感じるストレスを軽減することを目的としているからです。

オカルト要素もあるので、変な所があればツッコんでください(汗


大前提

軽量化は基本的に


hoge.m

-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;

の2つのメソッドの処理を軽くすることで実現します。

セルの生成とレイアウト決定の処理が終了して初めて表示されるためです。


半透明の要素をなくす

当然半透明にすると計算量が増え、スクロールがもたつきます。

透過度のあるパーツは本当に必要かどうか吟味しましょう。


UIImageView/UIlabelを使わない

UILabelに限らず、addsubViewするのは大きなコストになります。

特にUIImageViewはハイコストになるため本当に必要か吟味しましょう。

ではどうすればいいのかといえばdrawrectを使います。

文字に関してはdrawWithRect

画像に関してはUIBezierPath

でviewに直接描き込みます。

ただしUIの描画はメインスレッドで実行されるのでUIImageをわざわざdrawrectするのはあまり効率的ではありません。

たとえばチェックマークや鍵マーク等簡単なアイコンであればdrawrectで描く方が良いということです。

paintCode等を使うと簡単にアイコンを描くことが出来ます。


非同期読み込み

UITableViewで外部から画像を読み込むというのはよくあることですが、同期的に画像を読み込むと当然重くなります。

SDWebCache等で非同期読み込みすると簡単でいい感じです。

また、画像キャッシュも必要か吟味したりその画像が本当にサーバーから取得する必要があるのか、別のタイミングでダウンロード出来ないかなども意識すると良いと思います。


透過画像の利用

先述した非同期読み込みの後に別スレッドで透過を無くせばより軽快なスクロールが実現できます。


配置やサイズのピクセルを整数に保つ

例えば120px*120pxの画像を60px*60pxのUIImageViewで表示するとアンチエイリアス処理がかかります。

最初から60px*60pxにしていればこの処理は行われないので、処理が軽くなると言えます。

同様に境界線やビューのboundsにおいても同様のことが言え、例えば0.5pxの境界線は画面上の1pxで描画することが出来ないためにアンチエイリアス処理が発生します。


セルのリサイクル

dequeueReusableCellWithIdentifier:を利用することでセルがリサイクルでき、生成のコストが大幅にカットされます。

しかし、このリサイクル機構は開発するアプリケーション(特にセルのサイズがバラバラだったり、条件によってビューの内容レイアウトが大幅に変わるもの)によってはバグの温床になりがちです。

特にリサイクル後のaddSubViewではスクロールごとに新しいビューが貼られてメモリを圧迫してしまうバグ等を起こしやすいので、利用にはある程度の知識が必要と思われます。


UIScrollViewDecelerationRate

UIScrollViewのプロパティ、UIScrollViewDecelerationRateはスクロールの慣性を設定する値です。

UIScrollViewDecelerationRateNormal

UIScrollViewDecelerationRateFast

の2つが用意されており、それぞれNormalが0.998Fastが0.99となっています。

この値は小さい程スクロールにブレーキがかかるようになるためこの値を0.999以上にすることでスクロールを高速化することが出来(るような気がします)ます

http://stackoverflow.com/a/8711525

こちらで検証されていますが、固定値になるようです。

Normalを選択してください。


Blocks内のselfについて

UITableViewCellに限ったことではありませんが、UITableViewcellでは特に生成とリリースの回数が多く、Blocks内でselfを使っていたりしていつまでも開放されないとスクロールするほどメモリが圧迫されて動きが重くなっていきます。

blocks内でselfを使うときは__block装飾をして使いましょう


contentViewを外す

UITableViewCellを継承し、contentViewにremoveFromSuperViewをかければUITableViewCellのViewを1つ減らすことが出来ます。


右側16px程には何も置かない

スクロールの際のインジケータは半透明なのでスクロールの際に計算が発生します。インジケータの下に何も置かない事で計算量が節約出来るはずです。


Swiftを使う

計算処理においてSwiftはObjective-Cよりも早い速度で計算されるため、


-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;

などはSwiftで書くといいかもしれません。

同様にObjective-C++(.mを.mmにするとC++の関数が使えるようになります)で計算処理等を書くと書き方によっては高速化出来るかもしれません。


高さを事前に計算する

高さがまちまちなCellを生成する際には

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;

で高さ計算をするのではなく、例えば通信が完了した時点等で事前に計算すると格段にスクロールが改善されます。estimateでおおよその高さを決め打ちしても良いですが、ユーザーがコンテンツを閲覧している最中にセルの高さが変わるため、出来れば使わない方が良いと思います。


不要なdelegateは実装しない

UItableViewDelegateはもちろん、UIScrollViewのdelegate等、余分なdelegateは実装しない方が良いです。

特にスクロール毎に毎フレーム発生するようなdelegateでは何かをするにも大きなコストになります。本当に必要かどうか、また必要であっても引き下げ更新等条件次第では計算処理をしなくていい位置がある場合はスキップさせるなど工夫をすると良いと思います。


coreGraphicsの利用をやめる

角丸やボーダー等の実装にcoreGraphicsを使うと計算処理がうんと増えます。


hoge.m

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;


内で使うのは非常によくありません。どうしても使いたい場合はdrawrect等で角丸を実装したりするといいかもしれないです。


アニメーション

アニメーション効果はユーザー体験に大きな影響を与えます。

UITableViewにおいてはUITableViewCellそのものの表示やcontentView内のビュー、またタップ時などのインタラクションにアニメーションを採用することでユーザー体験をより良くすることが出来ます。


UITableViewCellの表示アニメーション

UITableViewCellの表示時のアニメーションはユーザーの視線を意識する必要があります。

特にalpha値を扱うフェードインに関しては透過率が0になるまでユーザーはコンテンツを見ることができない程度の極端な設計をするべきです。

透過率のあるビューを見ることは視界が妨げられている事と変わりません。よってストレスに繋がり、UITableViewで表示するコンテンツが多いほどそれは比例的に増えていきます。

ここでの適したアニメーションはページめくりを意識したアニメーションにすることです。またアニメーションは画面の下44px程度で完結するようにするべきです。

ページめくりを意識したアニメーションとは、実際のページめくりをシミュレートするというわけではありません。あくまでコンテンツを見る部分で無い範囲で無意識的に次のプレビューを見せる事を指します。


contentView内のアニメーション

contentView内のアニメーションは頻繁に行う必要はありません。

頻繁に行うアニメーションはUITableViewCellの表示で実装するべきでしょう。

なぜならcontentViewはユーザーに直接情報を伝える部品であり、アニメーションはその情報伝達を妨げる為です。

ではどのような箇所にアニメーションを挿入すれば良いかというと、画像のロード終了時等の情報の更新時です。

情報の更新時にアニメーションを与えることで無意識的にそれを伝えることが出来ます。

ここでのアニメーションはフェードイン・アウト程度に止め、派手に演出する必要はありません。


インタラクションのアニメーション

インタラクションのアニメーションは重要な要素です

UITableViewはタップするだけのUIButtonと違い、ダブルタップやスワイプ等複数のアクションの対象となります。

よってユーザーのアクションに対してUITableViewが正しくアクションを受け付けたかどうかを伝える必要があります。

タップに関しては水の波紋アニメーションや音、ヴァイブレーションによってフィードバックするとユーザーに伝わりやすいかと思います。

ここでのアニメーションのコツはなるべく現実に存在する動きにすることです。

水の波紋はその最たる例で、実際に水に指を浸すと同様の効果を得ることができます。

スワイプに関しては実際にCellを横にスライドしてメニューを出すMail.app風のアクションも良いでしょう。これも実際に紙を横に弾いた動きと同様です。


特別なアニメーション

アクセントとして派手なアニメーションをアプリに入れるとユーザー体験を設計することが出来ます。

ダイナミックなアニメーションはユーザーが頻繁に触りたくなるので、更新回数を増やしたければpull to refreshの部分に、多くのスクロールをさせたければスクロール部分に挿入することでユーザー体験を設計することが出来ます。

明日(6日)は@karno 氏です!