はじめに
SwiftUIを使うとなぜこんな簡単にUIの実装ができるのか、について基本的なことを整理しつつ調べた内容をまとめました
内容
こちらの公式ドキュメントを読んでみました
SwiftUI はユーザーインターフェイスの設計に宣言的なアプローチを提供します。伝統的な命令型のアプローチでは、ビューをインスタンス化し、レイアウトし、設定するだけでなく、条件の変更に応じて継続的に更新する負担がコントローラのコードにかかっています。これに対し、宣言的なアプローチでは、ビューを階層的に宣言することで、ユーザーインターフェースの軽量な記述を実現します。
Viewプロトコルに準拠する
Viewプロトコルに準拠した構造体を定義することで、カスタムビュータイプを宣言します。
struct MyView: View {
}
Bodyの定義
Viewプロトコルの主な要件は、bodyを定義しなければならないということです。
struct MyView: View {
var body: some View {
}
}
SwiftUI はViewを更新する必要があるときにいつでもこのプロパティの値を読み込みます。これはViewの寿命の間に繰り返し起こり、通常はユーザーの入力やシステムイベントに対応します。Viewが返す値は、SwiftUIが画面上に描画する要素です。
Viewの内容を組み立てる
Viewのbodyプロパティにコンテンツを追加することで、Viewの外観を記述します。SwiftUI が提供する組み込みのViewと、別の場所で定義したカスタムビューからボディを構成することができます。
struct MyView: View {
var body: some View {
VStack {
Text("Hello, World!")
Text("Glad to meet you.")
}
}
}
複数の子Viewを取るViewは、通常、ViewBuilder 属性でマークされたクロージャ(Layout containers・Collection containers)を使用して行います。これにより、呼び出し側で追加の構文を必要としない複数ステートメントクロージャが可能になります。
Layout containersとは
レイアウトコンテナは、ユーザーインターフェイスの要素を配置するために使用します。スタックとグリッドは、コンテンツやインターフェースの寸法の変更に対応して、それらが含む子Viewの位置を更新し、調整します。レイアウトコンテナは、他のレイアウトコンテナの中に任意の深さまでネストして、複雑なレイアウト効果を実現することができます。
レイアウトコンテナビューで構築したレイアウトの位置、配置、その他の要素を微調整するには、Layout modifiersを使用します。
Layout modifiers とは
レイアウト修飾子を使用して、View階層内のViewの配置を微調整します。Viewのサイズ、位置、整列を調整したり、制約したりすることができます。
Collection containersとは
コレクションビューを使用すると、他のViewを組み立てて、複雑で組み込みの動作を持つ動的なグループ化を行うことができます。たとえば、リストビューを作成して、大量のデータをスクロールできるようにすることができます。
VStackの内部はどうなっているの??
☝️ こちらでとても分かりやすく説明されていました。
SwiftUIを理解する
VStackの実装を見ると、引数の最後に、@ViewBuilder という関数ビルダのカスタム属性が付与されたクロージャが定義されています。関数ビルダが付与されているので、呼び出し元のクロージャ内で式文が使えます。@frozen public struct VStack<Content> : View where Content : View { @inlinable public init( alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () -> Content) public typealias Body = Never }
そして、この @ViewBuilder の内部をみると buildBlock の記述があるのが分かります。この buildBlock に式文が引数として渡され、処理されているのです。
@_functionBuilder public struct ViewBuilder { public static func buildBlock() -> EmptyView public static func buildBlock<Content>(_ content: Content) -> >Content where Content : View } extension ViewBuilder { public static func buildBlock<C0, C1>(_ c0: C0, _ c1: C1) -> >TupleView<(C0, C1)> where C0 : View, C1 : View }
buildBlockに渡すことができる引数の数は、最大10個までとなっています。
ViewBuilderとは
クロージャからViewを構築するカスタムパラメータ属性です。
ViewBuilder は、通常、子ビューを生成するクロージャのパラメータ属性として使用し、それらのクロージャが複数の子ビューを提供できるようにします。例えば、以下の contextMenu 関数は、ビュービルダーを介して 1 つ以上のビューを生成するクロージャを受け取ります。
func contextMenu<MenuItems: View>(
@ViewBuilder menuItems: () -> MenuItems
) -> some View
修飾子を使ったViewの設定
Viewの本体でViewを設定するには、View修飾子を適用します。
struct MyView: View {
var body: some View {
VStack {
Text("Hello, World!")
.font(.title)
Text("Glad to meet you.")
}
}
}
ViewをView階層に追加する
Viewを定義した後、組み込みのViewで行うのと同じように、他のViewでそれを組み込むことができます。Viewを追加するには、View階層内の表示させたい場所で宣言します。
struct ContentView: View {
var body: some View {
MyView(helloFont: .title)
}
}
おわりに
@ViewBuilder
の存在が実装の分かりやすさを支えているワケですかね。キホンのキが理解できました。さらにLayout containers
とCollection containers
の理解を深めることが重要なのかなと思いましたので、引き続き基本的な部分の理解を深めていきたいと思います。
参考