Applies verified fixes from deep audit (concurrency, performance, security, accessibility), standardizes CRUD form buttons to Add/Save pattern, removes .drawingGroup() that broke search bar TextFields, and converts vulnerable .sheet(isPresented:) + if-let patterns to safe presentation to prevent blank white modals. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
113 lines
2.8 KiB
Swift
113 lines
2.8 KiB
Swift
import SwiftUI
|
|
|
|
/// A view that handles loading, error, and success states with automatic error alerts
|
|
///
|
|
/// Example usage:
|
|
/// ```swift
|
|
/// ViewStateHandler(
|
|
/// isLoading: viewModel.isLoading,
|
|
/// error: viewModel.errorMessage,
|
|
/// onRetry: { viewModel.loadData() }
|
|
/// ) {
|
|
/// // Success content
|
|
/// List(items) { item in
|
|
/// Text(item.name)
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
struct ViewStateHandler<Content: View>: View {
|
|
let isLoading: Bool
|
|
let error: String?
|
|
let onRetry: () -> Void
|
|
let content: Content
|
|
|
|
@State private var errorAlert: ErrorAlertInfo? = nil
|
|
|
|
init(
|
|
isLoading: Bool,
|
|
error: String?,
|
|
onRetry: @escaping () -> Void,
|
|
@ViewBuilder content: () -> Content
|
|
) {
|
|
self.isLoading = isLoading
|
|
self.error = error
|
|
self.onRetry = onRetry
|
|
self.content = content()
|
|
}
|
|
|
|
var body: some View {
|
|
ZStack {
|
|
if isLoading {
|
|
ProgressView()
|
|
.scaleEffect(1.5)
|
|
} else {
|
|
content
|
|
}
|
|
}
|
|
.onChange(of: error) { _, errorMessage in
|
|
if let errorMessage = errorMessage, !errorMessage.isEmpty {
|
|
errorAlert = ErrorAlertInfo(message: errorMessage)
|
|
}
|
|
}
|
|
.errorAlert(
|
|
error: errorAlert,
|
|
onRetry: {
|
|
errorAlert = nil
|
|
onRetry()
|
|
},
|
|
onDismiss: {
|
|
errorAlert = nil
|
|
}
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Extension to add automatic error handling to any view
|
|
extension View {
|
|
/// Monitors an error message and shows error alert when it changes
|
|
///
|
|
/// Example usage:
|
|
/// ```swift
|
|
/// Form {
|
|
/// // Form fields
|
|
/// }
|
|
/// .handleErrors(
|
|
/// error: viewModel.errorMessage,
|
|
/// onRetry: { viewModel.submitForm() }
|
|
/// )
|
|
/// ```
|
|
func handleErrors(
|
|
error: String?,
|
|
onRetry: @escaping () -> Void
|
|
) -> some View {
|
|
modifier(ErrorHandlerModifier(error: error, onRetry: onRetry))
|
|
}
|
|
}
|
|
|
|
/// View modifier that handles errors automatically
|
|
private struct ErrorHandlerModifier: ViewModifier {
|
|
let error: String?
|
|
let onRetry: () -> Void
|
|
|
|
@State private var errorAlert: ErrorAlertInfo? = nil
|
|
|
|
func body(content: Content) -> some View {
|
|
content
|
|
.onChange(of: error) { _, errorMessage in
|
|
if let errorMessage = errorMessage, !errorMessage.isEmpty {
|
|
errorAlert = ErrorAlertInfo(message: errorMessage)
|
|
}
|
|
}
|
|
.errorAlert(
|
|
error: errorAlert,
|
|
onRetry: {
|
|
errorAlert = nil
|
|
onRetry()
|
|
},
|
|
onDismiss: {
|
|
errorAlert = nil
|
|
}
|
|
)
|
|
}
|
|
}
|