Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Core Data and SwiftUI polymorphism issues

Tags:

swift

swiftui

Having troubles putting down together SwiftUI and generic types for handling Core Data.

Consider following example:

enter image description here

Parent is abstract. Foo and Bar are children of Parent and they have some custom attributes.

Now what I want to do, is roughly that:

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()
        }
    }
}

I would want to add polymorphic builder to my core data entities that shape data or build views, that confirm to common interface so I can use them without casting/typing.

Problem that compiler throws errors if I try to modify generated Core data entity directly not through extension, and confirming to protocol though extension doesn't allow overriding.

like image 320
Pavel Gatilov Avatar asked May 03 '20 18:05

Pavel Gatilov


1 Answers

Ok, this is head-breaking (at least for Preview, which gone crazy), but it works in run-time. Tested with Xcode 11.4 / iOS 13.4.

As we need to do all in extension the idea is to use dispatching via Obj-C messaging, the one actually available pass to override implementation under such requirements.

Note: use Simulator or Device

demo

Complete test module

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)
        ])
    }
}
like image 90
Asperi Avatar answered Nov 08 '22 17:11

Asperi