こんにちは。delyデザインエンジニアのJohn(@johnny__kei)です。
本記事はdely Advent Calendar 2018の10日目の記事です。
Qiita: https://qiita.com/advent-calendar/2018/dely
Adventar: https://adventar.org/calendars/3535
前日は、プロダクトデザイナーのkassyが「ユーザーの声に振り回されないデザインの改善プロセス」という記事を書きました。いいプロダクトを作るには、ユーザーの声を鵜呑みにするのではなく、きちんと判断する必要がありますよね。
はじめに
みなさんは、iOSアプリ開発をするときに、XcodeのDebugging View Hierarchiesを使用していますか?
Debugging View Hierarchiesを使用すると、アプリが現在の状態で停止され、Viewの階層や、プロパティを確認できます。
出典: Apple Specialized Debugging Workflows
AutoLayoutが効いていないのを調査するときに、使用したりする方もいらっしゃると思います。
Debugging View HierarchiesはUIKitのUINavigationBarなどのクラスにも適用され、どのようなView階層か見ることができます。
アプリ独自のUIパーツを作成するときに、できるだけ、View構造や、メソッド、プロパティなどを、UIKitにそろえると、使いやすくなると思います。
そこで
前半は、UIKitのいくつかのクラスのView階層について書きます。
後半は、前半をふまえて、サンプルの実装について書きます。
UIKitのView階層
- UINavigationBar
UINavigationBarのsubviewには_UIBarBackground(非公開クラス)があります。
下の画像を見るとわかるように、UIVisualEffectViewやshadowImageが設定されるimageViewなどがあることがわかります。
UINavigationControllerのnavigationBarでは、StatusBarまでnavigationBarが伸びているように見えるのは、この_UIBarBackgroundがはみ出しているからです。 自分で、UINavigationBarをViewControllerのviewにaddSubviewする場合は、適応されないので、UINavigationControllerの方で、そういった実装がされると推測できます。
imageViewも、Viewから高さ0.5の分だけ下にはみ出ています。これを発見したとき、ビビりました。
- UIPageControl
横スクロールでページングがあるときに、よく使用されたりします。 UIPageControlはdotのサイズやdot間のマージンは変更できません。
dot自体は、単なるUIViewであることがわかります。
また、dotは7ptで、dot間のマージンは9ptということがわかりました。
結構シンプルな作りになっています。
また、UIPageControlは、InterfaceBuilderとCodeでの初期状態に違いがあったのも、新しい発見でした。
--- | currentPageIndicatorTintColor | pageIndicatorTintColor |
---|---|---|
InterfaceBuilder | UIColor.white | UIColor.white.withAlphaComponent(0.2) |
Code | nil | nil |
サンプル実装
- BottomBar
SafeAreaの登場で、下部に、Viewを配置したいときに、どういう風に実装したらいいか、悩む場合がありますよね。
自分なりに、こうしたらいいんじゃないかという実装を書いていきます。
static let viewHeight: CGFloat = 49.0
のように本来表示したい高さを定義します。
そして、実際にViewControllerのviewにのせるのはこんな感じに制約をつけます。
NSLayoutConstraint.activate([ bottomBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), bottomBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), bottomBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -BottomBar.viewHeight), bottomBar.bottomAnchor.constraint(equalTo: view.bottomAnchor) ])
こうすることで、safeAreaのbottomがある場合は、contentの高さと、safeArea分伸びた状態で表示することができます。
このBottomBarの上に、Viewをのせる場合は、contentViewにのせます。 contentViewは高さは以下のように固定されているので、ボタンはcontentViewに対して、制約をつけることで、いい感じに表示することができるようになります。
contentView.heightAnchor.constraint(equalToConstant: BottomBar.viewHeight)
さらに、前述した、UINavigationBarのshadowImageを表示するimageViewのように、border部分は、はみ出して作ってあります。 これも、contentViewにのせるようにしていて、UINavigationBarのようにしてあります。
contentView.addSubview(topBorder) topBorder.heightAnchor.constraint(equalToConstant: 0.5) topBorder.bottomAnchor.constraint(equalTo: contentView.topAnchor)
- PageControl
前述したように、UIPageControlはdotのサイズやdot間のマージンは定義されていないので、変更することができません。 そこで、UIPageControlとほぼ同じ、プロパティやメソッドを持ち、dotのサイズやdot間のマージンを設定できる、PageControlを実装してみます。
UIControlは、UIViewのサブクラスなので、普通にViewをのせていくことで大丈夫です。
さらに、UIPageControlのような実装にするには、タップしてPageが変化したときに、UIControl.Event.valueChanged
イベントを発火する必要があります。
UIPageControlの説明にも書いてあります。
その実装は以下のようになっています。
override func endTracking(_ touch: UITouch?, with event: UIEvent?) { guard let touchPoint = touch?.location(in: self), bounds.contains(touchPoint) else { return } // touchPointが、currentPageのdotの左右どちらかでなどのロジックで判断し、変更があれば発火する sendActions(for: .valueChanged) }
dotのサイズやマージンを定義することで、サイズの計算も簡単になります。
// UIPageControlに合わせる。 var dotSize: CGFloat = 7.0 var dotMargin: CGFloat = 9.0 // pageCountに応じた、size計算 func size(forNumberOfPages pageCount: Int) -> CGSize { let height = (15.0 * 2) + dotSize guard pageCount > 0 else { // UIPageControlがこんな感じの値 return CGSize(width: dotSize, height: height) } let width = dotSize * CGFloat(pageCount) + dotMargin * CGFloat(pageCount - 1) return CGSize(width: width, height: height) }
numberOfPages, currentPageなどのプロパティの実装に関しても、結構シンプルになっているので、ぜひサンプルの実装をみてもらえばと思います。
UIPageControlに実装されていることは、全て実装しています。(たぶん)
実装のサンプルは置いておきます。
GitHub Sample Code
まとめ
いかがでしたでしょうか?
Debugging View Hierarchiesを使用すると、AppleのUIKit内部の実装が見れておもしろいですよね。
UI部分の階層構造しかみれませんが、そこから滲み出る、ロジック部分も想像すると、なお面白いと思います。
また、View階層をデザイナーに見せることで、内部構造を理解してもらえるので、次から、そこを考えて、デザインを作ってくれるようになるかもしれませんね。
明日は、サーバーサイドエンジニアのjoeによる「好きな技術を使って作る!くだらないslackBot運用のすヽめ」です。お楽しみに!