0

I'm trying to show an alert with some text when the user taps on an image view. The image view is part of a foreach loop to build the individual list items.

The button is working and it is triggering changes on the Observable object but the alert isPresented is not being triggered... This is so confusing!

The view is nested inside a few views to simplify the code. I've shared code snippets to illustrate my issue. Hopefully that will make sense to someone and a solution can be proposed.

Here's the containing view:

struct AssignmentsBoardView: View {
    @ObservedObject var presenter: AssignmentPresenter
    @ObservedObject var dogAlert: BookingNotesAlert = BookingNotesAlert()
    
    init(_ presenter: AssignmentPresenter) {
        self.presenter = presenter
    }
    
    var body: some View {
        VStack {
            Spacer()
            if (presenter.state == ViewState.loading) {
                LoadingIndicator(label: "Fetching collections")
            } else if (presenter.state == ViewState.complete) {
                VStack {
                    BoardDriversView(drivers: self.presenter.boardDrivers, currentDriver: self.presenter.selectedDriver, onSelected: self.changeDriver(boardDriver:))
                    AssignmentTypeHeaders(setCollections: {
                        self.changeAssignmentState(state: AssignmentState.collections)
                    }, setDropOffs: {
                        self.changeAssignmentState(state: AssignmentState.dropOffs)
                    }, currentAssignmentType: self.presenter.assignmentState)
                    ScrollView {
                        AssignmentDataView(presenter: self.presenter, dogAlert: self.dogAlert)
                    }
                }.alert(isPresented: self.$dogAlert.showAlert, content: {
                    Alert(title: Text("Booking Notes"), message: Text(self.dogAlert.getDogNotes()), dismissButton: .default(Text("Ok")))
                })
            } else {
                Text("No Assignments")
            }
            Spacer()
        }
    }
    
    private func changeDriver(boardDriver: BoardDriver) {
        self.presenter.changeSelectedDriver(driver: boardDriver)
    }
    
    private func changeAssignmentState(state: AssignmentState)  {
        self.presenter.changeAssignment(newState: state)
    }
}

struct AssignmentDataView: View {
    @ObservedObject var presenter: AssignmentPresenter
    var dogAlert: BookingNotesAlert
    
    @ViewBuilder
    var body: some View {
        if (self.presenter.assignmentState == AssignmentState.collections) {
            if (self.presenter.collections.isEmpty) {
                Text("No Collections scheduled for this driver")
            } else {
                ForEach(self.presenter.collections, id: \.id) { listItem in
                    CollectionListItem(dog: listItem, dogNotesAlert: self.dogAlert)
                }
            }
        } else {
            if (self.presenter.dropOffs.isEmpty) {
                Text("No Drop Offs scheduled for this driver")
            } else {
                ForEach(self.presenter.dropOffs, id: \.id) { listItem in
                    CollectionListItem(dog: listItem, dogNotesAlert: self.dogAlert)
                }
            }
        }
    }
}

This is the Observable Object:

class BookingNotesAlert: ObservableObject {
    @Published var showAlert = false
    var dog: DogCollectionListItem? = nil
    
    func showAlert(dog: DogCollectionListItem) {
        self.dog = dog
        self.showAlert.toggle()
        print("Toggled stuff")
        print("Notes \(self.getDogNotes())")
    }
    
    func getDogNotes() -> String {
        self.dog?.bookingNotes ?? "No notes provided"
    }
}

And this is where I am consuming it:

struct CollectionListItemThumbnail: View {
    var dog: DogCollectionListItem
    var dogNotesAlert: BookingNotesAlert
    
    var body: some View {
        Group {
            HStack {
                if (!dog.bookingNotes.isEmpty) {
                    Button(action: {
                        self.dogNotesAlert.showAlert(dog: dog)
                        print("Tapping")
                    }, label: {
                        ImageView(withUrl: dog.thumbnailUrl, 75, highlight: true)
                            .padding(.trailing, 12)
                            .padding(.bottom, 12)
                            .padding(.top, 12)
                    })
                } else {
                    ImageView(withUrl: dog.thumbnailUrl, 75)
                        .padding(.trailing, 12)
                        .padding(.bottom, 12)
                        .padding(.top, 12)
                }
            }
        }
    }
}

Trying Asperi's suggestion:

struct AssignmentsBoardView: View {
    @ObservedObject var presenter: AssignmentPresenter
    @State var dogAlert: BookingNotesAlert?
    
    init(_ presenter: AssignmentPresenter) {
        self.presenter = presenter
    }
    
    var body: some View {
        VStack {
            Spacer()
            if (presenter.state == ViewState.loading) {
                LoadingIndicator(label: "Fetching collections")
            } else if (presenter.state == ViewState.complete) {
                VStack {
                    BoardDriversView(drivers: self.presenter.boardDrivers, currentDriver: self.presenter.selectedDriver, onSelected: self.changeDriver(boardDriver:))
                    AssignmentTypeHeaders(setCollections: {
                        self.changeAssignmentState(state: AssignmentState.collections)
                    }, setDropOffs: {
                        self.changeAssignmentState(state: AssignmentState.dropOffs)
                    }, currentAssignmentType: self.presenter.assignmentState)
                    Button(action: {
                        let dog = self.presenter.collections[0]
                        dog.bookingNotes = "Test"
                        self.dogAlert = BookingNotesAlert(dog: dog)
                    }, label: {Text("Show booking notes")})
                    ScrollView {
                        AssignmentDataView(presenter: self.presenter, dogAlert: self.$dogAlert)
                    }
                }.alert(item: $dogAlert) { dogAlert in
                    Alert(title: Text("Booking Notes"), message: Text(dogAlert.getDogNotes()), dismissButton: .default(Text("Ok")))
                }
            } else {
                Text("No Assignments")
            }
            Spacer()
        }
    }
}
class BookingNotesAlert: Identifiable {
    var dog: DogCollectionListItem? = nil
    let id: UUID = UUID()
    
    init(dog: DogCollectionListItem) {
        self.dog = dog
        print("Item Id >> \(id)")
        print("Notes >> \(self.getDogNotes())")
    }
    
    func getDogNotes() -> String {
        self.dog?.bookingNotes ?? "No notes provided"
    }
}

This is the console log from the button tap:

    Item Id >> 6D695709-2894-4735-8DF6-81E36E1E8F4D
    Notes >> Test
    Item Id >> FB69ED3A-D462-4C08-8DD5-764C8A54A7B5
    Notes >> Test
    Item Id >> 484E0B22-F0BD-477A-8BE2-55F626F02609
    Notes >> Test
    Item Id >> F99E568F-C5BE-4B35-AEDF-E5A4A2700447
    Notes >> Test
Luiz Mota
  • 1
  • 2
  • Presumable you have many alerts declared on same state, instead you need alert by item activation, like in https://stackoverflow.com/a/61184229/12299030. Also might be helpful https://stackoverflow.com/a/63948838/12299030 and https://stackoverflow.com/a/63089069/12299030, they are about sheet, but the origin of issue and the solution are the same. – Asperi Oct 06 '20 at 16:52
  • 1
    Thanks @Asperi I've made the item identifiable as per post and made the alert trigger by item but the alert is still not triggering. The id is different every time I tap the button according to the console. I've updated the original post with the suggested changes for clarification. – Luiz Mota Oct 07 '20 at 08:49

0 Answers0