如何在SwiftUI中实现像AnyView这样的类型擦除结构?

时间:2019-08-06 17:33:44

标签: swiftui

我很好奇SwiftUI中AnyView的默认实现。如何将具有不同泛型的结构放入协议数组?

例如:

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

我的实现方式:

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

编译错误将是“异构集合文字只能被推断为'[Any]';如果是故意的,则添加显式类型注释”。

有人有什么想法吗?

2 个答案:

答案 0 :(得分:0)

这是因为您有一个通用约束。 AnyView没有一般约束。您可以使用基础泛型View实例化它,但是其Body始终被声明为Never。可能有编译器魔术在这里发生,因为我无法使用通用的无约束版本。

答案 1 :(得分:0)

这里是可能方法的演示。它经过简化,但显示了如何完成此操作的通用概念……或至少是一个方向。

完整的可编译工作模块。在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()
    }
}