SwiftUI provides a solution to present LocalizedError
func alert<E, A>(
isPresented: Binding<Bool>,
error: E?,
@ViewBuilder actions: () -> A
) -> some View where E : LocalizedError, A : View
You can create a simple enum
that can present any standard Error
or custom errors.
enum LocalError: LocalizedError {
//Use for any built in error
case error(Error)
//Use for something custom
case invalidId
var errorDescription: String? {
switch self {
case .error(let error):
return error.localizedDescription
case .invalidId:
return "\(self)"
}
}
var recoverySuggestion: String? {
switch self {
case .error(let error):
let nsError = error as NSError
return nsError.localizedRecoverySuggestion
default:
return nil
}
}
}
Then you can modify your progress enum.
enum ProgressState {
case idle
case loading
case completed(Model)
case error(Error)
You can then trigger an alert
(or sheet) when that state is triggered
switch state {
case .error(let error):
Text(state.stateName)
.task {
alert = (true, .error(error))
}
default :
Text(state.stateName)
}
Here is the full code.
import SwiftUI
struct FooView: View {
enum ProgressState {
case idle
case loading
case completed(Model)
case error(LocalError)
var stateName: String {
switch self {
case .completed(_):
return "Complete"
case .error(_):
return "Something went wrong"
default:
return "\(self)"
}
}
}
@State private var state: ProgressState = .idle
@State private var alert: (isPresented: Bool, error: LocalError?) = (false, nil)
var body: some View {
Group {
switch state {
case .error(let error):
Text(state.stateName)
.task {
alert = (true, error)
}
default :
Text(state.stateName)
}
}.alert(isPresented: $alert.isPresented, error: alert.error) {
Button("Ok") {
alert = (false, nil)
}
}
.task {
try? await Task.sleep(for: .seconds(1))
state = .error(.invalidId)
}
}
struct Model {
}
enum LocalError: LocalizedError {
//Use for any built in error
case error(Error)
//Use for something custom
case invalidId
var errorDescription: String? {
switch self {
case .error(let error):
return error.localizedDescription
case .invalidId:
return "\(self)"
}
}
var recoverySuggestion: String? {
switch self {
case .error(let error):
let nsError = error as NSError
return nsError.localizedRecoverySuggestion
default:
return nil
}
}
}
}
#Preview {
FooView()
}
This provides an independent alert
variable so the presentation of the .alert
does not conflict with the View
behind it or make any unsafe assumptions.