SwiftUI: пользовательский вид вкладок для macOS & iOS - PullRequest
0 голосов
/ 13 марта 2020

Есть ли простой способ получить более настраиваемый вид панели вкладок с помощью SwiftUI? Я в основном спрашиваю с точки зрения macOS (хотя тот, который работает в любой системе, был бы идеальным), потому что реализация стандартного macOS имеет различные проблемы:

  • У него есть округленная граница вокруг это означает, что он выглядит ужасно с любым фоновым цветом в подпредставлениях.
  • Он не поддерживает значки вкладок.
  • Он очень ограничен с точки зрения настройки.
  • Это глючит (иногда это не переключает представления, как ожидалось).
  • Это выглядит довольно устаревшим.

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)
        }
    }
}

1 Ответ

0 голосов
/ 13 марта 2020

Для решения этой проблемы я собрал следующее простое пользовательское представление, которое обеспечивает интерфейс, более похожий на iOS, даже при работе на Ma c. Он работает, просто принимая массив кортежей, каждый из которых описывает заголовок, название значка и содержимое вкладки.

Он работает как в режиме Light & Dark, так и может работать на macOS или * 1004. * iOS / iPadOS / et c., Но вы можете просто использовать стандартную реализацию TabView при работе на iOS; на ваше усмотрение.

Он также включает в себя параметр, позволяющий расположить панель вверху или внизу, в зависимости от предпочтений (верхняя часть лучше подходит для рекомендаций MacOS).

Вот пример результата (в темном режиме):

Custom Tab Bar, running on macOS

Вот код. Некоторые примечания:

  • Используется базовое расширение c до Color, поэтому он может использовать системные цвета фона, а не жесткое кодирование.
  • Единственная слегка взломанная часть дополнительные модификаторы background & shadow, необходимые для предотвращения применения SwiftUI тени к каждому подпредставлению (!). Конечно, если вам не нужна тень, вы можете просто удалить все эти линии (включая zIndex).

Swift v5.1:

import SwiftUI

public extension Color {

    static var backgroundColor: Color {
        #if os(macOS)
        return Color(NSColor.windowBackgroundColor)
        #else
        return Color(UIColor.systemBackground)
        #endif
    }
    static var secondaryBackgroundColor: Color {
        #if os(macOS)
        return Color(NSColor.controlBackgroundColor)
        #else
        return 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)
    }
}

А вот пример того, как вы бы это использовали. Очевидно, что вы могли бы также передать его в полностью пользовательское подпредставление, а не создавать их на лету, как это. Обязательно оберните их внутри этого 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)
                    )
                )
            ]
        )
    }
}
...