Я пытаюсь создать собственный TabView
в SwiftUI, который также имеет функциональность .tabViewStyle(.page())
.
На данный момент я прошел 99% пути, но не могу понять, как получить все TabBarItems
в список.
Я использую PreferenceKey, поэтому порядок, в котором я добавляю их в закрытие, соответствует порядку в TabView.
Когда я запускаю его, элементы вкладки добавляются в массив, а затем удаляются, и, похоже, он не работает.
У меня это работало с перечислением как CaseIterable
и ForEach(tabs) { tab in
как ForEach(TabBarItems.allCases) { tab in
, но, как уже упоминалось, я хотел, чтобы порядок в панели был органичным из закрытия.
struct TabViewContainer<Content : View>: View {
@Binding private var selection: TabBarItem
@State private var tabs: [TabBarItem] = []
var content: Content
init(selection: Binding<TabBarItem>, @ViewBuilder content: () -> Content) {
self._selection = selection
self.content = content()
}
var body: some View {
ZStack(alignment: .bottom) {
TabView(selection: $selection) {
content
}
.tabViewStyle(.page(indexDisplayMode: .never))
tabBarItems()
}
.onPreferenceChange(TabBarItemsPreferenceKey.self) { self.tabs = $0 }
}
private func tabBarItems() -> some View {
HStack(spacing: 10) {
ForEach(tabs) { tab in
Button {
selection = tab
} label: {
tabButton(tab: tab)
}
}
}
.padding(.horizontal)
.frame(maxWidth: .infinity)
.padding(.top, 8)
.background(Color(uiColor: .systemGray6))
}
private func tabButton(tab: TabBarItem) -> some View {
VStack(spacing: 0) {
Image(icon: tab.icon)
.font(.system(size: 16))
.frame(maxWidth: .infinity, minHeight: 28)
Text(tab.title)
.font(.system(size: 10, weight: .medium, design: .rounded))
}
.foregroundColor(selection == tab ? tab.colour : .gray)
}
}
struct TabBarItemsPreferenceKey: PreferenceKey {
static var defaultValue: [TabBarItem] = []
static func reduce(value: inout [TabBarItem], nextValue: () -> [TabBarItem]) {
value += nextValue()
}
}
struct TabBarItemViewModifier: ViewModifier {
let tab: TabBarItem
func body(content: Content) -> some View {
content.preference(key: TabBarItemsPreferenceKey.self, value: [tab])
}
}
extension View {
func tabBarItem(_ tab: TabBarItem) -> some View {
modifier(TabBarItemViewModifier(tab: tab))
}
}
struct TabSelectionView: View {
@State private var selection: TabBarItem = .itinerary
var body: some View {
TabViewContainer(selection: $selection) {
PhraseView()
.tabBarItem(.phrases)
ItineraryView()
.tabBarItem(.itinerary)
BudgetView()
.tabBarItem(.budget)
BookingView()
.tabBarItem(.bookings)
PackingListView()
.tabBarItem(.packing)
}
}
}
Вы можете использовать более элегантный способ, @resultBuilder
:
View
и тег;tabBarItem
теперь должен возвращать ранее созданную структуру;@resultBuilder
создаст массив вашего представления и тега, который вы будете использовать внутри контейнера.
Строитель результатов:
@resultBuilder
public struct TabsBuilder {
internal static func buildBlock(_ components: Tab...) -> [Tab] {
return components
}
internal static func buildEither(first component: Tab) -> Tab {
return component
}
internal static func buildEither(second component: Tab) -> Tab {
return component
}
}
Вкладка:
struct Tab: Identifiable {
var content: AnyView //I don't recommend the use of AnyView, but I don't want to dive deep into generics for now.
var tag: TabBarItem
var id = UUID()
}
Модификатор:
struct Tab: Identifiable {
var content: AnyView
var tag: TabBarItem
var id = UUID()
}
Табвиевконтейнер:
struct TabViewContainer: View {
@Binding private var selection: TabBarItem
@State private var tabs: [TabBarItem]
var content: [Tab]
init(selection: Binding<TabBarItem>, @TabsBuilder content: () -> [Tab]) {
self._selection = selection
self.content = content()
self.tabs = self.content.map({$0.tag})
}
var body: some View {
ZStack(alignment: .bottom) {
TabView(selection: $selection) {
ForEach(content) { content in
content.content
.tag(content.tag)
}
}.tabViewStyle(.page(indexDisplayMode: .never))
tabBarItems()
}
}
private func tabBarItems() -> some View {
HStack(spacing: 10) {
ForEach(tabs) { tab in
Button {
selection = tab
} label: {
tabButton(tab: tab)
}
}
}
.padding(.horizontal)
.frame(maxWidth: .infinity)
.padding(.top, 8)
.background(Color(uiColor: .systemGray6))
}
private func tabButton(tab: TabBarItem) -> some View {
VStack(spacing: 0) {
Image(icon: tab.icon)
.font(.system(size: 16))
.frame(maxWidth: .infinity, minHeight: 28)
Text(tab.title)
.font(.system(size: 10, weight: .medium, design: .rounded))
}
.foregroundColor(selection == tab ? tab.colour : .gray)
}
}
Обновил мой ответ! В init
добавлен следующий код: self.tabs = self.content.map({$0.tag})
спасибо @Timmy, это выглядит довольно аккуратно и чисто, но проблема все еще существует, когда элементы не находятся в порядке закрытия
TabViewContainer(selection: $selection) {}
. То есть с 5 элементами в случаях TabBarItem, а затем перечислением их в другом порядке в закрытии означает, что смахивание прыгает