SwiftUI: var bodyの中にif文を書くとエラーになる
SwiftUIでView
を定義する際、var body: some View
内に、@ViewBuilder
構文でビューの定義を書いていきます。
SwiftUIのif文てどう動いているのか?
例えば、"Hello, World!"の前にチェックボックスを付けるとします。
struct TestView: View {
var isChecked: Bool = false
var body: some View {
HStack {
if isChecked {
Image(systemName: "checkmark.square")
}
else {
Image(systemName: "square")
}
Text("Hello, World!")
}
}
}
上記コードでは、isChecked
の値に応じて、チェックONかチェックOFFかが表示されます。
さて、いまここでImage
にいくつかオプションを指定したいけど2箇所に書くのは冗長だから、変わる部分だけif
に入れよう、ということで以下のように書き直すとします。
struct TestView: View {
var isChecked: Bool = false
var body: some View {
HStack {
var systemName = "square"
if isChecked {
// ↓エラー:Type '()' cannot conform to 'View'
systemName = "checkmark.square"
}
Image(systemName: systemName)
.foregroundColor(.blue)
.background(.green)
Text("Hello, World!")
}
}
}
これはエラーになってしまいます。
@ViewBuilder
構文では、宣言以外のすべての式が、View
を返す必要があります。
@ViewBuilder
構文により、
- 通常の式は
buildExpression
関数の呼び出し - ブロックは
buildBlock
関数の呼び出し -
if
文はbuildIf
関数の呼び出し -
if else
文はbuildEther
関数の呼び出し
にそれぞれ変換されます。if
文自体がクロージャを引数に取る関数のようなイメージです。
式にせよブロックにせよクロージャにせよ、いずれもView
を返す必要があります。
systemName = "checkmark.square"
の行は、View
を返していないのでエラーになります。
もし書き換えるとすればどうすればいいのか
struct TestView: View {
var isChecked: Bool = false
var body: some View {
HStack {
var systemName = "square"
if isChecked {
// ↓こうするとエラーにならない
let _ = systemName = "checkmark.square"
}
Image(systemName: systemName)
.foregroundColor(.blue)
.background(.green)
Text("Hello, World!")
}
}
}
if
文のなかをlet _ =
のような宣言に書き換えることで式と見なされなくなり、@ViewBuilder
構文の対象にならなくなります。
つまりこのif
文は、buildIf
に変換されなくなります。
そして期待通りに動作します。
ちなみに
View
を作る行でも、宣言文で受けると@ViewBuilder
変換されません。
struct TestView: View {
var body: some View {
let _ = Text("Hello, World!")
}
}
これだと「Hello, World!」は表示されません。
あと、let _ =
という特殊な変数で受けてますが、普通の変数名でも構いません。
ただしその場合は、この変数使われてないよ?という警告が出ます。
if 〜 else if 〜 else 〜
でelse if
の数に上限があるのか試してみました。
結論としては、おそらく制限がない、です。13個でも問題ありませんでした。
@ViewBuilder
内に書けるView
は10個までという制限がありますが、こっちは制限がないようです。
if 〜 else { if 〜 else 〜 }
のように分解される感じになっているのでしょうか。
とはいえ?if
を使うよりは、以下のように書いた方がいいですね。
Image(systemName: isChecked ? "checkmark.square" : "square" )
if
の中の条件が複雑になるようであれば、関数にしてしまったほうがいいでしょう。