Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement a type erased struct like AnyView in SwiftUI?

Tags:

swiftui

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?

like image 753
nalydadad Avatar asked Aug 06 '19 17:08

nalydadad


People also ask

How to avoid AnyView SwiftUI?

All we have to do is to add the attribute to our property or function and remove the return statements. That's the same mechanism the body of a SwiftUI view uses. The only difference is, that we explicitly have to add the ViewBuilder attribute on our own properties and functions.

How do I return different views?

The body property of any SwiftUI automatically gets the ability to return different views thanks to a special attributed called @ViewBuilder . This is implemented using Swift's result builder system, and it understands how to present two different views depending on our app's state.

How do I return a view in SwiftUI?

on the -> View return type.

What is some view in SwiftUI?

So, in SwiftUI case, “some View” means that the body will always be implementing the View protocol, but the concrete implementation type does not need to be known by the caller. As explained in Swift documentation on Opaque Types: You can think of an opaque type like being the reverse of a generic type.


1 Answers

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()
    }
}
like image 159
Asperi Avatar answered Nov 15 '22 08:11

Asperi