4

I'm new to SwiftUI and I'm having problems with presenting Alerts back-to-back.

The description of the .alert(item:content:) modifier has this written in it's definition:

/// Presents an alert.
    ///
    /// - Parameters:
    ///     - item: A `Binding` to an optional source of truth for the `Alert`.
    ///     When representing a non-nil item, the system uses `content` to
    ///     create an alert representation of the item.
    ///
    ///     If the identity changes, the system will dismiss a
    ///     currently-presented alert and replace it by a new alert.
    ///
    ///     - content: A closure returning the `Alert` to present.

    public func alert<Item>(item: Binding<Item?>, content: (Item) -> Alert) -> some View where Item : Identifiable

I'm particularly interested in the If the identity changes, the system will dismiss a currently-presented alert and replace it by a new alert part. Since I want Alerts to be presented back-to-back, if I'm somehow able to change the 'identity', I'll be able to achieve the functionality that I want - which is having the system dismiss the currently-presented alert and replacing the old Alert with a new Alert (back-to-back).

If someone can explain to me what 'identity' is and how I can change the 'identity' of something I'll be extremely grateful.

(Or if you know a better way to present alerts back-to-back that'll also be very very helpful.)

Thanks in advance!

Asperi
  • 228,894
  • 20
  • 464
  • 690
rayaantaneja
  • 1,182
  • 7
  • 18

2 Answers2

2

Notice how this method requires a Binding<Item?>, and that Item should be Identifiable. For the Binding<Item?> parameter, you're supposed to pass in a "source of truth" that controls what the alert shown looks like, or whether the alert shows at all. When this source of truth changes (i.e. becomes something else), the view will update the alert.

But here's the problem, how does SwiftUI know what does "change" mean in the context of your model? Let's say Item is a Person class that you wrote. Person has a name and age. It is your job to tell SwiftUI, that "a Person becomes a totally different person when its name changes". (Of course, you could have some other definition of what is meant by "a person changes" here. This definition is just an example.)

struct Person : Identifiable {
    var id: String {
        name
    }

    let name: String
    let age: Int
}

This is why Item must be Identifiable. Item.id is thus the "identity".

Note that Identifiable is different from Equatable, in that Identifiable asks the question "what makes this person a different person?" whereas Equatable asks "what result would you want == to give?". See this for another example.

how do we change the 'identity' of something?

Just change the binding you pass in (e.g. setting the @State that the binding is based on) in such a way that its id changes.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • Thank you so much for the clarification. I tried changing the binding I passed in but that still didn't seem to work how I wanted it to. As the other answer suggested, perhaps changing the identity programatically isn't something that's stable yet. Guess I'll have to find another way. Thanks a lot anyway! – rayaantaneja Apr 13 '20 at 11:31
2

Find below demo of alert-by-item usage. And some investigation results about changing identity as documented.

As you find experimenting with example (by tap on row) alert activated by user interaction works fine, but changing identity programmatically, as documented, seems not stable yet, however alert is really updated.

Tested with Xcode 11.4 / iOS 13.4

struct SomeItem: Identifiable { // identifiable item
    var id: Int // identity
}

struct DemoAlertOnItem: View {

    @State private var selectedItem: SomeItem? = nil

    var body: some View {
        VStack {
            ScrollView (.vertical, showsIndicators: false) {
                ForEach (0..<5) { i in

                    Text("Item \(i)").padding()
                    .onTapGesture {
                        self.selectedItem = SomeItem(id: i)

                        // below simulate change identity while alert is shown
                        DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
                            self.selectedItem = nil                    // works !!

                            // self.selectedItem?.id = 100             // crash !!
                            // self.selectedItem = SomeItem(id: 100)  // crash !!
                        }
                    }
                }
            }
        }
        .alert(item: self.$selectedItem) { item in
             Alert(title: Text("Alert"), message: Text("For item \(item.id)"), dismissButton: .default(Text("OK")))
        }
    }
}
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • Thank you so much. I was wondering why my app kept crashing when I tried to change the identity. Guess I'll have to find another way to have alerts pop up back-to-back if this method isn't stable. Thanks a ton! – rayaantaneja Apr 13 '20 at 11:28