(Xcode 12 ベータ版の macOS Big Sur で) SwiftUI で苦労していたことの 1 つは、たとえば、このビューでユーザーが入力したプレーヤー名に応じて構造体SetUpGameView
を作成する必要があるa からの移動であり、次にvia へGame
のナビゲーションに進みます。が nil のGameView
場合は無効にする必要がある、ボタンのような NavigationLink 。var game: Game?
ユーザーが必要なすべてのデータを入力するまで、ゲームを初期化することはできませんが、以下の例では、playerName: String
空でないことが必要です。
まともな解決策がありますが、複雑すぎるようです。
以下に複数の解決策を紹介しますが、どれも複雑すぎるようです。さらに簡単な解決策を考え出すのを手伝ってくれることを願っていました.
ゲーム構造
struct Game {
let playerName: String
init?(playerName: String) {
guard !playerName.isEmpty else {
return nil
}
self.playerName = playerName
}
}
SetUpGameView
単純な (動作しない) 実装は次のとおりです。
struct SetUpGameView: View {
// ....
var game: Game? {
Game(playerName: playerName)
}
var body: some View {
NavigationView {
// ...
NavigationLink(
destination: GameView(game: game!),
label: { Label("Start Game", systemImage: "play") }
)
.disabled(game == nil)
// ...
}
}
// ...
}
ただし、アプリがクラッシュするため、これは機能しません。式が原因で、アプリがクラッシュしGameView(game: game!)
ますdestionation
。NavigationLink 初期化子が遅延評価されないため、game!
早期に評価され、最初は nil になるため、ラップを強制的に解除するとクラッシュが発生します。これは私にとって本当に紛らわしいです...それはただ...間違っているように感じます! 上記のナビゲーションが使用されるまで使用されず、この特定の初期化子を使用しても、NavigationLink がクリックされるまで目的地が使用されないためです。したがって、これをif let
、しかし今はもう少し複雑になります。ゲーム nil/非 nil の両方の状態で、無効/有効なレンダリングを除いて、NavigationLink ラベルを同じに見せたいです。これにより、コードの重複が発生します。または、少なくとも、以下に提示するものよりも優れた解決策を思いつきませんでした. 以下は、2 つの異なるソリューションと、2 つ目の改善された (カスタム ビューConditionalNavigationLink
ビューにリファクタリングされた) バージョンの 3 つ目です...
struct SetUpGameView: View {
@State private var playerName = ""
init() {
UITableView.appearance().backgroundColor = .clear
}
var game: Game? {
Game(playerName: playerName)
}
var body: some View {
NavigationView {
VStack {
Form {
TextField("Player name", text: $playerName)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
// All these three solution work
// navToGameSolution1
// navToGameSolution2
navToGameSolution2Refactored
}
}
}
// MARK: Solutions
// N.B. this helper view is only needed by solution1 to avoid duplication in if else, but also used in solution2 for convenience. If solution2 is used this code could be extracted to only occur inline with solution2.
var startGameLabel: some View {
// Bug with new View type `Label` see: https://stackoverflow.com/questions/62556361/swiftui-label-text-and-image-vertically-misaligned
HStack {
Image(systemName: "play")
Text("Start Game")
}
}
var navToGameSolution1: some View {
Group { // N.B. this wrapping `Group` is only here, if if let was inline in the `body` it could have been removed...
if let game = game {
NavigationLink(
destination: GameView(game: game),
label: { startGameLabel }
)
} else {
startGameLabel
}
}
}
var navToGameSolution2: some View {
NavigationLink(
destination: game.map { AnyView(GameView(game: $0)) } ?? AnyView(EmptyView()),
label: { startGameLabel }
).disabled(game == nil)
}
var navToGameSolution2Refactored: some View {
NavigatableIfModelPresent(model: game) {
startGameLabel
} destination: {
GameView(game: $0)
}
}
}
NavigatableIfModelPresent
と同じソリューションnavToGameSolution2
ですが、リファクタリングされているため、ラベルを繰り返したり、複数を構築したりする必要はありませんAnyView
...
struct NavigatableIfModelPresent<Model, Label, Destination>: View where Label: View, Destination: View {
let model: Model?
let label: () -> Label
let destination: (Model) -> Destination
var body: some View {
NavigationLink(
destination: model.map { AnyView(destination($0)) } ?? AnyView(EmptyView()),
label: label
).disabled(model == nil)
}
}
ここに何かが欠けているように感じます...ゲームが非nilになったときに自動的にナビゲートしたくなく、NavigationLink
ゲームが非nilになるまで有効にしたくありません。