Aim
I would like to display an alert, when the user dismisses the alert the next alert should be displayed.
If the user doesn't dismiss the first alert, the 2nd alert should wait till the user dismisses the first alert
Functionality like back pressure.
My Attempt:
Given below is code where I have attempted to do it based on an @ObservedObject and @Published property.
Problem:
Alerts are displayed but they don't wait for the alert to be dismissed.
Code:
ContentView:
Code Block swift import SwiftUI struct ContentView: View { @ObservedObject var model = Model() var body: some View { Text("Hello World!") .alert(item: $model.employee, content: makeAlert(forEmployee:)) } private func makeAlert(forEmployee employee: Employee) -> Alert { let dismissButton = Alert.Button.cancel((Text("Ok"))) return Alert(title: Text("New Employee"), message: Text(employee.name), dismissButton: dismissButton) } }
Model:
Code Block swift import Foundation class Model : ObservableObject { @Published var employee : Employee? private var index = 0 private lazy var timer = { Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { [weak self] timer in self?.createNewEmployee() print("fired for \(self?.employee?.name ?? "none")") } }() init() { timer.fire() } func createNewEmployee() { guard let unicodeScalar = UnicodeScalar(index + 65) else { return } let name = String(Character(unicodeScalar)) employee = Employee(id: index, name: name) index += 1 } }
Employee:
Code Block swift import Foundation class Employee : Identifiable { var id : Int var name : String init(id: Int, name: String) { self.id = id self.name = name } }
I wanted to do this because I wanted to publish errors one after the other.
Given below is my solution:
Given below is my solution:
Code:
Model
Code Block swift import Foundation class Model : ObservableObject { @Published var error : Error? private lazy var timer : Timer = Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { timer in self.error = self.makeNextError() } init() { self.error = Error.networkConnectionUnavailable timer.fire() } private func makeNextError() -> Error { let nextError : Error switch error { case .networkConnectionUnavailable: nextError = .bluetoothUnavailable case .bluetoothUnavailable: nextError = .cameraUnavailable case .cameraUnavailable, .none: nextError = .networkConnectionUnavailable } return nextError } } extension Model { enum Error :String, Swift.Error, CustomStringConvertible { case networkConnectionUnavailable = "Network connection unavailable" case bluetoothUnavailable = "Bluetooth Unavailable" case cameraUnavailable = "Camera Unavailable" var description : String { rawValue } var localizedDescription : String { rawValue } } }
ErrorViewModel
Code Block swift import Foundation import Combine class ErrorViewModel : ObservableObject { struct ErrorWrapper : Identifiable { private static var sequenceID = 0 let id : Int let error : Model.Error init(error: Model.Error) { Self.sequenceID += 1 id = Self.sequenceID self.error = error } } var errorWrappers = [ErrorWrapper]() @Published var firstErrorWrapper : ErrorWrapper? var canceller : AnyCancellable? init(errorPublisher: Published<Model.Error?>.Publisher) { canceller = errorPublisher.sink { [self] in guard let error = $0 else { return } let isEmpty = self.errorWrappers.isEmpty let errorWrapper = ErrorWrapper(error: error) errorWrappers.append(errorWrapper) if isEmpty { firstErrorWrapper = errorWrapper } print("appended error \(errorWrapper.id)") } } func consumeError() { DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [self] in errorWrappers.removeFirst() firstErrorWrapper = errorWrappers.first } } }