Для решения этой проблемы я собрал следующее простое пользовательское представление, которое обеспечивает интерфейс, более похожий на iOS, даже при работе на Ma c. Он работает, просто принимая массив кортежей, каждый из которых описывает заголовок, название значка и содержимое вкладки.
Он работает как в режиме Light & Dark, так и может работать на macOS или * 1004. * iOS / iPadOS / et c., Но вы можете просто использовать стандартную реализацию TabView
при работе на iOS; на ваше усмотрение.
Он также включает в себя параметр, позволяющий расположить панель вверху или внизу, в зависимости от предпочтений (верхняя часть лучше подходит для рекомендаций 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)
)
)
]
)
}
}