核心数据和SwiftUI多态性问题

10

在将SwiftUI和处理Core Data的通用类型组合在一起时遇到了困难。

考虑以下示例:

enter image description here

Parent是抽象的。 FooBarParent的子类,它们具有一些自定义属性。

现在我想做的大致是:

protocol EntityWithView {
    associatedtype T: View
    func buildView() -> T
}

extension Parent: EntityWithView {
    func buildView() -> some View {
        fatalError("Re-implement in child")
    }
}

extension Foo {
    override func buildView() -> some View {
        return Text(footribute)
    }
}

extension Bar {
    override func buildView() -> some View {
        return Text(atrribar)
    }
}

struct ViewThatUsesCoreDataAsModel: View {
    let entities: [Parent]

    var body: some View {
        ForEach(entities) { entity in
            entity.buildView()
        }
    }
}

我希望为我的核心数据实体添加多态构建器,以形成数据或构建视图,这些实体符合通用接口,因此我可以在不需要强制类型转换的情况下使用它们。

问题是,如果我尝试直接修改生成的核心数据实体而不是通过扩展进行修改,则编译器会抛出错误,而通过扩展确认协议不允许覆盖。

1个回答

1

好的,这很费脑子(至少对于Preview来说,它变得疯狂了),但在运行时它是有效的。已在Xcode 11.4 / iOS 13.4中进行了测试。

由于我们需要在扩展中完成所有操作,所以想法是通过Obj-C消息传递来进行调度,实际可用的一种方法是在此类要求下覆盖实现。

注意:请使用模拟器或设备

demo

完整的测试模块

protocol EntityWithView {
    associatedtype T: View
    var buildView: T { get }
}

extension Parent {
    // allows to use Objective-C run-time messaging by complete
    // type erasing.
    // By convention subclasses
    @objc func generateView() -> Any {
        AnyView(EmptyView()) // << safe
        //fatalError("stub in base") // << alternate
    }
}

extension Parent: EntityWithView {
    var buildView: some View {
        // restory SwiftUI view type from dispatched message
        guard let view = self.generateView() as? AnyView else {
            fatalError("Dev error - subview must generate AnyView")
        }
        return view
    }
}

extension Foo {
    @objc override func generateView() -> Any {
        AnyView(Text(footribute ?? ""))
    }
}

extension Bar {
    @objc override func generateView() -> Any {
        AnyView(Text(attribar ?? ""))
    }
}

struct ViewThatUsesCoreDataAsModel: View {
    let entities: [Parent]

    var body: some View {
        VStack {
            ForEach(entities, id: \.self) { entity in
                entity.buildView
            }
        }
    }
}

struct DemoGeneratingViewInCoreDataExtension: View {
    @Environment(\.managedObjectContext) var context
    var body: some View {
        ViewThatUsesCoreDataAsModel(entities: [
           Foo(context: context), 
           Bar(context: context)
        ])
    }
}

谢谢你的回答,我并不是真的在寻找@objc override的解决方案,这有点抵消了使用泛型和协议的整个意义。在你的解决方案中,如果你要使用objc,你可以只在父类中声明方法,并在子类中重写它。 - Pavel Gatilov
@PavelGatilov,对我来说这是一个可行的解决方案。如果有人发现其他解决方案,我会非常高兴地看到。 - Asperi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接