🦔

Previewableマクロは何がいいのか[Xcode16, iOS17+, SwiftUI]

2024/11/28に公開

Previewableマクロとは

Xcode16 Release Note

Xcode16のPreviews関係のリリースにPreviewableマクロというものが出ました。

@Previewable is a new macro that can be applied to any SwiftUI property wrapper so it can be used directly inside of a #Preview without needing to define an intermediate wrapper view. This is especially common for the use of @State, which can now be written as @Previewable @State var myState = <initial value> directly inside of the #Preview. (110570957) (FB12298419)

翻訳

@Previewableは、新しいマクロであり、任意のSwiftUIプロパティラッパーに適用できます。これにより、中間的なラッパービューを定義する必要なく、#Preview内で直接使用できるようになります。この機能は特に@Stateの利用において一般的であり、@Previewable @State var myState = <初期値>のように、#Preview内で直接記述できるようになりました。(110570957) (FB12298419)

https://developer.apple.com/documentation/xcode-release-notes/xcode-16-release-notes#Previews

Apple Developer Documentation

Apple Developer Documentationによると、iOS17+から利用できます。

Tagging a variable declaration at root scope in your #Preview body with ‘@Previewable’ allows you to use dynamic properties inline in previews. The #Preview macro will generate an embedded SwiftUI view; tagged declarations become properties on the view, and all remaining statements form the view’s body.

翻訳

#Preview本文内でルートスコープにある変数宣言に@Previewableを付けることで、プレビュー内で動的プロパティをインラインで使用できるようになります。#Previewマクロは埋め込みのSwiftUIビューを生成し、タグ付けされた宣言はそのビューのプロパティとなり、残りのすべての文はビューのbodyを構成します。

https://developer.apple.com/documentation/SwiftUI/Previewable()

Article

「Previewing your app’s interface in Xcode」というPreviewに関する記事に、より具体的な解説が載っています。

なお、Previewableマクロは、SwiftUIでだけ使えるマクロであり、UIKitやAppKitのPreviewには適用できないことに注意が必要です。

Use inline dynamic properties with Previewable
When a view depends on a Binding property wrapper, you can create a functional binding for that property and pass it into your preview using the Previewable() macro. This macro works on any variable conforming to the DynamicProperty protocol.

翻訳

インライン動的プロパティをPreviewableで使用する
ビューがBindingプロパティラッパーに依存している場合、Previewable()マクロを使用して、そのプロパティの機能的なバインディングを作成し、プレビューに渡すことができます。このマクロは、DynamicPropertyプロトコルに準拠する任意の変数で動作します。

https://developer.apple.com/documentation/Xcode/previewing-your-apps-interface-in-xcode#Use-inline-dynamic-properties-with-Previewable

使用例

Apple Developer Documentationからの引用。

#Preview("toggle") {
    @Previewable @State var toggled = true
    return Toggle("Loud Noises", isOn: $toggled)
}

「Previewing your app’s interface in Xcode」という記事からの引用。

struct PlayButton: View {
    @Binding var isPlaying: Bool

    var body: some View {
        Button(action: {
            self.isPlaying.toggle()
        }) {
            Image(systemName: isPlaying ? "pause.circle" : "play.circle")
            .resizable()
            .scaledToFit()
            .frame(maxWidth: 80)
        }
    }
}

#Preview {
    // Tag the dynamic property with `Previewable`.
    @Previewable @State var isPlaying = true

    // Pass it into your view.
    PlayButton(isPlaying: $isPlaying)
}

何がうれしいのか

これまで

  1. .constant() を使った場合は、値がダイナミックに動きませんでした。
import SwiftUI

#Preview("Constant") {
    VStack {
        Toggle("Toggle", isOn: .constant(true))

        Text("ON")
    }
    .padding()
}

  1. Previewで値をダイナミックに動かすには、Preview用に用意した別のViewで囲むとうまくいっていました。ただ、周りくどいのがデメリットでした。
import SwiftUI

#Preview("Wrapper") {
    return WrapperView()

    struct WrapperView: View {
        @State var isOn = true

        var body: some View {
            VStack {
                Toggle("Toggle", isOn: $isOn)

                Text(isOn ? "ON" : "OFF")
            }
            .padding()
        }
    }
}

Previewマクロが使えると...

@Previewable @State var xxx = yyy をPreview内に定義するだけで、値がダイナミックに動きます。

import SwiftUI

#Preview("Previewable") {
    @Previewable @State var isOn = true

    VStack {
        Toggle("Toggle", isOn: $isOn)

        Text(isOn ? "ON" : "OFF")
    }
    .padding()
}

おまけ)マクロを展開するとどうなるか?

最後の例を展開すると以下のようになります。

マクロを展開するときの最初のポイントとして、@Previewableにフォーカスを当てても、マクロは展開することができません。 #Preview にフォーカスを当てた状態で、右クリック>[Expand Macro]するか(またはメニューの[Editor]>[Expand Macro])をします。
このことから、 #Preview マクロが@Previewableを使って、コードを生成している仕組みであることが伺えます。

Apple Developer Documentationの項に引用した文章を見ると、「#Previewマクロは埋め込みのSwiftUIビューを生成し、タグ付けされた宣言はそのビューのプロパティとなり、残りのすべての文はビューのbodyを構成します。」となっていますが、その通りになっていることがこの具体例から確認することができます。

@available(iOS 17.0, macOS 14.0, tvOS 17.0, visionOS 1.0, watchOS 10.0, *)
struct $s11Enjoy_iOS180021ToggleViewswift_IfFDefMX35_0_33_227D011B23D148CDEFF92716620B3F4FLl7PreviewfMf1_15PreviewRegistryfMu_: DeveloperToolsSupport.PreviewRegistry {
    static var fileID: String {
        "Enjoy_iOS18/ToggleView.swift"
    }
    static var line: Int {
        36
    }
    static var column: Int {
        1
    }

    static func makePreview() throws -> DeveloperToolsSupport.Preview {
        DeveloperToolsSupport.Preview("Previewable") {
            struct __P_Previewable_Transform_Wrapper: SwiftUI.View {
                @State var isOn = true

                var body: some SwiftUI.View {
                    VStack {
                        Toggle("Toggle", isOn: $isOn)

                        Text(isOn ? "ON" : "OFF")
                    }
                    .padding()
                }
            }
            return __P_Previewable_Transform_Wrapper()
        }
    }
}

Discussion