SwiftUI 将 TupleView 转换为 AnyView 数组

2023-11-23

Code

我有以下代码:

struct CustomTabView: View where Content: View {

    let children: [AnyView]

    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content

        let m = Mirror(reflecting: content())
        if let value = m.descendant("value") {
            let tupleMirror = Mirror(reflecting: value)
            let tupleElements = tupleMirror.children.map({ AnyView($0.value) }) // ERROR
            self.children = tupleElements
        } else {
            self.children = [AnyView]()
        }
    }

    var body: some View {
        ForEach(self.children) { child in
            child...
        }
    }
}

Problem

我正在尝试转换TupleView到一个数组中AnyView但我收到错误

Protocol type 'Any' cannot conform to 'View' because only concrete types can conform to protocols

可能的解决方案

我解决这个问题的一种方法是将类型擦除的视图传递到CustomTabView像这样:

CustomTabView {
    AnyView(Text("A"))
    AnyView(Text("B"))
    AnyView(Rectangle())
}

Ideally

但我希望能够像本地人一样执行以下操作TabView

CustomTabView {
    Text("A")
    Text("B")
    Rectangle()
}

那么我将如何去转换TupleView到一个数组中AnyView?


以下是我如何使用 SwiftUI 创建自定义选项卡视图:

struct CustomTabView<Content>: View where Content: View {

    @State private var currentIndex: Int = 0
    @EnvironmentObject private var model: Model

    let content: () -> Content

    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
    }

    var body: some View {

        GeometryReader { geometry in
            return ZStack {
                // pages
                // onAppear on all pages are called only on initial load
                self.pagesInHStack(screenGeometry: geometry)
            }
            .overlayPreferenceValue(CustomTabItemPreferenceKey.self) { preferences in
                // tab bar
                return self.createTabBar(screenGeometry: geometry, tabItems: preferences.map {TabItem(tag: $0.tag, tab: $0.item)})
            }
        }
    }

    func getTabBarHeight(screenGeometry: GeometryProxy) -> CGFloat {
        // https://medium.com/@hacknicity/ipad-navigation-bar-and-toolbar-height-changes-in-ios-12-91c5766809f4
        // ipad 50
        // iphone && portrait 49
        // iphone && portrait && bottom safety 83
        // iphone && landscape 32
        // iphone && landscape && bottom safety 53
        if UIDevice.current.userInterfaceIdiom == .pad {
            return 50 + screenGeometry.safeAreaInsets.bottom
        } else if UIDevice.current.userInterfaceIdiom == .phone {
            if !model.landscape {
                return 49 + screenGeometry.safeAreaInsets.bottom
            } else {
                return 32 + screenGeometry.safeAreaInsets.bottom
            }
        }
        return 50
    }

    func pagesInHStack(screenGeometry: GeometryProxy) -> some View {

        let tabBarHeight = getTabBarHeight(screenGeometry: screenGeometry)
        let heightCut = tabBarHeight - screenGeometry.safeAreaInsets.bottom
        let spacing: CGFloat = 100 // so pages don't overlap (in case of leading and trailing safetyInset), arbitrary

        return HStack(spacing: spacing) {
            self.content()
                // reduced height, so items don't appear under tha tab bar
                .frame(width: screenGeometry.size.width, height: screenGeometry.size.height - heightCut)
                // move up to cover the reduced height
                // 0.1 for iPhone X's nav bar color to extend to status bar
                .offset(y: -heightCut/2 - 0.1)
        }
        .frame(width: screenGeometry.size.width, height: screenGeometry.size.height, alignment: .leading)
        .offset(x: -CGFloat(self.currentIndex) * screenGeometry.size.width + -CGFloat(self.currentIndex) * spacing)
    }

    func createTabBar(screenGeometry: GeometryProxy, tabItems: [TabItem]) -> some View {

        let height = getTabBarHeight(screenGeometry: screenGeometry)

        return VStack {
            Spacer()
            HStack(spacing: screenGeometry.size.width / (CGFloat(tabItems.count + 1) + 0.5)) {
                Spacer()
                ForEach(0..<tabItems.count, id: \.self) { i in
                    Group {
                        Button(action: {
                            self.currentIndex = i
                        }) {
                            tabItems[i].tab
                        }.foregroundColor(self.currentIndex == i ? .blue : .gray)
                    }
                }
                Spacer()
            }
            // move up from bottom safety inset
            .padding(.bottom, screenGeometry.safeAreaInsets.bottom > 0 ? screenGeometry.safeAreaInsets.bottom - 5 : 0 )
            .frame(width: screenGeometry.size.width, height: height)
            .background(
                self.getTabBarBackground(screenGeometry: screenGeometry)
            )
        }
        // move down to cover bottom of new iphones and ipads
        .offset(y: screenGeometry.safeAreaInsets.bottom)
    }

    func getTabBarBackground(screenGeometry: GeometryProxy) -> some View {

        return GeometryReader { tabBarGeometry in
            self.getBackgrounRectangle(tabBarGeometry: tabBarGeometry)
        }
    }

    func getBackgrounRectangle(tabBarGeometry: GeometryProxy) -> some View {

        return VStack {
            Rectangle()
                .fill(Color.white)
                .opacity(0.8)
                // border top
                // https://www.reddit.com/r/SwiftUI/comments/dehx9t/how_to_add_border_only_to_bottom/
                .padding(.top, 0.2)
                .background(Color.gray)

                .edgesIgnoringSafeArea([.leading, .trailing])
        }
    }
}

以下是首选项和视图扩展:

// MARK: - Tab Item Preference
struct CustomTabItemPreferenceData: Equatable {
    var tag: Int
    let item: AnyView
    let stringDescribing: String // to let preference know when the tab item is changed
    var badgeNumber: Int // to let preference know when the badgeNumber is changed


    static func == (lhs: CustomTabItemPreferenceData, rhs: CustomTabItemPreferenceData) -> Bool {
        lhs.tag == rhs.tag && lhs.stringDescribing == rhs.stringDescribing && lhs.badgeNumber == rhs.badgeNumber
    }
}

struct CustomTabItemPreferenceKey: PreferenceKey {

    typealias Value = [CustomTabItemPreferenceData]

    static var defaultValue: [CustomTabItemPreferenceData] = []

    static func reduce(value: inout [CustomTabItemPreferenceData], nextValue: () -> [CustomTabItemPreferenceData]) {
        value.append(contentsOf: nextValue())
    }
}

// TabItem
extension View {
    func customTabItem<Content>(@ViewBuilder content: @escaping () -> Content) -> some View where Content: View {
        self.preference(key: CustomTabItemPreferenceKey.self, value: [
            CustomTabItemPreferenceData(tag: 0, item: AnyView(content()), stringDescribing: String(describing: content()), badgeNumber: 0)
        ])
    }
}

// Tag
extension View {
    func customTag(_ tag: Int, badgeNumber: Int = 0) -> some View {

        self.transformPreference(CustomTabItemPreferenceKey.self) { (value: inout [CustomTabItemPreferenceData]) in

            guard value.count > 0 else { return }
            value[0].tag = tag
            value[0].badgeNumber = badgeNumber

        }
        .transformPreference(CustomTabItemPreferenceKey.self) { (value: inout [CustomTabItemPreferenceData]) -> Void in

            guard value.count > 0 else { return }
            value[0].tag = tag
            value[0].badgeNumber = badgeNumber
        }
        .tag(tag)
    }
}

这是用法:

struct MainTabsView: View {
    var body: some View {
        // TabView
        CustomTabView {
            A()
                .customTabItem { ... }
                .customTag(0, badgeNumber: 1)
            B()
                .customTabItem { ... }
                .customTag(2)
            C()
                .customTabItem { ... }
                .customTag(3)
        }
    }
}

希望对大家有用,如果您知道更好的方法,请告诉我!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SwiftUI 将 TupleView 转换为 AnyView 数组 的相关文章

随机推荐

  • 如何使用 CSS 将长单词换行并避免水平滚动?

    我有以下 html div class box long text here div and css box width 400px height 100px overflow auto border 1px gold solid 我只想要
  • 原子操作的成本是多少?

    原子操作 任何比较和交换或原子加 减 的成本是多少 消耗多少周期 它会暂停 SMP 或 NUMA 上的其他处理器 还是会阻止内存访问 它会刷新乱序 CPU 中的重新排序缓冲区吗 对缓存会有什么影响 我对现代流行的 CPU 感兴趣 x86 x
  • 无法加载文件或程序集“System.Web.Mvc”

    我的新 ASP NET MVC Web 应用程序可以在我的开发工作站上运行 但不能在我的 Web 服务器上运行 应用程序中的服务器错误 配置错误 描述 处理服务此请求所需的配置文件期间发生错误 请查看下面的具体错误详细信息并适当修改您的配置
  • MySQL触发器/过程执行延迟

    有没有一种好的方法可以延迟mysql触发器的执行 WHILE condition 0 sleep for awhile insert into some table values NEW value1 NEW value2 从 MySQL
  • “帖子的链接必须指向应用程序的连接或画布 URL”- 错误

    我正在尝试发布到用户墙 但当共享窗口弹出时我收到此错误 The post s links must direct to the application s connect or canvas URL 现在 我进行了适当的 Google 搜索
  • 打破 R 中的嵌套循环

    非常简单的示例代码 仅用于演示 没有任何用处 repeat while 1 gt 0 for i in seq 1 100 break usually tied to a condition break break print finish
  • .NET Core Entity Framework - 在类库中添加 Context 迁移

    我在将初始迁移添加到 NET Core 类库内的实体框架数据库上下文时遇到问题 当我跑步时 dotnet ef migrations add migrationName c PlaceholderContext 我收到错误 Could no
  • 调试器可视化工具和“类型未标记为可序列化”

    我正在尝试创建一个调试器可视化工具 它可以显示任何内容的控制层次结构Control 已完成 但我遇到了例外 类型未标记为可序列化 我该如何克服这个问题 控件是 NET Windows Forms框架类型 我无法将其标记为可序列化 您还需要实
  • 为什么 const 允许参数中引用的隐式转换?

    这听起来像是一个愚蠢的问题 但我对以下行为感到困惑 void funcTakingRef unsigned int arg std cout lt lt arg void funcTakingByValue unsigned int arg
  • 带或不带引号的 JSON 对象

    我正在尝试学习 JSON 我了解到任何带有双引号的键的 javascript 对象都被视为 JSON 对象 我构建了这个对象 var jstr1 mykey my value 但是当我尝试使用 JSON parse jstr1 进行解析时
  • PROJ.4 库和 OSGB36

    一切顺利 我正在尝试使用 proj 4 库将纬度 经度坐标转换为 OSGB36 x 和 y 还有其他人成功地做到了这一点吗 我需要填充 srcPrj4String 和 destPrj4String 变量 例如 字符串 srcPrj4Stri
  • jQuery 可移动模态对话框

    我一直在寻找可拖动模式框的所有可用 jQuery 插件 唯一的问题是我发现的每个可拖动的模式框都需要标题栏 有谁知道有什么 jQuery 插件可以让我创建一个没有标题栏的可拖动模式框吗 在这种情况下 您可以通过框的边框拖动它 有什么方法可以
  • 无法访问 attr_accessor 定义的变量

    我正在使用 Thinking Sphinx 来运行搜索 并且我得到了适当的 ActiveRecord 模型 问题是 我想在每个模型上创建适当的链接路径和文本 然后通过 AJAX 将信息以 JSON 形式发送到浏览器 我使用以下内容来构建这些
  • 如何限制cakephp中的分页

    如何限制 cakephp 中的分页 假设我有 400 条记录 我只需要获取从第50条记录到第75条记录的25条记录 每页需要显示5条记录 我如何在分页中做到这一点 示例代码 this gt paginate array contain gt
  • Google App Engine Node.js 应用程序不健康

    我正在尝试在谷歌应用程序引擎上测试一个相当简单的node js应用程序 如下所示 它要做的就是监听 Firebase 数据库中的更改 然后向用户发送 GCM 消息 var Firebase require firebase var gcm
  • Apache HTTP BasicScheme.authenticate 已弃用?

    在 Apache HTTP Component 4 类 org apache http impl auth BasicScheme 中 我注意到该方法 public static Header authenticate final Cred
  • 动画边距/厚度

    我只是以为我已经知道 WPF 和 XAML 语法是如何工作的 呜呜 我收到消息 WithEvents variables can only be typed as classes interfaces or type parameters
  • 我可以在类外部初始化“constexpr static”成员吗?

    我正在使用可变宽度通信格式 处理它的结构看起来像这样 struct Header int msgType 1 len Header len sizeof this struct A public Header int x char y A
  • 每次更改 Rails 代码时都必须重新启动 Apache

    我正在使用 Apache 和 Passenger 运行 Rails 3 自从我从 Mongrel 切换到 Passenger 后 我发现每次更改代码时都必须重新启动 Apache 这是预期的行为吗 在开发中使用 Mongrel 是避免这个问
  • SwiftUI 将 TupleView 转换为 AnyView 数组

    Code 我有以下代码 struct CustomTabView View where Content View let children AnyView init ViewBuilder content escaping gt Conte