しめ鯖日記

swift, iPhoneアプリ開発, ruby on rails等のTipsや入門記事書いてます

SwiftUIでローカル変数を定義する方法

SwiftUIだとローカル変数の定義でエラーになることが多いので整理してみました。

SwiftUIでのローカル変数定義方法

bodyの中の場合は下のようにreturnを付けることで定義できます。

struct ContentView: View {
    var body: some View {
        let a = 1
        return Text("Hello, World!")
    }
}

ZStackのように複数Viewを返す場合はreturnを付けた上で() -> TupleView<(Text, Text)>のように戻り値の型を指定する必要があります。

struct ContentView: View {
    var body: some View {
        ZStack { () -> TupleView<(Text, Text)> in
            let a = 1
            return ViewBuilder.buildBlock(
                Text("AAA"),
                Text("BBB")
            )
        }
    }
}

定義に関する調査

なぜ上記方法でうまくいくかの調査は下の通りです。

下のように変数を定義するとFunction declares an opaque return type, but has no return statements in its body from which to infer an underlying typeというエラーになります。
これは値をreturnしていないというエラーです。

struct ContentView: View {
    var body: some View {
        let a = 1
        Text("Hello, World!")
    }
}

以下のようにreturnを付けることでエラーは出なくなります。

struct ContentView: View {
    var body: some View {
        let a = 1
        return Text("Hello, World!")
    }
}

ローカル変数を定義するまで動いていたのはSwiftのreturn文省略の仕様が理由です。
Swiftはクロージャー内が1行だけの場合はreturnを省略できるので、変数定義をしなければreturnを省略することができます。

struct ContentView: View {
    var body: some View {
        Text("Hello, World!")
    }
}

mapメソッドなどでもreturnが不要なのはこの仕様が理由になります。

[1, 2, 3].map { $0 * 2 }

メソッドも同じようにreturnを省略できます。

struct ContentView: View {
    func test() -> some View {
        Text("Hello, World!")
    }
}

returnを付ける方法ですが、Zstackの中では使うことができません。
下のように書くとCannot convert return expression of type 'ZStack<_>' to return type 'some View'というエラーになります。
これはZStack<_>をsome Viewに変換できないというエラーです。

struct ContentView: View {
    var body: some View {
        ZStack {
            let a = 1
            return Text("Hello, World!")
        }
    }
}

このエラーは下のようにZStackの戻り値の型を宣言することで解消できます。

struct ContentView: View {
    var body: some View {
        ZStack { () -> Text in
            let a = 1
            return Text("AAA")
        }
    }
}

複数Viewを返す場合は下のようにViewBuilderを使ってTupleViewを作る必要があります。

struct ContentView: View {
    var body: some View {
        ZStack { () -> TupleView<(Text, Text)> in
            let a = 1
            return ViewBuilder.buildBlock(
                Text("AAA"),
                Text("BBB")
            )
        }
    }
}