🍣
[iOS18]NavigationLinkのisActiveによる画面遷移でデータが受け渡せない
要するにこんなコードがある場合、
import SwiftUI
/// ios18だと、StackChildViewのvalueの値が次の画面に伝わらない
struct StackParentView: View {
@State var isNext: Bool = false
@State var childValue: String = ""
var body: some View {
Button(action: {
childValue = "Good bye."
isNext = true
},label: {
Text("次の画面")
})
NavigationLink(
destination: StackChildView(value: childValue),
isActive: $isNext
) {
EmptyView()
}
}
}
struct StackChildView: View {
@State var value: String
init(value: String) {
self.value = value
}
var body: some View {
VStack {
Text(UIDevice.current.systemVersion)
Text(value)
}
}
}
ios17.5と、ios18.0の場合それぞれ下記のような動作になります。
ios17.5の場合
ios18.0の場合ios18.0の場合は、1回目の遷移では、値が受け渡せていません。
'init(destination:isActive:label:)' was deprecated in iOS 16.0: use NavigationLink(value:label:), or navigationDestination(isPresented:destination:), inside a NavigationStack or NavigationSplitView
という警告は前から出てましたが、18になって大きく挙動が変わってしまいました。
これを修正する方法がいろいろあると思いますが、単純に解決しようとすると下記のような感じになると思います
/// 色々省略
@State private var path = NavigationPath()
NavigationStack(path: $path) {
NavigationLink(destination: NewStackParentView(path: $path)) {
Text("NewStackParentView")
.font(.title2)
}
.navigationDestination(for: String.self) { selection in
NewStackChildView(value: selection)
}
}
struct NewStackParentView: View {
@Binding var path: NavigationPath
var body: some View {
VStack {
Button(action: {
path.append("hello")
}, label: {
Text("Push!")
})
}
}
}
struct NewStackChildView: View {
@State var value: String
var body: some View {
Text("\(value)")
}
}
ただ、そんな単純なケースもあまりないと思いますので、EnvironmentとTimerと使ったサンプルを作ってみました。
struct ContentView: View {
@Environment(PathVM.self) private var pathVM
var body: some View {
@Bindable var pathVM = pathVM
NavigationStack(path: $pathVM.path) {
VStack {
Text("hello")
Button("Push!") {
pathVM.path.append("welcom")
}
}
.navigationDestination(for: String.self) { selection in
SubView(value: "\(selection)")
}
.navigationDestination(for: Route.self) { route in
switch route {
case .sub2:
Sub2View()
}
}
}
}
}
enum Route: Hashable {
case sub2
}
@Observable
class PathVM {
var path = NavigationPath()
var someVariable: String = ""
}
struct SubView: View {
@State var value: String?
@Environment(PathVM.self) private var pathVM
@State var timer: Timer?
@State var buttonDisable: Bool = false
var body: some View {
VStack {
Text("ここは、SubViewです")
Button("このボタンを押すと3秒後にSub2Viewに移動します") {
buttonDisable = true
// Web APIなどで通信が完了したら画面遷移するイメージ
timer = Timer.scheduledTimer(withTimeInterval: 3, repeats: false) { timer in
pathVM.someVariable = "from SubView Variable"
pathVM.path.append(Route.sub2)
buttonDisable = false
}
}
.disabled(buttonDisable)
}
.onDisappear {
timer?.invalidate()
buttonDisable = false
}
}
}
struct Sub2View: View {
@State var value: String?
@Environment(PathVM.self) private var pathVM
var body: some View {
VStack {
Text(pathVM.someVariable)
}
}
}
Timerのところは、apiに問い合わせて、レスポンスを待つところを簡易的に表現してみました。
Discussion