I want to navigate to a detail view from within an alert.
Here a minimal reproducible code sample for the error:
struct ContentView: View {
let items = ["1", "2", "3", "4"]
var body: some View {
NavigationView {
List {
ForEach(items, id: \.self) { item in
RowView(item: item)
}
}
}
}
}
struct RowView: View {
let item: String
@State private var isShowingNext = false
@State private var isShowingAlert = false
var body: some View {
let itemNumber = Int(item)!
let isEven = itemNumber % 2 == 0
ZStack {
if isEven {
NavigationLink(
destination: Text("Hello"),
isActive: $isShowingNext,
label: {
Text(item)
}
).hidden()
Button(
action: {
isShowingAlert.toggle()
}, label: {
Text("\(item) (with alert)")
}
)
.alert(isPresented: $isShowingAlert) {
Alert(
title: Text("Alert")
.foregroundColor(Color.red)
.font(.title),
message: Text("Hello"),
primaryButton: .destructive(
Text("Navigate"),
action: {
isShowingNext = true
}
),
secondaryButton: .cancel()
)
}
} else {
NavigationLink(
destination: Text("Hello"),
isActive: $isShowingNext,
label: {
Text(item)
}
)
}
}
}
}
When you click on e.g. the item 2 (with alert)
the alert shows up but the error also appears in the console. Then when you click on Navigate
inside the alert it doesn't navigate.
What could be the issue?
Both Alert
and NavigationLink
present another view. And you cannot present a view when the other view is already presented on current view.
You have to wait until the Alert
is hidden, and then you can present the NavigationLink
.
The easiest way to do this is to pass Binding
to NavigationLink
, which will make sure that the Alert
is not displayed:
struct RowView: View {
let item: String
@State private var isShowingNext = false
@State private var isShowingAlert = false
private var isShowingNextBinding: Binding<Bool> {
Binding(
get: {
!isShowingAlert && isShowingNext
}, set: {
isShowingNext = $0
}
)
}
var body: some View {
let itemNumber = Int(item)!
let isEven = itemNumber % 2 == 0
ZStack {
if isEven {
NavigationLink(
destination: Text("Hello"),
isActive: isShowingNextBinding,
label: {
Text(item)
}
).hidden()
Button(
action: {
isShowingAlert.toggle()
}, label: {
Text("\(item) (with alert)")
}
)
.alert(isPresented: $isShowingAlert) {
Alert(
title: Text("Alert")
.foregroundColor(Color.red)
.font(.title),
message: Text("Hello"),
primaryButton: .destructive(
Text("Navigate"),
action: {
isShowingNext = true
}
),
secondaryButton: .cancel()
)
}
} else {
NavigationLink(
destination: Text("Hello"),
isActive: $isShowingNext,
label: {
Text(item)
}
)
}
}
}
}