You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI:如何从View body返回遵循协议的视图并处理错误?

SwiftUI网络错误处理方案

你的代码主要存在两个问题:

  1. SwiftUI中直接使用any ErrorableViewProtocol作为子视图会触发类型擦除问题,因为View是泛型协议,existential类型无法直接作为View的子节点;
  2. ErrorableView中的error参数未被使用,且值类型的normal无法直接修改其error属性。

修正后的基础代码

用泛型替代existential类型,同时通过绑定来管理错误状态:

protocol ErrorableViewProtocol: View {
    var error: Error? { get set }
}

struct ErrorableView<Content: ErrorableViewProtocol>: View {
    @Binding var content: Content
    
    var body: some View {
        if let error = content.error {
            ErrorView(error: error)
        } else {
            content
        }
    }
}

// 示例错误视图
struct ErrorView: View {
    let error: Error
    var body: some View {
        VStack {
            Text("出错了")
            Text(error.localizedDescription)
            Button("重试") {
                // 此处添加重试逻辑
            }
        }
    }
}

// 示例符合协议的正常视图
struct NormalView: ErrorableViewProtocol {
    @State var error: Error?
    var body: some View {
        Text("正常展示内容")
    }
}

实用的网络错误处理方案

方案1:当前位置替换显示错误视图

适合在原视图位置直接切换到错误界面,通过状态变量控制视图展示:

struct ContentView: View {
    @State private var networkError: Error?
    @State private var isLoading = false
    
    var body: some View {
        VStack {
            if let error = networkError {
                ErrorView(error: error, retryAction: fetchData)
            } else if isLoading {
                ProgressView("加载中...")
            } else {
                Text("网络请求成功的内容")
            }
        }
        .onAppear(perform: fetchData)
    }
    
    private func fetchData() {
        isLoading = true
        // 模拟网络请求
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            isLoading = false
            // 模拟错误场景
            networkError = NSError(domain: "com.example", code: -1, userInfo: [NSLocalizedDescriptionKey: "网络连接失败"])
        }
    }
}

// 带重试按钮的错误视图
struct ErrorView: View {
    let error: Error
    let retryAction: () -> Void
    
    var body: some View {
        VStack(spacing: 16) {
            Image(systemName: "exclamationmark.triangle")
                .font(.largeTitle)
                .foregroundColor(.orange)
            Text("请求失败")
                .font(.title)
            Text(error.localizedDescription)
                .foregroundColor(.secondary)
                .multilineTextAlignment(.center)
            Button("重试", action: retryAction)
                .buttonStyle(.borderedProminent)
        }
        .padding()
    }
}

方案2:导航到独立错误页面

如果需要跳转至单独的错误详情页,可结合NavigationStack(iOS 16+)使用:

struct ContentView: View {
    @State private var networkError: Error?
    @State private var isLoading = false
    
    var body: some View {
        NavigationStack {
            VStack {
                if isLoading {
                    ProgressView("加载中...")
                } else {
                    Text("正常内容")
                }
            }
            .navigationTitle("首页")
            .navigationDestination(item: $networkError) { error in
                ErrorDetailView(error: error, retryAction: {
                    networkError = nil
                    fetchData()
                })
            }
            .onAppear(perform: fetchData)
        }
    }
    
    private func fetchData() {
        isLoading = true
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            isLoading = false
            networkError = NSError(domain: "com.example", code: -1, userInfo: [NSLocalizedDescriptionKey: "网络连接失败"])
        }
    }
}

// 错误详情页
struct ErrorDetailView: View {
    let error: Error
    let retryAction: () -> Void
    
    var body: some View {
        VStack(spacing: 20) {
            Image(systemName: "wifi.slash")
                .font(.system(size: 60))
                .foregroundColor(.red)
            Text("网络错误")
                .font(.largeTitle)
                .fontWeight(.bold)
            Text(error.localizedDescription)
                .foregroundColor(.gray)
                .padding(.horizontal)
                .multilineTextAlignment(.center)
            Button("返回重试", action: retryAction)
                .buttonStyle(.borderedProminent)
        }
        .navigationTitle("错误提示")
    }
}

// 让Error符合Identifiable协议,支持navigationDestination
extension Error: Identifiable {
    public var id: String { localizedDescription }
}

方案3:用弹出层展示错误视图

通过fullScreenCover或sheet弹出错误提示,不切换页面:

struct ContentView: View {
    @State private var networkError: Error?
    @State private var isLoading = false
    
    var body: some View {
        VStack {
            if isLoading {
                ProgressView("加载中...")
            } else {
                Text("正常内容")
            }
        }
        .fullScreenCover(item: $networkError) { error in
            ErrorPopoverView(error: error) {
                networkError = nil
                fetchData()
            }
        }
        .onAppear(perform: fetchData)
    }
    
    private func fetchData() {
        isLoading = true
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            isLoading = false
            networkError = NSError(domain: "com.example", code: -1, userInfo: [NSLocalizedDescriptionKey: "网络连接失败"])
        }
    }
}

struct ErrorPopoverView: View {
    let error: Error
    let retryAction: () -> Void
    
    var body: some View {
        VStack(spacing: 20) {
            Spacer()
            Image(systemName: "xmark.octagon")
                .font(.system(size: 50))
                .foregroundColor(.red)
            Text("请求失败")
                .font(.title)
            Text(error.localizedDescription)
                .foregroundColor(.secondary)
                .multilineTextAlignment(.center)
                .padding(.horizontal)
            HStack {
                Button("取消", action: retryAction)
                    .buttonStyle(.bordered)
                Button("重试", action: retryAction)
                    .buttonStyle(.borderedProminent)
            }
            Spacer()
        }
        .padding()
        .background(Color.white)
        .cornerRadius(20)
        .padding(.horizontal)
    }
}

内容的提问来源于stack exchange,提问作者Anton Tropashko

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 05:01:47