如何将TupleView<_VariadicView.Children>转为自定义视图并访问其内部属性?
问题
我接收的多个视图为TupleView<_VariadicView.Children>类型,尝试转为[AnyView]数组后,无法将AnyView转回原自定义Routable视图,也就没法访问其activated等内部属性。请问这种转回操作是否可行?或者有没有不用TupleView的更好实现方案?
代码示例
视图结构定义
struct View1: View { var body: some View { VStack { Text("page!") } } } struct Routable<Content: View> : View { var activated: Bool = false let _content: Content init(@ViewBuilder content: @escaping () -> Content) { self._content = content() } var body: some View { _content } } struct SplashViews: View { var body: some View { ZStack { HStack { RouterStack { Routable { View1() } Routable { View1() } } } } } }
转换数组及尝试转回的代码
public typealias Views = _VariadicView.Children extension TupleView { var views: [AnyView] { make_array(from: value) } private struct GenericView { let body: Any var any_view: AnyView? { AnyView(_fromValue: body) } } private func make_array<Tuple>(from tuple: Tuple) -> [AnyView] { func convert(child: Mirror.Child) -> AnyView? { withUnsafeBytes(of: child.value) { pointer -> AnyView? in if case Optional<Any>.some(_) = child.value { print("[TurpleView Children Value]: \(child)") let binded = pointer.bindMemory(to: GenericView.self) return binded.first?.any_view } return nil } } let mirror = Mirror(reflecting: tuple) return mirror.children.compactMap(convert) } } struct RouterStack: View { @ObservedObject var _router: Router private let _content: [AnyView] init<Views>(@ViewBuilder content: @escaping () -> TupleView<Views>) { self._content = content().views self._router = Router(self._content) // 尝试转回Routable失败,报错如下: // Cannot convert value of type 'AnyView' to type 'Routable<Content>' in coercion // Cannot convert value of type 'Routable<Content>' to specified type 'AnyView' // Generic parameter 'Content' could not be inferred in cast to 'Routable' let x: AnyView = self._content[0] as Routable // print("\(x.activated)") } var body: some View { NavigationView { } } }
解决方案
1. 为什么AnyView转回Routable不可行?
AnyView是类型擦除容器,它仅保留了View协议的实现,完全丢失了原视图的具体类型信息。因此无法直接将AnyView强制转回Routable<Content>:编译器既无法推断泛型参数Content的具体类型,也无法验证容器内的视图确实是Routable实例。
2. 无需转换AnyView,直接处理TupleView的泛型内容
TupleView的泛型Views本质是_VariadicView.Children,可以直接遍历它的子视图并访问Routable属性,不需要转成AnyView。核心思路是用协议统一Routable的属性访问:
// 定义协议,统一Routable的属性访问 protocol RoutableProtocol { var activated: Bool { get set } } extension Routable: RoutableProtocol {} struct RouterStack: View { @ObservedObject var _router: Router private let _content: _VariadicView.Children init(@ViewBuilder content: @escaping () -> TupleView<_VariadicView.Children>) { self._content = content().value self._router = Router(with: self._content) // 直接遍历子视图访问属性 for child in _content { if let routableView = child as? any RoutableProtocol { print("activated status: \(routableView.activated)") } } } var body: some View { NavigationView { ForEach(_content.indices, id: \.self) { index in _content[index] } } } }
3. 更简洁的方案:直接接收[RoutableProtocol]数组
如果不需要依赖ViewBuilder的多视图语法糖,可以让RouterStack直接接收符合协议的数组,彻底避开TupleView:
struct RouterStack: View { @ObservedObject var _router: Router private let _content: [any RoutableProtocol] init(content: [any RoutableProtocol]) { self._content = content self._router = Router(with: content) // 直接访问属性 _content.forEach { print($0.activated) } } var body: some View { NavigationView { ForEach(_content.indices, id: \.self) { index in // 将协议类型转回View (_content[index] as! some View) } } } } // 使用方式 struct SplashViews: View { var body: some View { ZStack { HStack { RouterStack(content: [ Routable { View1() }, Routable { View1() } ]) } } } }
4. 保留ViewBuilder语法的优化写法
如果想继续用ViewBuilder的多视图写法,可以重载init方法,直接将_VariadicView.Children转为协议数组:
struct RouterStack: View { @ObservedObject var _router: Router private let _content: [any RoutableProtocol] init(@ViewBuilder content: @escaping () -> TupleView<_VariadicView.Children>) { let children = content().value self._content = children.compactMap { $0 as? any RoutableProtocol } self._router = Router(with: self._content) } var body: some View { NavigationView { ForEach(_content.indices, id: \.self) { index in (_content[index] as! some View) } } } } // 继续使用原写法 // RouterStack { // Routable { View1() } // Routable { View1() } // }
内容的提问来源于stack exchange,提问作者OlderPeo
相关产品推荐
相关产品推荐

