iOSアプリデザインリニューアルの舞台裏

モバイルファースト室の @slightair です。

先ほど、デザインをリニューアルしたクックパッドiOSアプリ 6.0.0 をリリースしました。 https://itunes.apple.com/jp/app/kukkupaddo-no.1reshipi-jian/id340368403?mt=8

この記事では、どのようにして新しいデザインをiOSアプリに適用していったのかを紹介したいと思います。

新しいアプリの画面

スクリーンショットを見ていただければわかるように、全体的にフラットな印象を与える画面に変わりました。

トップ

レシピ詳細画面

サイドメニュー

この記事で全ての画面を紹介することはできませんが、ぜひダウンロードしてお手持ちのiOS端末で触ってみてください。

新デザインの適用

基本的には、画面デザイン案をもらい、既存のアプリを修正して少しずつ適用していく形で進めていきました。 GitHub Enterpriseを利用しているので修正点ごとにissueを作り、デザイン適用、確認、調整を繰り返して進みました。 プルリクエストをマージするたびに、デザインの責任者がTestFlight経由で新しいアプリをインストールして試し、問題がなくなればissueをクローズしていきます。 新しい問題が見つかれば、それをどんどんissueに追加していったので、画面リニューアルの作業中はissueの数が激しく増減しましたが次第に0に収束していきます。

画面数もそれなりにあったり、機能の開発時期によって実装の仕方が違ったり(Storyboard/xib の利用有無など)するなど、地味ながらとても大変な作業でした。

アプリケーションの検証

デザインを置き換えるだけといっても、修正は確実に行っているわけで、その作業によってエンバグしてしまう可能性がありますよね? デザイン面でのチェックだけでなく、テストエンジニアが画面の動作が変わってしまっていないか、古いデザインがあたったままの画面がないかなど様々な視点から修正したアプリケーションの確認を行いました。 修正期間中は毎日夜間にアプリケーションの全画面のスクリーンショットを記録するスクリプトを実行し、画面崩れが起きてないか、新デザイン未反映の画面はないか、進捗状況の確認に利用していました。

CookpadUI

新しいデザインを画面に適用する際に、作業を簡略化するため CookpadUI というプライベートなライブラリを作りました。 このライブラリには、よく使う色やフォントが定義されていたりシンボルフォントを元に画像を生成する機能などがあります。

クックパッドではクックパッドアプリ以外のアプリも作っており、今後も増えていくので、将来的には色々なアプリからこのライブラリを利用してもらえるように考えて作っています。 ライブラリという形で切り分けておくと、クックパッドらしさや空気感のようなものをアプリ間で統一するのに有効であると考えています。

色とフォント

アプリの中で使用する色とフォントはある程度決まっているので、UIColor と UIFont のカテゴリを定義して CookpadUI に含めています。 画面の様々な部分で細かく指定してしまうと、今回のような大規模な変更の時に困ってしまいますし、画面によって似たような別の色を指定してしまってアプリ内での統一が崩れてしまうようなことがあります。 色やフォントに意味のある名前をつけて用意しておくことで、新しい機能・画面を追加するときにもこの色・フォントを使おうと選択することができます。 当たり前のことといえば当たり前のことなんですが、この機会に整理してみました。

ただ、この方法には課題があります。 それは Storyboard/xib のような Interface Builder を使うものと相性が悪いことです。 クックパッドアプリはコードで画面レイアウトを定義している部分が多く、そのような場所ではうまく指定できましたが、Storyboard/xibではどうしたらよいか悩んでいます。 awakeFromNib: などでコードで指定することもできるでしょうが、InterfaceBuilderの良さが生かせなくなってしまいます。 そのため、InterfaceBuilder を使うものでは直接指定しています。 InterfaceBuilderで色・フォントを指定する際にコード中の候補から選べるようなものがあればよいのですが… なにかよい方法があったら教えて欲しいです。

ちなみに、フォントに関しては Dynamic Type がありますが、クックパッドアプリは iOS6 のサポートも続けているので使っていません。

UIColor と UIFont のカテゴリには一長一短ありそうですが、色やフォント指定に名前をつけ、デザイン担当者とのコミュニケーションツールとするのはかなり有効でした。

シンボルフォント

クックパッドでは独自のシンボルフォントを作っていて、デザインツールとして利用しています。 これをiOSアプリから自由に利用できるようにして今回のバージョンから利用しています。

FontAwesomeKit というライブラリがあり、これに乗っかる形で実装しています。 このライブラリを使うと、シンボルフォントから自由な色、サイズのUIImageインスタンスを得ることができます。 独自のシンボルフォントを使う方法は、上記ページの Using Custom Icon Font の節に書いてあります。

この仕組みを使うと、わざわざ画像を場面や端末サイズにあわせていくつも用意する必要がなくなります。 画像を作成する負担が減るだけでなく、アプリのリソースを削減や画面のプロトタイプの作成にも利用できて良いこと尽くめです。

例えば、クックパッドのロゴ画像はこのように書くことで生成できます。

CUICookpadSymbols *symbol = [CUICookpadSymbols cookpadIconWithSize:64];
[symbol addAttribute:NSForegroundColorAttributeName value:[UIColor cui_grayColor]];
UIImage *cookpadImage = [symbol imageWithSize:CGSizeMake(64, 64)];

簡単な画像の作成

デザインを変更したことでシンプルな画面要素が増えました。 このような画面要素がたくさん出てくるので、わざわざ画像を用意しなくても済むように、ボタンの背景色や画像に被せるバッヂをコードで生成できるようにしました。 単純に UIBezierPath で描画したものを UIGraphicsGetImageFromCurrentImageContext 関数で UIImage にするものです。 色と矩形を与えたらUIImageのインスタンスを作るメソッドを用意するだけで、あちこちで便利に使えるようになりました。

画像にある4色のボタンはUIButtonのサブクラスで定義されています。 中では以下のようにして画像を生成して setBackgroundImage:forState: で指定しています。

...
UIColor *buttonColor      = [UIColor cui_greenColor];
UIColor *highlightedColor = [[UIColor cui_greenColor] cui_colorForHeavyHighlight];
UIColor *disabledColor    = [UIColor cui_greenColorWithAlpha:0.5];

[self setBackgroundImage:[self backgroundImageWithColor:buttonColor]      forState:UIControlStateNormal];
[self setBackgroundImage:[self backgroundImageWithColor:highlightedColor] forState:UIControlStateHighlighted];
[self setBackgroundImage:[self backgroundImageWithColor:disabledColor]    forState:UIControlStateDisabled];
...

- (UIImage *)backgroundImageWithColor:(UIColor *)color
{
    CGRect rect = CGRectMake(0, 0, CUIButtonCornerRadius * 2 + 1, CUIButtonCornerRadius * 2 + 1);
    UIEdgeInsets capInsets = UIEdgeInsetsMake(CUIButtonCornerRadius, CUIButtonCornerRadius, CUIButtonCornerRadius, CUIButtonCornerRadius);

    return [[UIImage cui_imageWithColor:color roundedRect:rect cornerRadius:CUIButtonCornerRadius] resizableImageWithCapInsets:capInsets];
}

左上と右下だけが丸い緑のおすすめバッヂはこのように書くことで生成できます。

UIImage *badgeImage = [UIImage cui_badgeImageWithText:@"おすすめ"
                                      backgroundColor:[UIColor cui_greenColor]
                                          roundedRect:CGRectMake(0, 0, 48, 18)
                                    byRoundingCorners:UIRectCornerTopLeft | UIRectCornerBottomRight
                                          cornerRadii:CGSizeMake(3.0, 3.0)];

まとめ

クックパッドiOSアプリのデザインリニューアルの裏側をほんの少しではありますが、紹介しました。 クックパッドでのアプリ開発の流れや空気みたいなものが伝われば幸いです。 今回はデザインリニューアル時の話になりましたが、クックパッドではよりよいアプリ開発の進め方をずっと考えています。 参考になった!とかもっと良い方法があるよ!という風にネイティブアプリ開発界隈がもっともっと盛り上がっていったらうれしいです。

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/