错误处理与用户反馈
在 MVVM 架构中,优雅地处理错误并提供清晰的用户反馈是提升应用健壮性和用户体验的关键环节。本节将探讨如何在 SwiftUI 中实现这一目标。
错误处理的设计原则
1. 错误分类与建模
- 领域错误(如业务逻辑失败)
- 网络错误(如请求超时、服务器错误)
- 本地错误(如数据验证失败、存储失败)
enum AppError: Error, LocalizedError {
case networkError(description: String)
case validationFailed(field: String)
case persistenceError
var errorDescription: String? {
switch self {
case .networkError(let desc):
return "网络错误: \(desc)"
case .validationFailed(let field):
return "\(field) 验证失败"
case .persistenceError:
return "数据存储失败"
}
}
}
2. ViewModel 中的错误处理
class TodoViewModel: ObservableObject {
@Published var error: AppError?
func fetchTodos() {
Task {
do {
let todos = try await apiService.fetchTodos()
// 更新数据...
} catch {
DispatchQueue.main.async {
self.error = error as? AppError
}
}
}
}
}
用户反馈的实现方式
1. 使用 Alert 展示错误
struct TodoListView: View {
@ObservedObject var viewModel: TodoViewModel
var body: some View {
List(viewModel.todos) { todo in
// ...
}
.alert("错误",
isPresented: .constant(viewModel.error != nil),
presenting: viewModel.error) { error in
Button("确定", role: .cancel) {
viewModel.error = nil
}
} message: { error in
Text(error.localizedDescription)
}
}
}
2. 非侵入式 Toast 提示
// 在 ViewModel 中
@Published var toastMessage: String?
// 在视图中
.overlay(alignment: .bottom) {
if let message = viewModel.toastMessage {
Text(message)
.padding()
.background(Color.black.opacity(0.7))
.foregroundColor(.white)
.cornerRadius(10)
.transition(.move(edge: .bottom))
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
viewModel.toastMessage = nil
}
}
}
}
3. 表单验证的即时反馈
struct TodoFormView: View {
@ObservedObject var viewModel: TodoFormViewModel
@State private var showValidationErrors = false
var body: some View {
Form {
TextField("标题", text: $viewModel.title)
.background(
viewModel.isTitleValid ? Color.clear : Color.red.opacity(0.1)
)
if showValidationErrors && !viewModel.isTitleValid {
Text("标题不能为空")
.foregroundColor(.red)
}
// ...
Button("保存") {
if viewModel.validate() {
viewModel.save()
} else {
showValidationErrors = true
}
}
}
}
}
最佳实践建议
- 错误本地化:为所有错误类型提供用户友好的本地化描述
- 错误恢复:尽可能提供恢复选项(如重试按钮)
- 日志记录:在显示用户反馈的同时记录详细错误日志
- 统一处理:创建全局错误处理器来集中管理常见错误
- 避免阻塞:长时间操作应提供进度指示
案例:Todo 应用的错误处理增强
// 增强后的 ViewModel
class EnhancedTodoViewModel: ObservableObject {
enum State {
case idle, loading, loaded([Todo]), failed(AppError)
}
@Published private(set) var state: State = .idle
func loadTodos() {
state = .loading
Task {
do {
let todos = try await repository.fetchTodos()
DispatchQueue.main.async {
self.state = .loaded(todos)
}
} catch {
DispatchQueue.main.async {
self.state = .failed(error as? AppError ?? .networkError(description: "未知错误"))
}
}
}
}
}
// 对应的视图
struct EnhancedTodoView: View {
@ObservedObject var viewModel: EnhancedTodoViewModel
var body: some View {
Group {
switch viewModel.state {
case .idle:
ProgressView()
case .loading:
ProgressView("加载中...")
case .loaded(let todos):
TodoList(todos: todos)
case .failed(let error):
ErrorView(error: error, onRetry: viewModel.loadTodos)
}
}
.onAppear {
viewModel.loadTodos()
}
}
}
通过系统化的错误处理和用户反馈机制,可以显著提升应用的可靠性和用户体验,同时保持代码的可维护性。
