Help us understand the problem. What is going on with this article?

Stack View でパスコードロック画面風ナンバーパッドをつくる

More than 3 years have passed since last update.

大阪のお好み焼屋です。今年も店に新メニューは増えませんでしたが、App StoreではWatch appiMessage Stickers packをリリースできました! でもステッカーに関しては「そもそもiMessageを使っていない」というiPhoneユーザーが多くて、「フェデリギさん、話がちがうじゃないですか!」とけっこう驚きました。

さて以前RubyMotion+AutoLayooutでパスコードロック画面風ナンバーパッドをつくりました。当時はダイナミック言語+Terminal+Vimな環境で、UIからなにから全てスニペットもコードコンプリーションも使わずごりごり書いていてそれはそれで楽しかったのですが、あれから3年、いまはすっかりスタティック言語+Xcode+Storyboardな生活です。住めば都です。

最終目的

qiita_2016_goal.jpg

みなさんご存知パスコードロック画面です。完コピ、というわけにはいきませんがこれを目指します。

UIのトレース

Sketch

より再現度を高めるため画面の測定をするならドローイングソフトのSketchが便利です。Sketchはすごく使いやすいのでここ数年機会があるごとにつかっています(AppやステッカーのグラフィックはすべてSketch)。

しかし、残念ながらというべきか、僕が購入したときとくらべると、App Storeから抜けたり、値上げしたり、ついには年課金アップデートシステムに移行したりと、正直導入の敷居が上がってしまった感じです。でもいいソフトであることは間違いないと思います。

ねぎがべらぼうに高騰して苦しい時も、Sketchがあれば簡単にねぎを描くことができます。
おすすめしておきます。

green_leek.png

UIトレースの手順

  1. iPhone実機でスクリーンショットを撮る
  2. 1をSketchにインポート
  3. [A]を押してその機種のサイズに合ったアートボードをつくる
  4. スクリーンショットをアートボードのサイズにリサイズ
  5. 測りたい箇所に線を引いたり描いたりしてそのサイズや距離を測る(Xcodeと同じようにoptionキーが使える)

ということになります。実例が以下。

qiita_advcal_2016_2_comparison.png

明るい紫色がサイズ測定のためにSketchで描いたものです。

いろいろ線を引いていて気がついたことは、

  • 画面の中央には(ナンバーパッドのセンターではなく)数字の5が来ていること(スクリーンショットの薄いグレイのレクタングルがナンバーパッドをセンタリングした場合の枠です)
  • ステータスバーを除いて、ナンバーパッドの周囲には同じ程度の大きさの箱が5つ入る
  • ナンバーパッドのリーディングから線を引くと、"Emergency"はすこしはみ出し、Cancelは逆にすこし内に入り過ぎのように見える
  • 当たり前のようですが、ちゃんと計算されてデザインされているのだなあ、ということ。見習いたい。

必要な情報を集めたら、さっそく再現にとりかかりましょう!

Stack View とは

Stack ViewはAuto Layoutを補完するものとしてiOS9から加えられました。レイアウト上で共通の特性を持つエレメントをStack Viewへとその特性ごとに小さいものから大きいものへとスタックしてまとめていき、全体のレイアウトをよりシンプルに、簡単にしようという仕組みです。要するに「Divide and conquer」です。

最近たびたび耳にするLinkedInのLayoutKitもStackベースのようで、流行りのソリューションなのかもしれません。

従来の方法でナンバーパッドのような沢山のView(とそのサブクラス)が必要なUIをレイアウトしようとすると、なにか新しい部品をくわえるたびにいちいちXcodeからやれどこに置くのかわからないだの(ambiguity)、前に言ったことと相反しているだの(conflicts)ブーブー文句を言われたものですが、Stack Viewだととにかく一度スタックしてしまえば文句をいわれる的(マト)は絞れるようになる(Stack Viewをスケープゴートにできる)ので、だいぶ気が楽になります。

下が今回パスコード入力画面の再現のために使ったStack Viewを図示したものです。

qiita_2016_v_h.png

画面内のすべての要素をスタックしました。NSMatrixと格闘した世代が見たら卒倒するんじゃないかというぐらいViewてんこ盛り状態ですが、UIStackViewはUIViewのサブクラスでありながらrenderの必要がないために特別な最適化が働いていて、動作は軽いんだそうです(参考)。NSCellもdeprecatedらしいので、これも時代の流れと割り切っていきましょう。

プロパティ

プロパティに関しては図解付きで丁寧にまとめられたものがあるので、詳しくはそちらをご参照ください。日本語ならこちら。

ここでは大雑把な話だけ。

Axis

従来のAuto Layoutの作法とくらべてStack Viewがもっともちがう点はどこかというとそれはこのAxisの存在だと思います。

もちろんいままでもhorizontalverticalは重要な概念だったわけですが、Stack Viewでは「このStack Viewはhorizontalです」と決めると、とにかくそこに加えられた要素はすべて横並びにされます。そのStack View内での微妙な位置決めにはalignmentdistributionが使われるものの、横並びであることは変わりません。

qiita_advcal_2016_1_hor_ver.png

UIStackView
var axis: UILayoutConstraintAxis

/*
enum UILayoutConstraintAxis: Int {
    case horizontal = 0
    case vertical
}
*/

ナンバーパッドではボタンが11個、左最下部を除いて綺麗に3x4で並んでいます。そこで今回は、まずはhorizontalでボタンを3つスタックし、次にそのスタックを計4つに複製したら、最後にそれらすべてをまとめてverticalでスタックすることにしました。

Storyboardでの実際の作業は以下のようになります。積み重なった(スタックされた)箱に下矢印がついたものがstackボタンです。

qiita_advcal_2016_2_stack_buttons.gif

たくさんあった警告の赤いラインが、その場しのぎとはいえスタックで一瞬にして消えるのは見ていてとても気持ちがいい😊

あと、あらかじめオリジンとなるボタンのプロパティ(aspect ratioやclass)をカスタマイズしてから複製すると、後の作業がすごく楽になります。

Alignment, Distribution

AlignmentとDistributionはすごーくややこしいのですが、今回はボタンの大きさおよび列などすべて同じディメンションのため、verticalもhorizontalもそれぞれUIStackViewAlignment.fillUIStackViewDistribution.fillEquallyで大丈夫でした。

実現したいレイアウトがもっと複雑な場合は、先ほど紹介したサイトに図付きが解説がとても参考になると思います。

Spacing

上のgifを見ていただくとわかるように、最初にViewをスタックすると、まずはそれぞれのintrinsicContentSizeに基づいているであろうと思われるサイズに部品がリサイズされ、隙間もなくピタッとくっつけられます。

var spacing: CGFloat

spacingはそれぞれarrangedSubviewsaxisの方向にその値分だけ間をあけてやる、というプロパティです。これらの値はSketchを利用して測量済みなので、そのまま適用してやります。この際、共通の値を共通のaxisを持つStack Viewに渡したければ、複数選択した状態にすると一括で入力ができます。

qiita_2016_spacing_x640.gif

これだけで、すでに3年前に実現したことはほぼ達成できています! すばらしい。

Button

ボタンをレイアウト

せっかくなのでボタンの数字の下に英文字をプリントします。UIButtonに新たにレーベルを足したいんですが、StoryboardでUIButtonのインスタンスにlabelをsubviewとして加えようとしてもなぜかうまくいきません(原因はわかりませんでした。ハードコードすれば普通にできるはず)。

仕方がないのでUIViewをベースにして、以下のような構造にすることにします。

最背面から順に

  • ベース(コンテナ)となるview
    • 以下2つのlabelをverticalに束ねるstack view
      • 大きいキーの番号用のlabel
      • 小さい英文字用のlabel
    • ボーダー(枠)だけ表示された最前面のbutton

qiita_button_1_base_view.png

仕上げ

入力のインディケーターlabel4つをhorizontalにスタックし、"Enter Passcode"labelとverticalでスタックしたら、最後にナンバーパッドとまとめてverticalにスタックします。

それっぽいエフェクトをくわえたのがこちら。

qiita_advcal_2016_1_results_2.gif

まとめ

参考プロジェクト

おまけとしてStack Viewで簡単に実現できるアニメーションも実装しました(参考)。秘密のパスコードを入力した方だけが見ることができます! ヒントは『屋号』ですが、じつはコードに直書き(!)だったりもします。

qiita_2016_stackview_animation.gif

そもそもの話として Auto Layoutは難しい! というのはあると思います。でもその難しさを軽減する手段としてStack Viewは十分期待に応える出来になっていると思います。

ありがとうございました。

OkonomiyakiYuki
大阪のお好み焼屋です!
http://okono.me
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away