0

I have a list of items. When I click an item Show Alert I am showing an alert which displays two options: either Navigate to dismiss the alert and navigate to the detail view or Cancel which is supposed to just dismiss the alert.

The problem is that Cancel still makes the NavigationLink navigate. What is a solution such that the second item navigates directly and the others show an alert that allows you to cancel.

This is a minimal reproducible example:

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

    private var isShowingNextBinding: Binding<Bool> {
        Binding(
            get: {
                !isShowingAlert && isShowingNext
            }, set: {
                isShowingNext = $0
            }
        )
    }

    var body: some View {
        ZStack {
            if item != "2" {
                NavigationLink(
                    destination: Text("Hello"),
                    isActive: isShowingNextBinding,
                    label: {
                        Text(item)
                    }
                ).hidden()
                Button(
                    action: {
                        isShowingAlert.toggle()
                    }, label: {
                        Text("\(item) Show Alert First")
                    }
                )
                .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: isShowingNextBinding,
                    label: {
                        Text("\(item) Show Directly")
                    }
                )
            }
        }
    }
}

Make sure to run it in iOS because on watchOS it works as expected.

Isaak
  • 1,107
  • 2
  • 11
  • 29

2 Answers2

1

Just change your 'set' part, like here:

Binding(
        get: {
            !isShowingAlert && isShowingNext
        }, set: {_ in
            isShowingNext = false
        }
    )

I can't define this answer. I just experimented and it works definitely how you'd expected(hope so).

P.S. in Xcode Version 13.0 beta 5 it works pretty well without changing 'set'

Suprafen
  • 58
  • 3
  • 9
0

Update RowView to this:

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: { _ in
                isShowingNext = false
            }
        )
    }

    var body: some View {
        ZStack {
            if item != "2" {
                NavigationLink(
                    destination: Text("Hello"),
                    isActive: isShowingNextBinding,
                    label: {
                        Text(item)
                    }
                ).hidden()
                Button(
                    action: {
                        isShowingAlert.toggle()
                    }, label: {
                        Text("\(item) Show Alert First")
                    }
                )
                .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) Show Directly")
                    }
                )
            }
        }
    }
}

Update the setter of the Binding to always update to false and have a separate NavigationLink that navigates directly by passing it a Binding to isShowingNext directly.

This is a workaround but it works.

Isaak
  • 1,107
  • 2
  • 11
  • 29