🪶

NavigationStackで遷移して次のViewに状態を共有する

に公開

Wrapする位置に注意が必要

GitHub Copilot For XcodeやChatGPT、Claudeなど精度の高いAIを使ってもなぜか非推奨のNavigationLinkを使いたがる???

やりたいこととしては、@EnvironmentObject を使用してObservableObjectで変更した状態を共有したい。

NavigationLink(destination: ChildView().environmentObject(countModel)) {
                    Text("子ビューへ移動")
                        .font(.title)
                        .padding()
                        .background(Color.blue)
                        .foregroundColor(.white)
                        .cornerRadius(15)
                }
            }

流石にこれは困る😅さすがにこ

こんな時は、stack overflowを見て同じ課題を見てみる。

https://stackoverflow.com/questions/76386652/how-to-use-navigationdestination-with-button-in-swiftui-for-complex-navigation

でもnoteの記事の方が参考になった。

https://note.com/taatn0te/n/n0b2a1833a73f

@State private var isPresented = falseを定義して、NavigationStackVStackをWrapする。ボタンを押すと、isPresented = trueに変更することで画面遷移することができる。この時に、状態を他のViewに渡すコードを書く。

Button{
                isPresented = true
            } label: {
                Text("子ビューを表示")
            }
            .navigationDestination(isPresented: $isPresented) {
                ChildView()
                    .environmentObject(countModel)
            }

全体のコード

import SwiftUI

class CountObservableObject: ObservableObject {
    @Published var count: Int = 0
    
    init(count: Int) {
        self.count = count
    }
    
    // returnは戻り値がある
    // Voidは戻り値がない
    func increment() {
        count += 1
    }
}

struct ChildView: View {
    @EnvironmentObject var countModel: CountObservableObject
    var body: some View {
        Text("子ビューのカウント: \(countModel.count)")
            .font(.title)
            .padding()
            .onTapGesture {
                countModel.increment()
            }
    }
}

struct ContentView: View {
    @StateObject private var countModel = CountObservableObject(count: 0)
    @State private var isPresented = false
    
    var body: some View {
        NavigationStack {
        VStack {
            Text("現在のカウント: \(countModel.count)")
                .font(.largeTitle)
                .padding()
            Button(action: {
                countModel.increment()
            }) {
                Text("カウントを増やす")
                    .font(.title)
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(15)
            }
        }
        .padding()
            Button{
                isPresented = true
            } label: {
                Text("子ビューを表示")
            }
            .navigationDestination(isPresented: $isPresented) {
                ChildView()
                    .environmentObject(countModel)
            }
        }}
}

#Preview {
    ContentView()
}

ボタンを押してカウントする。画面遷移して、他のページに状態を共有してみる。

はいできました🙌
ちなみに、@StateObject private var countModel = CountObservableObject(count: 0)を呼び出せば表示できるだろうと思って実験したらカウントされてませんでした😇
なので、他のページに状態を共有するときには、@EnvironmentObjectは必要です。

最後に

状態を変更して他のページに値を共有するときは、@EnvironmentObjectが必要です。そして画面遷移するときは、NavigationStackを使用するのが現在は推奨されていてNavigationLinkとは書き方が変わってますので注意が必要です。

Discussion