#背景
SwiftUIにて画面作成するときにほぼ間違いなくお世話になる、VStack、HStackについて、その公式ドキュメントのリファレンスと実装に差異があり戸惑いました。そのときの疑問とそれをどのように整理して解消したのかを記録したものです。
#対象とする読者
Swift初心者
SwiftUI初心者
#環境
私がこの記事を書いている際に利用しているのは次の環境です。
Xcode 11.3.1
Swift 5.1
#はじめに
VStack、HStackは、SwiftUIで画面レイアウトを作るときにほぼ間違いなくお世話になるもので、ビュー(ここでいうビューは、ボタンやテキスト等のUIコントロール部品や、それらをグループ化した子ビューなど広義でのUIコントロール全般を指すものとご理解ください)をレイアウトとして配置するためのコンテナーとして利用され、それ自体もビューとなります。
ビューを縦(垂直)方向に並べるときに利用するのが、VStack
ビューを横(水平)方向に並べるときに利用するのが、HStack
ビューを奥行き方向に並べるときに利用するのが、ZStack
入れ子でこれらを使い分けることで、様々なレイアウトにカスタマイズして画面を構成していきます。
今回は、このうちのVStackを取り上げます。
#VStack
###イニシャライザの呼び出し方
イニシャライザとは、インスタンスの初期化用メソッドのこと。
公式ドキュメントの記載は次のとおりです。
公式ドキュメント(VStack)
https://developer.apple.com/documentation/swiftui/vstack
// Creating a Stack
init (alignment: HorizontalAlignment, spacing: CGFloat?, @ViewBuilder content: () -> Content)
VStackイニシャライザのパラメータは3つあります。
-
alignment
水平方向の配置位置をHorizontalAlignment構造体のタイププロパティで指定。.leadeing
(左寄せ),.center
(中央寄せ),.trailing
(右寄せ)等の指定が可能。デフォルトは.center
-
spacing
子ビュー同士の間隔を調整するための余白を指定します。デフォルトはnil
でシステムデフォルトの余白が付与されます。 -
content
このスタックに並べるビュー(子ビュー、ボタン、テキストなど)をまとめたものを返すクロージャを指定します。
デフォルトのあるパラメータは省略可能です。一番最後のcontent
だけは必ず指定が必要です。
###実際の使い方
VStack(alignment: .leading, spacing: 10) {
Text("Hello World!")
Text("Here We Go!")
}
もし私のようなSwift初心者は、ここで少し混乱します。
- 疑問1 イニシャライザの定義だと引数が3つあるけど、そのうちの最後のcontentが欠落している?
- 疑問2 その代わりに、{}内にVStack内に表示させたいビュー(Text2つ)を記述している?
- 疑問3 そもそも{}内にビュー(Text2つ)を改行して列挙してだけのようだけど、こういう書き方していいの?
では、これらの疑問を見ていきます。
疑問1と疑問2について
swiftでは「トレーリングクロージャー式」という記述をすることが可能です。
トレーリングは後方を指し、クロージャー式は{}で囲んだ式のこと。
トレーリングクロージャーというのは、以下のようなSwift独自の構文です。
関数の最後の引数としてクロージャー式を渡す必要があり、クロージャー式が長い場合に、それを関数の引数として記載せずに、関数呼び出しの括弧の後(末尾)にクロージャー式{ }として記述できる
つまり、さきほどの実装例はトレーリングクロージャー式で記述されていたということです。それを利用しなかった場合は次のよう書き換えることができます。
// トレーリングクロージャー式を使わないで書くと・・・
VStack(alignment: .leading, spacing: 10,
content: {
Text("Hello World!")
Text("Here We Go!")
}
)
この例では、たしかにイニシャライザの宣言になんとなく寄ってきました。クロージャーの中身が数行なので、読みにくいということもありません。
ですが、クロージャーに記述する行数が増えれば増えるほど、読みずらくなるのは容易に想像できますので、基本はトレーリングクロージャー式を利用して、重たい記述は引数とは別に記述すべきなのですね。とても合理的な考え方だと思います。
疑問3について
疑問1,疑問2 についてはコードの書き方として便利な式が用意されていることを知りました。しかし、疑問3の「そもそも{}内にビュー(Text2つ)を改行して列挙してだけのようだけど、こういう書き方していいの?」については、まだ解決していません。
ここで、改めてVStackのイニシャライザを見てみると、content引数には、@ViewBuilder
属性が付与されていることがわかります。
この属性はSwift5.1の新機能である「ファンクションビルダー」が利用されています。
この属性がついたクロージャー内の各行は、暗黙的に1行が1引数となって、ViewBuilder
のbuildBlock
メソッドに渡されます(暗黙的に内部で変換されます)。
つまり、クロージャー内にTextを2行記述しましたが、実際は内部では以下のように2つの引数を受け取るbuildBlockメソッドに渡され、View型のインスタンスを返却してもらっているのです。
// ViewBuilderへの変換がされた後の実装はこんな感じ
//(こちらの実装でも、もちろん表示されます)
VStack(alignment: .leading, spacing: 10,
content: ViewBuilder.buildBlock(
Text("Hello World!"),
Text("Here We Go!")
)
ここまで記述すると、ようやくイニシャライザの定義と完全に理論的な一致が確認でき、安心できます。
なお、ViewBuilderでは、VStack、HStackなどクロージャー内部に最大10行のViewまでを引数として置き換えてくれるbuildBlockメソッドが事前に用意されています。VStackに10行分のTextを記述しても正常に画面表示できますが、11行以上記述するとビルドエラーが発生します。時間がある方はぜひ実際に試してみてください。
#さいごに
新しい関数(イニシャライザ)を見て、リファレンス(公式ドキュメント)を参照するのは開発者として基本的な行動です。ですが、実際のコードとリファレンスに乖離があると、混乱したり正しい理解の妨げになります。
私も今回のこの記事を自分で整理してアウトプットすることで、頭の中を整理できました。私のようなSwift初心者の方に、この記事が少しでも役立てば幸いです。