Skip to content

Instantly share code, notes, and snippets.

@mbernson
Last active January 25, 2024 13:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mbernson/ddd5d3fb964f0690fb98ccff8f9e43b3 to your computer and use it in GitHub Desktop.
Save mbernson/ddd5d3fb964f0690fb98ccff8f9e43b3 to your computer and use it in GitHub Desktop.
Handling of (non-fatal) errors in SwiftUI
import SwiftUI
extension View {
/// Presents an alert when an error is present.
func alert<E: Error>(_ titleKey: LocalizedStringKey, error: Binding<E?>, buttonTitleKey: LocalizedStringKey = "Oke") -> some View {
modifier(ErrorAlert(error: error, titleKey: titleKey, buttonTitleKey: buttonTitleKey))
}
}
private struct ErrorAlert<E: Error>: ViewModifier {
@Binding var error: E?
let titleKey: LocalizedStringKey
let buttonTitleKey: LocalizedStringKey
var isPresented: Bool {
error != nil
}
func body(content: Content) -> some View {
content.alert(titleKey, isPresented: .constant(isPresented)) {
Button(buttonTitleKey, action: dismiss)
} message: {
if let message = error?.localizedDescription {
Text(message)
}
}
}
func dismiss() {
error = nil
}
}
#Preview("Error alert") {
Text(verbatim: "Preview text")
.alert("This is the error title", error: .constant(NonFatalError.preview))
}
import SwiftUI
struct ContentView: View {
@State var error: Error?
@StateObject var model = Model()
var body: some View {
VStack {
Button("Trigger error") {
do {
try model.doTheThing()
} catch {
self.error = error
}
}
}
.alert("Unable to do the thing", error: $error)
}
}
class Model: ObservableObject {
func doTheThing() throws {
throw NonFatalError.couldNotDoTheThing
// Include this line in order to log the error to Firebase Crashlytics
.recordToCrashlytics()
}
}
private extension NonFatalError {
static let couldNotDoTheThing = NonFatalError(code: 1, message: "This is the error message that is shown to the user.")
}
import Foundation
import FirebaseCrashlytics
/// A non fatal error which may be sent to Firebase Crashlytics
struct NonFatalError: Error {
let domain: String
let code: Int
let message: String
private var userInfo: [String: String]
init(domain: String = #fileID, code: Int, message: String) {
self.domain = domain
self.code = code
self.message = message
self.userInfo = ["message": message]
}
func withUserInfo(_ key: String, value: String) -> NonFatalError {
var result = self
result.userInfo[key] = value
return result
}
func withUserInfo(_ key: String, value: Int) -> NonFatalError {
withUserInfo(key, value: String(value))
}
}
extension NonFatalError: LocalizedError {
var errorDescription: String? {
message
}
}
extension NonFatalError {
static let preview = NonFatalError(code: 1, message: "Preview error")
}
extension NonFatalError {
var nsError: NSError {
NSError(domain: domain, code: code, userInfo: userInfo)
}
@discardableResult
func recordToCrashlytics() -> Self {
Crashlytics.crashlytics().record(error: nsError)
return self
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment