错误处理与加载状态
在图表应用中,数据通常来自外部数据源(如API、数据库或本地文件)。网络请求可能失败,数据可能不完整或格式错误,因此良好的错误处理和加载状态管理至关重要。本章将介绍如何在SwiftUI图表中优雅地处理这些情况。
1. 加载状态管理
基本加载状态枚举
enum LoadingState<Value> {
case idle // 初始状态
case loading // 加载中
case success(Value) // 加载成功
case failure(Error) // 加载失败
}
在视图中使用加载状态
struct WeatherChartView: View {
@StateObject var viewModel = WeatherViewModel()
var body: some View {
Group {
switch viewModel.loadingState {
case .idle:
Color.clear.onAppear { viewModel.fetchData() }
case .loading:
ProgressView("加载天气数据...")
.frame(maxWidth: .infinity, maxHeight: .infinity)
case .success(let data):
Chart { /* 渲染图表 */ }
case .failure(let error):
ErrorView(error: error, retryAction: viewModel.fetchData)
}
}
}
}
2. 错误处理策略
常见错误类型
enum DataError: Error, LocalizedError {
case invalidURL
case networkError(Error)
case decodingError(Error)
case emptyData
var errorDescription: String? {
switch self {
case .invalidURL: return "无效的URL"
case .networkError(let error): return "网络错误: \(error.localizedDescription)"
case .decodingError(let error): return "数据解析错误: \(error.localizedDescription)"
case .emptyData: return "没有可用数据"
}
}
}
错误视图组件
struct ErrorView: View {
let error: Error
let retryAction: () -> Void
var body: some View {
VStack {
Image(systemName: "exclamationmark.triangle")
.font(.largeTitle)
.foregroundColor(.orange)
Text("加载失败")
.font(.title)
Text(error.localizedDescription)
.multilineTextAlignment(.center)
.padding()
Button("重试", action: retryAction)
.buttonStyle(.borderedProminent)
}
.padding()
}
}
3. 数据预处理与回退方案
处理不完整数据
extension WeatherData {
func sanitized() -> WeatherData {
// 过滤掉无效数据点
let validPoints = dataPoints.filter { $0.temperature != -999 }
return WeatherData(points: validPoints.isEmpty ? [.default] : validPoints)
}
}
图表中的占位数据
Chart {
if data.points.isEmpty {
RuleMark(y: .value("平均值", 0))
.annotation {
Text("无可用数据")
.font(.caption)
.foregroundColor(.secondary)
}
} else {
ForEach(data.points) { point in
LineMark(x: ..., y: ...)
}
}
}
4. 最佳实践
- 渐进式加载:对于大数据集,考虑分页加载或增量渲染
- 本地缓存:实现本地缓存机制,在网络不可用时显示上次成功加载的数据
- 超时处理:为网络请求设置合理的超时时间(建议15-30秒)
- 错误日志:记录错误信息以便后续分析
- 用户反馈:重要的错误(如权限问题)应该明确提示用户解决
案例实现
class StockViewModel: ObservableObject {
@Published var loadingState: LoadingState<[StockData]> = .idle
@MainActor
func fetchData() async {
loadingState = .loading
do {
let data = try await StockAPI.fetchLatest()
loadingState = .success(data.sanitized())
} catch {
loadingState = .failure(DataError.networkError(error))
}
}
}
struct StockChartView: View {
@StateObject var viewModel = StockViewModel()
@State var showErrorDetails = false
var body: some View {
VStack {
switch viewModel.loadingState {
case .success(let data):
StockChart(data: data)
.frame(height: 300)
case .failure(let error):
VStack {
ErrorView(error: error) {
Task { await viewModel.fetchData() }
}
if showErrorDetails {
Text(error.localizedDescription)
.font(.caption)
}
}
default:
ProgressView()
}
}
.task { await viewModel.fetchData() }
.toolbar {
if case .failure = viewModel.loadingState {
Button {
showErrorDetails.toggle()
} label: {
Image(systemName: "info.circle")
}
}
}
}
}
通过以上方法,您可以创建健壮的图表应用,在各种数据状态下都能提供良好的用户体验。
