HàPhan 河

[SwiftUI] Presenting an Alert or a View from a View

Any View in SwiftUI framework could present an Alert or another View in modal presentation style.

presenting

Absolutely, to present them we need create Them first. :)
An Alert.

Alert(title: Text("Alert")
    , message: Text("Information is data, data is king")
     , dismissButton: .default(Text("Ok")))

and a view to be presented

struct PresentedView : View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    
    var body: some View {
        VStack {
            Button("Dismiss") {
                self.presentationMode.wrappedValue.dismiss()
            }.padding()
        } 
    }
}

Then we try to present them in our main view

struct ContentView: View {
    @State var showAlert: Bool = false
    @State var presentingView: Bool = false
    
    var body: some View {
        VStack {
            Button("Present Alert") {
                self.showAlert.toggle()
            }.alert(isPresented: $showAlert) { () -> Alert in
                Alert(title: Text("Alert")
                    , message: Text("Information is data, data is king")
                    , dismissButton: .default(Text("Ok")))
            }.padding()
            
            Button("Present View") {
                self.presentingView.toggle()
            }.sheet(isPresented: $presentingView) { () -> PresentedView in
                PresentedView()
            }
        }
        
    }
}

The code is easy to understand. When button was tapped, the @State boolean variable will be flipped. So alert will be shown or view will be presented.

There's a big concern here. How could we make the content of alert dynamic, and pass additional data to presented view? Hmm...

Let's check document a bit https://developer.apple.com/documentation/swiftui/view. I found something:
Screen-Shot-2019-10-27-at-1.17.21-PM

Document says that, we could not only control Alert/Sheet by Bool binding, but also Optional binding. Yay! As I guess, the presented view will be presented when optional binding has value. Try it now...

We need to create two class that implement Identifiable protocol. Those class is our Optional Binding data.

class AlertContent : Identifiable {
    var message: String
    init(_ msg: String) {
        self.message = msg
    }
}

class PresentContent : ObservableObject, Identifiable {
    typealias PresentData = String
    var message: PresentData
    init(_ msg: String) {
        self.message = msg
    }
}

Then we modify of our views a little

struct ContentView: View {
    @State var showAlert: AlertContent? = nil //content of alert
    @State var presentingData: PresentContent? = nil //content of present
    
    var body: some View {
        VStack {
            Button("Present Alert") {
                self.showAlert = AlertContent("\(Date().description)")
                
            }.alert(item: $showAlert) { item -> Alert in
                Alert(title: Text("Alert")
                    , message: Text("\(item.message)")
                    , dismissButton: .default(Text("Ok")))
            }.padding()
            
            Button("Present View") {
                self.presentingData = PresentContent("Data is passed from content View")
            }.sheet(item: $presentingData) { item -> PresentedView in
                PresentedView(item) // should define init method for our presented view
            }
        }
        
    }
}

struct PresentedView : View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    @ObservedObject var content: PresentContent
    
    init(_ data: PresentContent) {
        self.content = data
    }
    var body: some View {
        VStack {
            Text("\(content.message)")
            .padding()
            Button("Dismiss") {
                self.presentationMode.wrappedValue.dismiss()
            }.padding()
        }
    }
}

Okay, that's some basic about presenting and alerting users with SwiftUI. Our @State variables could be grouped in one @ObservedObject var viewModel: ContentViewModel class for make our code cleaner. Then we could do logic-only with Combine framework easily.

That's all for today.
Happy Coding.

Comments