AnyView 是 SwiftUI 提供的一个类型擦除容器(Type Eraser),用于包装不同类型的视图(View),使它们变成同一种可以统一处理的具体类型。
在SwiftUI中,所有视图(比如Text、Image、VSStack或者自定义的视图)都遵循View协议,但是View是一个带有关联类型的协议(associatedtype Body)。
Swift中带有关联类型的协议,不能直接当作普通变量使用。
例如:
let view: View = Text("Hello") // Use of protocol 'View' as a type must be written 'any View'; this will be an error in a future Swift language mode
因为View是一个类型,所以不能作为具体类型存在,否则会提示协议View作为类型必须写为any View,在未来Swift语言模式中将会出现报错。
AnyView的作用
AnyView可以解决这个问题,它是一个可以封装任何View的容器,只需要把视图封装进AnyView,就可以像处理具体类型一样处理它。
import SwiftUI
struct ContentView: View {
func makeConditionalView(condition: Bool) -> AnyView {
if condition {
return AnyView(Text("Hello"))
} else {
return AnyView(Image(systemName: "star"))
}
}
var body: some View {
VStack {
makeConditionalView(condition: false)
}
.padding()
}
}
如果不使用AnyView,这个函数就没有办法写出来,因为Text和Image是两个不同的类型,但函数要返回同一种类型。
可以将AnyView类比为,不能把Int和String存在 [Any<T>] 的数组中,因为它们不是同类型。
也不能把Text和Image 存在 [some View] 中,它们也不是同一个具体类型。
就需要使用AnyView将它们包装起来,变成一个统一类型:
let views: [AnyView] = [
AnyView(Text("Hi")),
AnyView(Image(systemName: "star")),
AnyView(VStack { Text("Nested") })
]
使用场景
1、配合AppKit设置视图类型:
class ToolbarWindow: NSWindow {
init(rectOrigin rect: CGPoint,rectSize size: CGSize, ViewBuilder: () -> any View) {
// 先创建内容视图
let swiftUIView = ViewBuilder()
let hostingControllerView = NSHostingController(rootView: swiftUIView) // 报错,提示:Type 'any View' cannot conform to 'View'
}
}
使用NSHostingController将SwiftUI视图转换为AppKit视图时,因为any View是一个协议,所以不能传入NSHostingController。
必须将不确定类型的any View类型强制转换为AnyView,便于NSHostingController使用:
class ToolbarWindow: NSWindow {
init(rectOrigin rect: CGPoint,rectSize size: CGSize, ViewBuilder: () -> any View) {
// 先创建内容视图
let swiftUIView = ViewBuilder()
let hostingControllerView = NSHostingController(rootView: AnyView(swiftUIView)) // 使用 AnyView 封装遵循 any View的视图
}
}
总结
AnyView 是一个用来“类型擦除”的容器,让可以把各种不同的 View 类型统一包装为同一个具体类型,将视图作为参数传入,用于参数传递、数组存储、条件分支等场景。
优点在于,统一处理不同的视图类型,提供灵活性,但是性能略有开销,因为SwiftUI不能优化视图树。