8
votes

I'm curious about the default implementation of AnyView in SwiftUI. How to put structs with different generic types into a protocol array?

For example:

let a = AnyView(Text("hello"))
let b = AnyView(Image(systemName: "1.circle"))
let genericViews = [a, b] // No compile error

And my implementation:

struct TypeErasedView<V: View>: View {
    private var _view: V
    init(_ view: V) {
        _view = view
    }
    var body: V {
        _view
    }
}

let a = TypeErasedView(Text("Hello"))
let b = TypeErasedView(Image(systemName: "1.circle"))
let genericViews = [a, b] // compile error

The compile error will be "Heterogeneous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional".

Does anyone have any ideas?

2

2 Answers

2
votes

Here is a demo of possible approach. It is simplified, but shows the generic idea of how this might be done... or at least a direction.

Full compilable & working module. Tested on Xcode 11.2 / iOS 13.2

import SwiftUI

private protocol TypeErasing {
    var view: Any { get }
}

private struct TypeEraser<V: View>: TypeErasing {
    let orinal: V
    var view: Any {
        return self.orinal
    }
}

public struct MyAnyView : View {
    public var body: Never {
        get {
            fatalError("Unsupported - don't call this")
        }
    }

    private var eraser: TypeErasing
    public init<V>(_ view: V) where V : View {
        eraser = TypeEraser(orinal: view)
    }

    fileprivate var wrappedView: Any { // << they might have here something specific
        eraser.view
    }

    public typealias Body = Never
}


struct DemoAnyView: View {
    let container: [MyAnyView]
    init() {
        let a = MyAnyView(Text("Hello"))
        let b = MyAnyView(Image(systemName: "1.circle"))
        container = [a, b]
    }

    var body: some View {
        VStack {
            // dynamically restoring types is different question and might be
            // dependent on Apple's internal implementation, but here is
            // just a demo that it works
            container[0].wrappedView as! Text
            container[1].wrappedView as! Image
        }
    }
}

struct DemoAnyView_Previews: PreviewProvider {
    static var previews: some View {
        DemoAnyView()
    }
}
0
votes

It's because there's a generic constraint on yours. AnyView has no generic constraint. You instantiate it with an underlying generic View, but its Body is always declared as Never. There might be compiler magic happening here as I couldn't get a generic constraint-less version to work.