通过 Swift 泛型初始化 SwiftUI 视图

Posted

技术标签:

【中文标题】通过 Swift 泛型初始化 SwiftUI 视图【英文标题】:Initialising a SwiftUI View via a Swift generic 【发布时间】:2021-04-29 09:11:17 【问题描述】:

我正在将 Pull To Refresh 添加到基于 SwiftUI 的应用程序中。为此,我不得不退回到 UIKit。由于我想跨多个表和数据模型(使用共享协议)使用 Pull To Refresh,因此我想使用泛型。

我传入了我的dataModel的类型,我还想传入我想在scrollView中实例化的SwiftUI View的类型。

但是,当我添加我声明支持协议“视图”的通用类型 V 时,我在代码尝试实例化视图时遇到编译错误。

类型“V”没有成员“init”

如果我放弃通用类型“V”,而是硬编码一个特定的 SwiftUI 视图类型来实例化,比如 DataView,这一切都很好 - 但这并没有给我所需的视图类型的灵活性。

请问我该如何解决这个编译错误?

import SwiftUI

struct GenericRefreshView<T, V>: UIViewRepresentable where T:ObservableObject, T:dataModel, V: View 

    @EnvironmentObject var dataModel: T

    func makeCoordinator() -> Coordinator 
        Coordinator(self, regs: dataModel)
    

    let size: CGSize

    func makeUIView(context: Context) -> UIScrollView 
        let scrollView = UIScrollView()
        scrollView.refreshControl = UIRefreshControl()
        scrollView.refreshControl?.addTarget(context.coordinator, action: #selector(Coordinator.handleRefreshControl(sender:)), for: .valueChanged)

        // *******************************************************************************
        let refreshVC = UIHostingController(rootView: V()) // Type 'V' has no member 'init'
        // *******************************************************************************

        refreshVC.view.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)

        scrollView.addSubview(refreshVC.view)

        return scrollView
    

    func updateUIView(_ uiView: UIScrollView, context: Context) 

    class Coordinator: NSObject 
        var refreshScrollView: GenericRefreshView
        var dataModel: T

        init(_ refreshScrollView: GenericRefreshView, dataM: T) 
            self.refreshScrollView = refreshScrollView
            self.dataModel = dataM
        

        @objc func handleRefreshControl (sender: UIRefreshControl) 
            self.dataModel.refresh()
            sender.endRefreshing()
        
    

【问题讨论】:

【参考方案1】:

你可以要求它在外部注入,比如

struct GenericRefreshView<T, V>: UIViewRepresentable where T:ObservableObject, T:dataModel, V: View 

    @EnvironmentObject var dataModel: T
    let contentView: V                      // here !!
    // let builder: () -> V                 // alternate, as view builder

    func makeCoordinator() -> Coordinator 
        Coordinator(self, regs: dataModel)
    

    let size: CGSize

    func makeUIView(context: Context) -> UIScrollView 
        let scrollView = UIScrollView()
        scrollView.refreshControl = UIRefreshControl()
        scrollView.refreshControl?.addTarget(context.coordinator, action: #selector(Coordinator.handleRefreshControl(sender:)), for: .valueChanged)

        let refreshVC = UIHostingController(rootView: self.contentView)    // here !!

   ...

更新:简化以避免遗漏部分,这里是使用演示

struct DemoView: View 
    var body: some View 
        GenericRefreshView(dataModel: TestViewModel(), 
                         contentView: Text("Demo"), 
                                size: CGSize(width: 100, height: 100))
    


struct GenericRefreshView<T, V>: UIViewRepresentable where T:ObservableObject, V: View 

    @ObservedObject var dataModel: T     // << EnvironmentObject is internal, so cannot be inferred from input arguments, so not usable here
    let contentView: V
    let size: CGSize

【讨论】:

感谢您的回复。对不起,我不关注。您是说我应该将已经实例化的视图作为参数传递吗?在那种情况下,我不会失去通用方面吗?你能告诉我怎么称呼它吗?谢谢。 感谢您的更新,我现在已经按照您的建议工作了 - 非常感谢。我希望必须指定 GenericRefreshView 类型,但显然 Swift 算出来了!干杯。

以上是关于通过 Swift 泛型初始化 SwiftUI 视图的主要内容,如果未能解决你的问题,请参考以下文章

如何通过单击按钮在 Swift UI 中从一个视图导航到另一个视图

Swift:SwiftUI 视图的多个条件的内联评估

SwiftUI 将 Swift 代码作为参数传递给可重用视图

如何使用 SwiftUI 按按钮移动到另一个视图 - swift 5.5

如何在 SwiftUI 中使用 Toast-Swift?

SwiftUI:如何在父视图中初始化新的 StateObject?