SwiftUIのコードを読み解く

WWDC19参加中です。4日目です。
ラボのSwift Open Hoursにいきまして、SwiftUIのコードを文法レベルで教えてもらいました。
Swift5.1になり、新しい構文がたくさん追加され、SwiftUIはそれをフルに活用しているので

今回はチュートリアルのこちらのコードを読み合わせしました。


struct ContentView: View {
    var body: some View {
        VStack {
            MapView()
                .edgesIgnoringSafeArea(.top)
                .frame(height: 300)

            CircleImage()
                .offset(x: 0, y: -130)
                .padding(.bottom, -130)

            VStack(alignment: .leading) {
                Text("Turtle Rock")
                    .font(.title)
                HStack(alignment: .top) {
                    Text("Joshua Tree National Park")
                        .font(.subheadline)
                    Spacer()
                    Text("California")
                        .font(.subheadline)
                }
            }
        }
    }
}

では一行ずつ読み合わせします。

struct ContentView: View

ContentView というstructを定義しViewプロトコルに準拠されています。

var body: some View {
    VStack{...}
}

bodyはコンピューテッドプロパティで型はViewプロトコル。
some修飾子をつけることでOpaque Result Typeであることを表します。
Opaque Result Typeは内部実装を隠蔽しながらパフォーマンスにも影響しない手段です。

Swift 5.1 に導入される Opaque Result Type とは何か


bodyVStackのインスタンスを返すコンピューテッドプロパティです。

Swift5.1から単一式の場合にクロージャーだけではなく関数やコンピューテッドプロパティにもreturnを書かなくても良くなりました。

swift-evolution note 6/5

なので、

var body: some View {
    VStack{...}
}

var body: some View {
    get { // 
        return VStack{...}
    }
}

と同じ意味になります。


VStack{...}

VStack{}の{}はSwift5.1のFunctionBuilderという機能を利用しています。

SwiftUIの魔法を実現する仕組み (Custom Attributes, Function Builder)

ViewBuilderというアノテーションをつけることでコンパイル時にコードが変換されます。
ViewBuilderの定義はこちら

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
@_functionBuilder public struct ViewBuilder {

    /// Builds an empty view from an block containing no statements, `{ }`.
    public static func buildBlock() -> EmptyView

    /// Passes a single view written as a child view (e..g, `{ Text("Hello") }`) through
    /// unmodified.
    public static func buildBlock<Content>(_ content: Content) -> Content where Content : View
}

今回の例で言えば

        VStack {
            MapView()
                .edgesIgnoringSafeArea(.top)
                .frame(height: 300)

            CircleImage()
                .offset(x: 0, y: -130)
                .padding(.bottom, -130)

            VStack(alignment: .leading) {
                Text("Turtle Rock")
                    .font(.title)
                HStack(alignment: .top) {
                    Text("Joshua Tree National Park")
                        .font(.subheadline)
                    Spacer()
                    Text("California")
                        .font(.subheadline)
                }
            }
        }

というソースコードは

        VStack {
            ViewBuilder.buildBlock(
                MapView()
                    .edgesIgnoringSafeArea(.top)
                    .frame(height: 300),

                CircleImage()
                    .offset(x: 0, y: -130)
                    .padding(.bottom, -130),

                VStack(alignment: .leading) {
                    Text("Turtle Rock")
                        .font(.title)
                    HStack(alignment: .top) {
                        Text("Joshua Tree National Park")
                            .font(.subheadline)
                        Spacer()
                        Text("California")
                            .font(.subheadline)
                    }
                }
            )
        }

のように変換されるとのことです。

疑問点

VStackでViewBuilderがなぜ使えるのかがわからない。
明日Labで疑問解消します。

参考