macos - SwiftUI:适用于 macOS 和 iOS 的自定义选项卡 View

标签 macos swiftui tabview nstabview

有没有一种简单的方法可以使用 SwiftUI 获得更可定制的选项卡栏 View ?我主要是从 macOS 的角度来问(尽管在任何系统上都可以工作的系统是理想的),因为标准系统的 macOS 实现存在各种问题:

  • 它周围有一个圆形边框,这意味着它与 subview 中的任何背景颜色一起看起来都很糟糕。
  • 它不支持选项卡图标。
  • 在定制方面非常有限。
  • 它有问题(有时它不会按预期切换 View )。
  • 看起来相当过时。

Standard macOS tab bar with SwiftUI

当前代码:

import SwiftUI

struct SimpleTabView: View {

    @State private var selection = 0

    var body: some View {

        TabView(selection: $selection) {

            HStack {
                Spacer()
                VStack {
                    Spacer()
                    Text("First Tab!")
                    Spacer()
                }
                Spacer()
            }
                .background(Color.blue)
                .tabItem {
                    VStack {
                        Image("icons.general.home")
                        Text("Tab 1")
                    }
                }
                .tag(0)

            HStack {
                Spacer()
                VStack {
                    Spacer()
                    Text("Second Tab!")
                    Spacer()
                }
                Spacer()
            }
                .background(Color.red)
                .tabItem {
                    VStack {
                        Image("icons.general.list")
                        Text("Tab 2")
                    }
                }
                .tag(1)

            HStack {
                Spacer()
                VStack {
                    Spacer()
                    Text("Third Tab!")
                    Spacer()
                }
                Spacer()
            }
                .background(Color.yellow)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .tabItem {
                    VStack {
                        Image("icons.general.cog")
                        Text("Tab 3")
                    }
                }
                .tag(2)
        }
    }
}

最佳答案

为了解决这个问题,我整理了以下简单的自定义 View ,它提供了与 iOS 更相似的选项卡界面,即使在 Mac 上运行也是如此。它的工作原理只是采用一组元组,每个元组概述了选项卡的标题、图标名称和内容。

它可以在浅色和深色模式下工作,并且可以在 macOS iOS/iPadOS/等上运行,但您可能只想使用标准 TabView 在iOS上运行时的实现;由你决定。

它还包含一个参数,以便您可以根据偏好将栏定位在顶部或底部(整个顶部更符合 macOS 准则)。

以下是结果示例(在深色模式下):

Custom Tab Bar, running on macOS

这是代码。一些注意事项:

  • 它使用 Color 的基本扩展,因此可以使用系统背景颜色,而不是硬编码。
  • 唯一稍微有点 hack 的部分是额外的 backgroundshadow 修饰符,它们是防止 SwiftUI 将阴影应用到每个 subview 所必需的( !)。当然,如果您不想要阴影,则可以删除所有这些行(包括 zIndex)。

Swift v5.1:

import SwiftUI

public extension Color {

    #if os(macOS)
    static let backgroundColor = Color(NSColor.windowBackgroundColor)
    static let secondaryBackgroundColor = Color(NSColor.controlBackgroundColor)
    #else
    static let backgroundColor = Color(UIColor.systemBackground)
    static let secondaryBackgroundColor = Color(UIColor.secondarySystemBackground)
    #endif
}

public struct CustomTabView: View {
    
    public enum TabBarPosition { // Where the tab bar will be located within the view
        case top
        case bottom
    }
    
    private let tabBarPosition: TabBarPosition
    private let tabText: [String]
    private let tabIconNames: [String]
    private let tabViews: [AnyView]
    
    @State private var selection = 0
    
    public init(tabBarPosition: TabBarPosition, content: [(tabText: String, tabIconName: String, view: AnyView)]) {
        self.tabBarPosition = tabBarPosition
        self.tabText = content.map{ $0.tabText }
        self.tabIconNames = content.map{ $0.tabIconName }
        self.tabViews = content.map{ $0.view }
    }
    
    public var tabBar: some View {
        
        HStack {
            Spacer()
            ForEach(0..<tabText.count) { index in
                HStack {
                    Image(self.tabIconNames[index])
                    Text(self.tabText[index])
                }
                .padding()
                .foregroundColor(self.selection == index ? Color.accentColor : Color.primary)
                .background(Color.secondaryBackgroundColor)
                .onTapGesture {
                    self.selection = index
                }
            }
            Spacer()
        }
        .padding(0)
        .background(Color.secondaryBackgroundColor) // Extra background layer to reset the shadow and stop it applying to every sub-view
        .shadow(color: Color.clear, radius: 0, x: 0, y: 0)
        .background(Color.secondaryBackgroundColor)
        .shadow(
            color: Color.black.opacity(0.25),
            radius: 3,
            x: 0,
            y: tabBarPosition == .top ? 1 : -1
        )
        .zIndex(99) // Raised so that shadow is visible above view backgrounds
    }
    public var body: some View {
        
        VStack(spacing: 0) {
            
            if (self.tabBarPosition == .top) {
                tabBar
            }
            
            tabViews[selection]
                .padding(0)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
            
            if (self.tabBarPosition == .bottom) {
                tabBar
            }
        }
        .padding(0)
    }
}

这是一个如何使用它的示例。显然,您也可以向它传递一个完全自定义的 subview ,而不是像这样动态构建它们。只需确保将它们包装在 AnyView 初始值设定项内即可。

图标及其名称是自定义的,因此您必须使用自己的替代品。

struct ContentView: View {
    
    var body: some View {
        CustomTabView(
            tabBarPosition: .top,
            content: [
                (
                    tabText: "Tab 1",
                    tabIconName: "icons.general.home",
                    view: AnyView(
                        HStack {
                            Spacer()
                            VStack {
                                Spacer()
                                Text("First Tab!")
                                Spacer()
                            }
                            Spacer()
                        }
                        .background(Color.blue)
                    )
                ),
                (
                    tabText: "Tab 2",
                    tabIconName: "icons.general.list",
                    view: AnyView(
                        HStack {
                            Spacer()
                            VStack {
                                Spacer()
                                Text("Second Tab!")
                                Spacer()
                            }
                            Spacer()
                        }
                        .background(Color.red)
                    )
                ),
                (
                    tabText: "Tab 3",
                    tabIconName: "icons.general.cog",
                    view: AnyView(
                        HStack {
                            Spacer()
                            VStack {
                                Spacer()
                                Text("Third Tab!")
                                Spacer()
                            }
                            Spacer()
                        }
                        .background(Color.yellow)
                    )
                )
            ]
        )
    }
}

关于macos - SwiftUI:适用于 macOS 和 iOS 的自定义选项卡 View ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60674035/

相关文章:

linux - 如何以跨平台方式将彩色打印到控制台?

swift - 如何从 Coordinator 类更新 EnvironmentObject 变量?

SwiftUI下TabView是怎么实现的

mysql - 自 El Capitan 以来,通过 Vagrant 连接 MySQL 不再有效

objective-c - 在子类中使用 IBOutlet (Cocoa OSX)

swift - 如何使用 SwiftUI 在 TabView 中禁用垂直滚动?

ios - NavigationView 标题中的 SwiftUI 多行文本

swift - 更改默认蓝色 TabBar

Primefaces p :confirmDialog inside tabView

linux - 在 Mac 上运行 Linux 系统调用?