例によるSwiftUI(SwiftUI by Example)18 What now?

SwiftUIのヒントとコツ
SwiftUI tips and tricks

SwiftUIには強力なヘッドライン機能が満載ですが、より優れたアプリを作成するのに役立つ小さなヒントやコツも多数あります。

私はこれまでに出くわしたすべてのヒントを以下に要約しようとしました。また、該当する場合は、追加のコンテキストを提供するために、より詳細なSwiftUIチュートリアルへのリンクも提供しました。

ライブプレビューを再開します

トラックパッドで常に[再開]を押すのではなく、SwiftUI開発者にとって最も重要なキーボードショートカットを次に示します。Option-Cmd-Pを押して、プレビューウィンドウをすぐに再読み込みし、ライブアップデートを再開します。

Make private

Appleは、アプリで状態を使用する3つの方法を提供しています。@State単純なローカルプロパティ@ObservedObject用、複雑なプロパティまたはビュー間で共有さ@EnvironmentObjectれるプロパティ用、および多くのビューによって間接的に共有される可能性のあるプロパティ用です。

@Stateローカルビューで使用するために特別に設計されているため、他の場所でアクセスするように設計されていないことを実際に補強@Stateするためにprivate、プロパティにマークを付けることをお勧めします。

一定の結合を持つプロトタイプ
Prototype with constant bindings

//--------------------------------------------
struct ContentView: View {
    @ObservedObject var updater = FrequentUpdater()
    @State private var tapCount = 0

    var body: some View {
        VStack {
            TextField("Example placeholder", text: .constant("Hello"))
                .textFieldStyle(RoundedBorderTextFieldStyle())
            
            Slider(value: .constant(0.5))
        }
    }
}
//--------------------------------------------

テストビューの提示
Presenting test views

プロトタイプを作成する際のもう1つの便利なヒントは、ナビゲーションビューを使用している場合でも、完全な詳細ビューではなく、あらゆる種類のビューを表示できることです。

たとえば、ユーザーのリストがあり、そのうちの1つをタップしても機能することを確認したい場合は、次のように、本格的なカスタムカスタムビューではなく、テキストビューを指すナビゲーションリンクを使用できます。

//--------------------------------------------
struct ContentView: View {
    let users = (1...100).map { number in "User \(number)" }

    var body: some View {
        NavigationView {
            List(users, id: \.self) { user in
                NavigationLink(destination: Text("Detail for \(user)")) {
                    Text(user)
                }
            }.navigationBarTitle("Select a user")
        }
    }
}
//--------------------------------------------

ビュー制限10を超えます
Go past the 10 view limit

SwiftUIのすべてのコンテナーは、10個以下の子を返す必要があります。
10を超えるビューが必要な場合、bodyプロパティから複数のビューを返す必要がある場合、または複数の異なる種類のビューを返す必要がある場合は、次のようなグループを使用する必要があります。

//--------------------------------------------
struct ContentView: View {
    var body: some View {
        List {
            Group {
                Text("Row 1")
                Text("Row 2")
                Text("Row 3")
                Text("Row 4")
                Text("Row 5")
                Text("Row 6")
            }

            Group {
                Text("Row 7")
                Text("Row 8")
                Text("Row 9")
                Text("Row 10")
                Text("Row 11")
            }
        }
    }
}
//--------------------------------------------

グループは純粋に論理的なコンテナであり、レイアウトには影響しません。

print()

print()への呼び出しはすべて無視されます。
プレビューキャンバスの再生ボタンを右クリックして、[デバッグプレビュー]を選択します。その小さな変更により、print()通話は通常どおり機能することがわかります。

暗黙の依存 HStack
Relying on the implicit

アイテムのリストを作成するときは、左側に画像を表示し、次に右側にテキストを表示するというiOS標準の外観を取得するのが一般的です。

アイテムの動的リスト(つまり、データの配列に添付されたリスト)を使用している場合は、実際にはHStackリスト内で無料で入手できるため、手動で作成する必要はありません。

したがって、このコードは配列からの画像名に基づいてリストを作成し、暗黙的HStackに画像とテキストを並べて配置します。

//--------------------------------------------
struct ContentView: View {
    let imageNames = ["dokuro", "dokuro"]

    var body: some View {
        List(imageNames, id: \.self) { image in
            Image(image).resizable().frame(width: 40)
            Text(image)
        }
    }
}
//--------------------------------------------

大きなビューを分割する
Splitting up large views

//--------------------------------------------
struct ContentView: View {
    let users = ["Paul Hudson", "Taylor Swift"]

    var body: some View {
        NavigationView {
            List(users, id: \.self) { user in
                NavigationLink(destination: Text("Detail View")) {
                    Image("dokuro").resizable().frame(width: 50, height: 50)

                    VStack(alignment: .leading) {
                        Text("Johnny Appleseed").font(.headline)
                        Text("Occupation: Programmer")
                        Text(user)
                    }
                }
            }.navigationBarTitle("Users")
        }
    }
}//--------------------------------------------

より良いプレビュー
Better previewing

SwiftUIの多くの利点の1つは、作業中にレイアウトのプレビューを即座に取得できることです。さらに良いことに、これらのプレビューをカスタマイズして、複数のデザインを並べて表示したり、ナビゲーションビューでどのように表示されるかを確認したり、ダークモードを試したりすることができます。

たとえば、これにより、ContentView特大テキスト、ダークモード、ナビゲーションビューの3つの異なるデザインを並べて表示するプレビューが作成されます。

//--------------------------------------------
#if DEBUG
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView()
                .environment(\.sizeCategory, .accessibilityExtraExtraExtraLarge)
            ContentView()
                .environment(\.colorScheme, .dark)
            NavigationView {
                ContentView()
            }
        }
    }
}
#endif

カスタム修飾子を作成する
Create custom modifiers

//--------------------------------------------
struct PrimaryLabel: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .background(Color.black)
            .foregroundColor(.white)
            .font(.largeTitle)
            .cornerRadius(10)
    }
}
//--------------------------------------------
struct ContentView: View {
    var body: some View {
        Text("Hello World")
            .modifier(PrimaryLabel())
    }
}
//--------------------------------------------

変更を簡単にアニメーション化
Animate changes easily

SwiftUIには、ビュー階層への変更をアニメーション化するための2つの方法animation()ありwithAnimation()ます。これらはさまざまな場所で使用されますが、どちらもアプリのビューへの変更をスムーズにする効果があります。

このanimation()メソッドはバインディングで使用され、バインディングの値が変更される結果となる変更をアニメーション化するようにSwiftUIに要求します。たとえばToggle、ラベルを表示または非表示にするビューは次のとおりです。

$showingWelcome

$showingWelcome.animation()

$showingWelcome.animation(.spring())

//--------------------------------------------
struct PrimaryLabel: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .background(Color.black)
            .foregroundColor(.white)
            .font(.largeTitle)
            .cornerRadius(10)
    }
}
//--------------------------------------------
struct ContentView: View {
    @State private var showingWelcome = false

    var body: some View {
        VStack {
//            Toggle(isOn: $showingWelcome) {
//                Text("Toggle label")
//            }
            
//            Toggle(isOn: $showingWelcome.animation()) {
//                Text("Toggle label")
//            }
            
            Toggle(isOn: $showingWelcome.animation(.spring())) {
                Text("Toggle label")
            }

            if showingWelcome {
                Text("Hello World")
            }
        }
    }
}
//--------------------------------------------

バインディングではなく通常の状態で作業している場合は、変更をwithAnimation()呼び出しでラップすることで変更をアニメーション化できます。

self.showingWelcome.toggle()

withAnimation {
self.showingWelcome.toggle()
}

withAnimation(.spring()) {
self.showingWelcome.toggle()
}

//--------------------------------------------
struct ContentView: View {
    @State private var showingWelcome = false

    var body: some View {
        VStack {
            Button(action: {
                withAnimation(.spring()) {
                    self.showingWelcome.toggle()
                }
            }) {
                Text("Toggle label")
            }

            if showingWelcome {
                Text("Hello World")
            }
        }
    }
}
//--------------------------------------------

ビューに複数のアラートを表示する
Showing multiple alerts in a view

alert()1つのビューに複数の修飾子をアタッチしようとすると、コードが期待どおりに機能しないことがわかります。一方のアラートは機能しますが、もう一方は機能しません。

これを修正するには、アラートの表示をトリガーするボタンやその他のビューなど、ビュー階層のさまざまな部分にアラートを添付する必要があります。

例によるSwiftUI(SwiftUI by Example)9 Alerts, action sheets, and menus

バインディングから新しい値を公開する
Publishing new values from a binding

大事なことを言い忘れsend()ましたが、パブリッシャーから更新通知を送信するときの問題(たとえばPassthroughSubject、@Publishedプロパティの呼び出しや更新)を回避するには、常にメインスレッドにいることを確認する必要があります。

UIKitや他のほとんどのUIフレームワークと同様に、SwiftUIアプリで必要なすべてのバックグラウンド作業を実行できますが、メインスレッドのユーザーインターフェイスのみを操作する必要があります。状態の変更は自動的にの更新をトリガーするためbody、メインスレッドでこれらの状態の変更を実行することを確認することが重要です。

例によるSwiftUI(SwiftUI by Example)5 Advanced state