How to show alerts one after the other?

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
    }
}


Answered by newwbee in 620621022
I wanted to do this because I wanted to publish errors one after the other.

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
}
}
}

Accepted Answer
I wanted to do this because I wanted to publish errors one after the other.

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
}
}
}

Here is the view code, I couldn't post it in the same post, as it exceeded text limit:

ContentView

Code Block swift
import SwiftUI
import Combine
struct ContentView: View {
@StateObject var viewModel : ErrorViewModel
init(model: Model) {
_viewModel = .init(wrappedValue: ErrorViewModel(errorPublisher: model.$error))
}
var body: some View {
Text("Hello world!")
.alert(item: $viewModel.firstErrorWrapper, content: makeAlert(errorWrapper:))
}
private func makeAlert(errorWrapper: ErrorViewModel.ErrorWrapper) -> Alert {
let cancelButton = Alert.Button.cancel {
viewModel.consumeError()
}
let id = errorWrapper.id
let description = errorWrapper.error.localizedDescription
let text = "\(id) - \(description)"
let alert = Alert(title: Text("Error"),
message: Text(text),
dismissButton: cancelButton)
return alert
}
}

How to show alerts one after the other?
 
 
Q