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

如何在SwiftUI中包装带有可抛出初始化器的UIKit视图?

在SwiftUI中包装初始化器可抛出错误的UIKit组件的可行方案

问题背景

我参考相关资料创建了通用的UIKit视图包装结构体Wrap,代码如下:

struct Wrap<Wrapped: UIView>: UIViewRepresentable {
    typealias Updater = (Wrapped, Context) -> Void

    var makeView: () -> Wrapped
    var update: (Wrapped, Context) -> Void

    init(_ makeView: @escaping @autoclosure () -> Wrapped,
         updater update: @escaping Updater) {
        self.makeView = makeView
        self.update = update
    }

    func makeUIView(context: Context) -> Wrapped {
        makeView()
    }

    func updateUIView(_ view: Wrapped, context: Context) {
        update(view, context)
    }
}

extension Wrap {
    init(_ makeView: @escaping @autoclosure () -> Wrapped,
         updater update: @escaping (Wrapped) -> Void) {
        self.makeView = makeView
        self.update = { view, _ in update(view) }
    }

    init(_ makeView: @escaping @autoclosure () -> Wrapped) {
        self.makeView = makeView
        self.update = { _, _ in }
    }
}

但当UIKit视图的初始化器可抛出错误时,在SwiftUI中使用会编译失败,例如这个自定义UIView:

final class MyView: UIView {

    init(text: String) throws {
        throw NSError(domain: NSOSStatusErrorDomain, code: 20)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

Xcode编译错误

我知道SwiftUI的声明式body不直接支持这种可抛出的初始化模式,但有时不得不依赖第三方模块中这类视图。我想探索实用的包装方案,比如:

  • 捕获错误并替换为EmptyView或自定义备选视图
  • 将错误处理移至视图模型,注入非抛出包装器
  • 扩展UIViewRepresentable以接收Result<UIView,Error>

可行方案

方案1:捕获错误并展示备选视图

创建一个支持可抛出初始化的包装器,内部处理错误,返回正常视图或备选视图:

struct ThrowingWrap<Wrapped: UIView, Fallback: View>: View {
    typealias MakeView = () throws -> Wrapped
    typealias Updater = (Wrapped, Context) -> Void
    
    private let makeView: MakeView
    private let update: Updater
    private let fallback: (Error) -> Fallback
    
    init(
        _ makeView: @escaping MakeView,
        updater update: @escaping Updater = { _, _ in },
        @ViewBuilder fallback: @escaping (Error) -> Fallback
    ) {
        self.makeView = makeView
        self.update = update
        self.fallback = fallback
    }
    
    var body: some View {
        Group {
            if let view = try? makeView() {
                Wrap(view, updater: update)
            } else if let error = try? Result(catching: makeView).failure {
                fallback(error)
            } else {
                fallback(NSError(domain: "UnknownError", code: -1))
            }
        }
    }
}

// 使用示例
struct ContentView: View {
    var body: some View {
        ThrowingWrap({ try MyView(text: "test") }) { error in
            Text("初始化失败:\(error.localizedDescription)")
                .foregroundColor(.red)
        }
    }
}

这种方式直接在视图层处理错误,适合简单场景,能快速替换为备选UI。

方案2:视图模型层预处理,注入非抛出实例

把可抛出的初始化逻辑移到视图模型中,提前处理错误,将成功创建的视图实例注入SwiftUI视图:

class MyViewModel: ObservableObject {
    @Published var myView: MyView?
    @Published var error: Error?
    
    func loadView(text: String) {
        do {
            myView = try MyView(text: text)
            error = nil
        } catch {
            myView = nil
            error = error
        }
    }
}

struct ContentView: View {
    @StateObject private var viewModel = MyViewModel()
    
    var body: some View {
        VStack {
            if let view = viewModel.myView {
                Wrap(view)
            } else if let error = viewModel.error {
                Text("加载失败:\(error.localizedDescription)")
            } else {
                ProgressView("加载中...")
            }
        }
        .onAppear {
            viewModel.loadView(text: "test")
        }
    }
}

这种方式符合MVVM架构,错误逻辑集中在视图模型,适合复杂场景,还能添加加载状态等交互。

方案3:扩展UIViewRepresentable支持Result类型

创建一个基于Result的包装器,明确处理成功/失败状态:

struct ResultWrap<Wrapped: UIView, FailureView: View>: UIViewRepresentable {
    typealias Updater = (Wrapped, Context) -> Void
    
    private let result: Result<Wrapped, Error>
    private let update: Updater
    private let failureView: (Error) -> FailureView
    
    init(
        result: Result<Wrapped, Error>,
        updater update: @escaping Updater = { _, _ in },
        @ViewBuilder failureView: @escaping (Error) -> FailureView
    ) {
        self.result = result
        self.update = update
        self.failureView = failureView
    }
    
    func makeUIView(context: Context) -> UIView {
        switch result {
        case .success(let view):
            return view
        case .failure(let error):
            let hostingView = UIHostingController(rootView: failureView(error)).view
            hostingView?.translatesAutoresizingMaskIntoConstraints = false
            return hostingView ?? UIView()
        }
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {
        if let wrappedView = uiView as? Wrapped {
            update(wrappedView, context)
        }
    }
}

// 使用示例
struct ContentView: View {
    var body: some View {
        ResultWrap(
            result: Result { try MyView(text: "test") },
            failureView: { error in
                Text("错误:\(error.localizedDescription)")
            }
        )
        .frame(width: 200, height: 200)
    }
}

这种方式直接在UIViewRepresentable层面处理Result,保持了和原生协议的对齐,适合需要完全控制视图创建流程的场景。


内容的提问来源于stack exchange,提问作者Dielson Sales

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 15:24:56