Jack
Jack

Reputation: 247

How to present alert by code or in custom class in swiftui

I have searched all document about how to present alert in swiftui, but they all show code like this:

Button(action: {
            self.showingAlert = true
}) {
            Text("Show Alert")
}
.alert(isPresented:$showingAlert) {
    Alert(title: Text("title"))
}

Which means that alert must be used for button.

How can I use alert in my custom class like handle http request and then show error alert like this:

class A{
    func getDate(){
        alert("error")
    }
}

Upvotes: 3

Views: 4713

Answers (3)

Arvind Patel
Arvind Patel

Reputation: 501

We need to Create a UserState class and conform to the ObservableObject protocol. Classes that conform to the ObservableObject protocol can use SwiftUI’s @Published property wrapper to automatically announce changes to properties, so that any views using the object get their body property reinvoked and stay in sync with their data.

//MARK:- Step:1 On SceneDelegate
// set environmentObject
let userState = UserState()
let contentView = ContentView().environmentObject(userState)


//MARK:- Step:2 Create UserState Class

class UserState: ObservableObject {
    @Published var sessionExpired: Bool = false
}

//MARK:- Step:3 Create a ContentView

struct ContentView: View {
    
    // Step:3 Create a userState EnvironmentObject
    @EnvironmentObject var userState: UserState
    @State private var isShowSecondView = false
    @State private var showingAlert = false
    
    var body: some View {
        NavigationView {
            NavigationLink(destination: SecondView(),  isActive: $isShowSecondView) {
                
                ZStack(alignment: .center) {
                    
                    Button("Show Second") {
                        isShowSecondView.toggle()
                        
                    }
                    
                }
                .alert(isPresented: $showingAlert) {
                    Alert(title: Text("Alert"), message: Text("Session Expired"), dismissButton: .default(Text("Got it!")))
                }
                .onChange(of:  userState.sessionExpired, perform: { value in // Step:4 in onChange we need to check sessionExpired or not
                    print("onChange")
                    if userState.sessionExpired {
                        showingAlert = true
                    }
                })
            }
        }
    }
}

//MARK:-

struct SecondView: View {
    
    @EnvironmentObject var userState: UserState
    
    var body: some View {
        ZStack(alignment: .center) {
            
            Button("Show Alert Message") {  // Step:5 -  Here I changed flag then alert will present
                userState.sessionExpired.toggle()
            }
        }
    }
}

Upvotes: 2

Jacob Jidell
Jacob Jidell

Reputation: 2792

Another way to solve it, if you want to use custom error description, then create your custom error which conforms to the Identifiable protocol like below:

enum NetworkError: LocalizedError, Identifiable {
    case notFound
    case serverError(responseCode: Int)
    case underlyingError(Error)
    case unknown

    var id: String { localizedDescription }

    var errorDescription: String? {
        switch self {
        case .notFound: return "Not found"
        case .serverError(let responseCode): return "Server error \(responseCode)"
        case .underlyingError(let error): return error.localizedDescription
        case .unknown: return "Unknown error"
        }
    }
}

ViewModel

Inside your viewModel, then set the error you'll get (from your api etc.) and set it to your published error.

class YourViewModel: ObservableObject {
    @Published var persons: [Person]
    @Published var error: NetworkError? // <- Set your error from your api here

    private var subscriptions = Set<AnyCancellable>()

    init() {
        fetch()
    }

    func fetch() {
        api.people
            .sink(receiveCompletion: { [weak self] (completion) in
                if case .failure(let error) = completion {
                    self?.error = error // An error occured
                }
        }, receiveValue: { [weak self] (persons) in
            self?.onReceive(persons)
            self?.error = nil
        })
        .store(in: &subscriptions)
    }

   //...//
}

View

The published error will trigger the alert and you'll be able to present the errors' localizedDescription within the closure.

struct MainView: View {
    @ObservedObject var viewModel = YourViewModel()

    var body: some View {
        Text(viewModel.persons.first?.name ?? "N/A")
            .alert(item: self.$viewModel.error) { error in
                Alert(title: Text("Error"),
                      message: Text(error.localizedDescription),
                      dismissButton: .cancel())
        }
    }
}

Upvotes: 2

Schaheer Saleem
Schaheer Saleem

Reputation: 529

Create a class and conform to the ObservableObject protocol (Anything that conforms to ObservableObject can be used inside SwiftUI, and publish announcements when its values have changed so the user interface can be updated):

class A: ObservableObject {

    @Published var showAlert = false

    func buttonTapped() {
        //handle request and then set to true to show the alert
        self.showAlert = true
    }

}

Your View:

struct ContentView: View {

    @ObservedObject private var viewModel = A()

    var body: some View {

        Button(action: {
            self.viewModel.buttonTapped()
        }) {
            Text("Show Alert")
        }
        .alert(isPresented: $viewModel.showAlert, content: { () -> Alert in
            Alert(title: Text("Error"), message: Text("Please try again"), dismissButton: .default(Text("Okay")))
        })
    }

}

Upvotes: 3

Related Questions