アトミックデザインとシステムデザインはデザインで一般的です。これは、コントロールから画面まで、すべてがコンポーネントで構成される場合です。 プログラマーが個別のコントロールを作成することは難しくありませんが、画面全体をどうするか?
新年の例を見てみましょう。
- すべてをまとめましょう。
- コントローラーに分割:ナビゲーション、テンプレート、コンテンツを選択します。
- 他の画面にコードを再利用します。
すべての束
この新年のスクリーンでは、ピッツェリアの特別営業時間について説明しています。 これは非常に単純なので、1つのコントローラーにすることは犯罪ではありません。
しかし。 次回、同様の画面が必要になったときは、すべてをもう一度繰り返し、すべての画面に同じ変更を加える必要があります。 まあ、それは編集なしでは起こりません。
したがって、それを部分に分割し、他の画面に使用する方が合理的です。 私は3つを強調しました:
- ナビゲーション
- コンテンツの領域とアクションの場所が画面の下部にあるテンプレート、
- センター内のユニークなコンテンツ。
独自のUIViewController
で各パーツを選択します。
コンテナナビゲーション
ナビゲーションコンテナの最も顕著な例は、 UINavigationController
とUITabBarController
です。 それぞれが独自のコントロールの下で画面上のストリップを占有し、残りのスペースを別のUIViewController
用にUIViewController
ます。
私たちの場合、閉じるボタンが1つだけのすべてのモーダル画面用のコンテナがあります。
ボタンを右に移動する場合は、1つのコントローラーで変更するだけです。
または、AppStoreストーリーカードのように、すべてのモーダルウィンドウを特別なアニメーションで表示し、スワイプでインタラクティブに閉じることにした場合。 次に、 UIViewControllerTransitioningDelegate
をこのコントローラーに対してのみ設定する必要があります。
コントローラーを分離するには、 container view
使用できcontainer view
。これにより、親にUIView
が作成され、そこに子コントローラーのUIView
挿入されます。
container view
を画面の端まで引き伸ばします。 Safe area
は、自動的に子コントローラーに適用されます。
スクリーンパターン
コンテンツは画面上で明白です:写真、タイトル、テキスト。 ボタンはその一部のようですが、コンテンツはさまざまなiPhoneで動的であり、ボタンは固定されています。 異なるタスクを持つ2つのシステムが表示されます。1つはコンテンツを表示し、もう1つはコンテンツを埋め込み、調整します。 これらは2つのコントローラーに分割する必要があります。
1つ目は画面のレイアウトを担当します。コンテンツを中央に配置し、ボタンを画面の下部に固定する必要があります。 2番目はコンテンツを描画します。
テンプレートがなければ、すべてのコントローラーは似ていますが、要素は踊ります。
最後の画面のボタンは異なります-それは内容によって異なります。 委任は、問題の解決に役立ちます。コントローラーテンプレートは、コンテンツからコントロールを要求し、 UIStackView
表示します。
// OnboardingViewController.swift protocol OnboardingViewControllerDatasource { var supportingViews: [UIView] { get } } // NewYearContentViewController.swift extension NewYearContentViewController: OnboardingViewControllerDatasource { var supportingViews: [UIView] { return [view().doneButton] } }
ボタンは、関連オブジェクトを介してコントローラーに接続できます。 IBOutlet
およびIBAction
はコンテンツコントローラーに保存され、要素は階層に追加されません。
UIStoryboardSegue
準備段階で、コンテンツから要素を取得し、テンプレートに追加できます。
// OnboardingViewController.swift override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if let buttonsDatasource = segue.destination as? OnboardingViewControllerDatasource { view().supportingViews = buttonsDatasource.supportingViews } }
セッターでは、コントロールをUIStackView
追加します。
// OnboardingView.swift var supportingViews: [UIView] = [] { didSet { for view in supportingViews { stackView.addArrangedSubview(view) } } }
その結果、コントローラーはナビゲーション、テンプレート、コンテンツの3つの部分に分割されました。 写真では、すべてのcontainer view
灰色container view
表示されています。
動的コントローラーサイズ
コンテンツコントローラーには独自の最大サイズがあり、内部のconstraints
によって制限されconstraints
。
Container view
は、 Autoresizing mask
基づいてコンストアを追加し、コンテンツの内部ディメンションと競合します。 この問題はコードで解決されます。コンテンツコントローラーでは、 Autoresizing mask
からのAutoresizing mask
ストアの影響を受けないことを示す必要があります。
// NewYearContentViewController.swift override func loadView() { super.loadView() view.translatesAutoresizingMaskIntoConstraints = false }
Interface Builderにはさらに2つのステップがあります:
手順1. UIView
Intrinsic size
を指定します 。 実際の値は発売後に表示されますが、ここでは適切な値を入力します。
ステップ2.コンテンツコントローラーに対して、 Simulated Size
指定します。 過去のサイズと一致しない場合があります。
AutoLayout
が現在のサイズの要素を分解する方法を理解できない場合、エラーが発生しAutoLayout
。
ほとんどの場合、定数の優先順位を変更すると問題はなくなります。 UIView
1つが他のUIView
よりも拡大/縮小できるように、それらを配置する必要があります。
部分に分けてコードを書く
コントローラーをいくつかの部分に分割しましたが、今のところそれらを再利用することはできませんUIStoryboard
からのインターフェースを部分的に抽出することUIStoryboard
困難です。 一部のデータをコンテンツに転送する必要がある場合は、階層全体でそれをノックする必要があります。 それは他の方法であるべきです:最初にコンテンツを取得し、それを設定し、それから必要なコンテナでそれをラップします。 電球のように。
次の3つのタスクが表示されます。
- 各コントローラーを独自の
UIStoryboard
分離します。 -
container view
拒否し、コードでコンテナにコントローラを追加します。 - それをすべて結びます。
UIStoryboardの共有
2つの追加のUIStoryboard
を作成し、Navigation ControllerとTemplate Controllerをそれらにコピーアンドペーストする必要があります。 Embed segue
は壊れますが、制約が設定されたcontainer view
は転送されます。 制約を保存し、 container view
通常のUIView
置き換える必要があります。
- コードの形式で
UIStoryboard
を開きます(ファイルコンテキストメニュー→として開く...→ソースコード); タイプを
containerView
からview
変更しview
。 開始タグと終了タグの両方を変更する必要があります。
同様に、必要に応じて、たとえば
UIView
をUIScrollView
に変更できます。 そしてその逆。
コントローラーをプロパティに設定します。プロパティis initial view controller
で、 UIStoryboard
をコントローラーとして呼び出します。
コントローラーの名前がUIStoryboard
の名前と一致する場合、ダウンロードはそれ自体が目的のファイルを見つけるメソッドでラップできます。
protocol Storyboardable { } extension Storyboardable where Self: UIViewController { static func instantiateInitialFromStoryboard() -> Self { let controller = storyboard().instantiateInitialViewController() return controller! as! Self } static func storyboard(fileName: String? = nil) -> UIStoryboard { let storyboard = UIStoryboard(name: fileName ?? storyboardIdentifier, bundle: nil) return storyboard } static var storyboardIdentifier: String { return String(describing: self) } static var storyboardName: String { return storyboardIdentifier } }
コントローラーが.xib
で記述されている場合、標準コンストラクターはそのようなダンスなしでロードされます。 .xib
、 .xib
ことができるコントローラーは1つだけです。多くの場合、これでは十分ではありません。良い場合には、1つの画面が複数の画面で構成されます。 したがって、 UIStoryborad
を使用します。画面を複数の部分に分割するのは簡単です。
コードでコントローラーを追加する
コントローラーが適切に機能するためには、そのライフサイクルのすべてのメソッド、 will/did-appear/disappear
です。
正しく表示するには、5つのステップを呼び出す必要があります。
willMove(toParent parent: UIViewController?) addChild(_ childController: UIViewController) addSubview(_ subivew: UIView) layout didMove(toParent parent: UIViewController?)
AppleはaddChild()
自体がwillMove(toParent)
を呼び出すため、コードを4ステップに減らすことをおwillMove(toParent)
ます。 要約すると:
addChild(_ childController: UIViewController) addSubview(_ subivew: UIView) layout didMove(toParent parent: UIViewController?)
簡単にするために、すべてをextension
ラップできます。 このケースでは、 insertSubview()
をinsertSubview()
バージョンが必要です。
extension UIViewController { func insertFullframeChildController(_ childController: UIViewController, toView: UIView? = nil, index: Int) { let containerView: UIView = toView ?? view addChild(childController) containerView.insertSubview(childController.view, at: index) containerView.pinToBounds(childController.view) childController.didMove(toParent: self) } }
削除するには、同じ手順が必要です。親コントローラーの代わりにnil
を設定する必要があります。 didMove(toParent: nil)
removeFromParent()
がdidMove(toParent: nil)
removeFromParent()
呼び出すdidMove(toParent: nil)
になり、レイアウトは不要になりました。 短縮バージョンは大きく異なります。
willMove(toParent: nil) view.removeFromSuperview() removeFromParent()
レイアウト
制約を設定する
コントローラーのサイズを正しく設定するには、 AutoLayout
を使用しAutoLayout
。 すべての面をすべての面に釘付けする必要があります。
extension UIView { func pinToBounds(_ view: UIView) { view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ view.topAnchor.constraint(equalTo: topAnchor), view.bottomAnchor.constraint(equalTo: bottomAnchor), view.leadingAnchor.constraint(equalTo: leadingAnchor), view.trailingAnchor.constraint(equalTo: trailingAnchor) ]) } }
コードで子コントローラーを追加する
これですべてを組み合わせることができます:
// ModalContainerViewController.swift public func embedController(_ controller: UIViewController) { insertFullframeChildController(controller, index: 0) }
使用頻度のため、これらすべてをextension
ラップできます。
// ModalContainerViewController.swift extension UIViewController { func wrapInModalContainer() -> ModalContainerViewController { let modalController = ModalContainerViewController.instantiateInitialFromStoryboard() modalController.embedController(self) return modalController } }
テンプレートコントローラーにも同様の方法が必要です。 prepare(for segue:)
以前はprepare(for segue:)
で設定されていましたが、コントローラーの埋め込みメソッドでバインドできるようになりました:
// OnboardingViewController.swift public func embedController(_ controller: UIViewController, actionsDatasource: OnboardingViewControllerDatasource) { insertFullframeChildController(controller, toView: view().contentContainerView, index: 0) view().supportingViews = actionsDatasource.supportingViews }
コントローラーの作成は次のようになります。
// MainViewController.swift @IBAction func showModalControllerDidPress(_ sender: UIButton) { let content = NewYearContentViewController.instantiateInitialFromStoryboard() // let onboarding = OnboardingViewController.instantiateInitialFromStoryboard() onboarding.embedController(contentController, actionsDatasource: contentController) let modalController = onboarding.wrapInModalContainer() present(modalController, animated: true) }
新しい画面をテンプレートに接続するのは簡単です:
- コンテンツに関係のないものを削除します。
- OnboardingViewControllerDatasourceプロトコルを実装してアクションボタンを指定します。
- テンプレートとコンテンツをリンクするメソッドを記述します。
コンテナの詳細
ステータスバー
多くの場合、 status bar
可視性は、コンテナではなく、コンテンツを持つコントローラーによって制御される必要がありstatus bar
。 これにはいくつかのproperty
があります。
// UIView.swift var childForStatusBarStyle: UIViewController? var childForStatusBarHidden: UIViewController?
これらのproperty
を使用して、コントローラーのチェーンを作成できます。後者は、 status bar
表示を担当しstatus bar
。
安全なエリア
コンテナのボタンがコンテンツに重なる場合、 safeArea
ゾーンを増やす必要があります。 これはコードで実行できます。子コントローラーのadditinalSafeAreaInsets
を設定します。 embedController()
から呼び出すことができます:
private func addSafeArea(to controller: UIViewController) { if #available(iOS 11.0, *) { let buttonHeight = CGFloat(30) let topInset = UIEdgeInsets(top: buttonHeight, left: 0, bottom: 0, right: 0) controller.additionalSafeAreaInsets = topInset } }
上に30ポイントを追加すると、ボタンはコンテンツの重複を停止し、 safeArea
は緑の領域を占有します。
マージン。 スーパービューのマージンを保持
コントローラーには標準のmargins
ます。 通常、それらは画面の各側から16ポイントに等しく、プラスサイズでのみ20ポイントです。
margins
基づいてmargins
定数を作成できます。エッジへのインデントはiPhoneごとに異なります。
あるUIView
を別のUIView
に配置すると、 margins
は半分になり、8ポイントになります。 これを防ぐには、 Preserve superview margins
するを有効にする必要があります。 子UIView
のmargins
親のmargins
と等しくなります。 フルスクリーンコンテナーに適しています。
終わり
コンテナコントローラーは強力なツールです。 コードを簡素化し、タスクを分離し、再利用できます。 ネストされたコントローラーは、 UIStoryboard
、 .xib
または単にコードのいずれの方法でも作成できます。 最も重要なことは、作成が簡単で使いやすいことです。
テンプレートを作成する価値がある画面はありますか? コメントで共有してください!