iOS, Swift, SwiftUI

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で疑問解消します。

参考

Author image

About Sato Takeshi

  • Tokyo, Japan
You've successfully subscribed to Personal Factory
Great! Next, complete checkout for full access to Personal Factory
Welcome back! You've successfully signed in.
Unable to sign you in. Please try again.
Success! Your account is fully activated, you now have access to all content.
Error! Stripe checkout failed.
Success! Your billing info is updated.
Error! Billing info update failed.