I have a tab bar that works great on iOS 15.x, however on iOS 14 it fails miserably. Anyone know of an alternative method I can use?
public struct Tab: Identifiable, Equatable, Hashable {
public let title: String
public let isEnabled: Bool
public let id: Int
var isDisabled: Bool {
!isEnabled
}
public init(title: String, isEnabled: Bool = true, id: Int) {
self.title = title
self.isEnabled = isEnabled
self.id = id
}
public static func == (lhs: Tab, rhs: Tab) -> Bool {
lhs.id == rhs.id
}
}
public struct TabBar: View {
private let tabs: [Tab]
@Binding private var selectedTab: Tab
@State private var selectedIndex = 0
@Namespace var namespace
public init(tabs: [Tab], selectedTab: Binding<Tab>) {
self.tabs = tabs
self._selectedTab = selectedTab
}
public var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 0) {
ForEach(Array(zip(self.tabs.indices, self.tabs)), id: \.0) { index, tab in
TabBarItem(selectedIndex: self.$selectedIndex,
selectedTab: self.$selectedTab,
namespace: namespace.self,
tab: tab,
index: index)
}
}
.padding(.horizontal)
}
.onAppear {
selectedIndex = tabs.firstIndex(where: { $0.id == selectedTab.id }) ?? 0
}
}
}
struct TabBarItem: View {
@Binding var selectedIndex: Int
@Binding var selectedTab: Tab
let namespace: Namespace.ID
let tab: Tab
let index: Int
var isSelected: Bool {
selectedIndex == index
}
var textColor: Color {
if tab.isEnabled {
return Color.black
} else {
return Color.gray
}
}
var body: some View {
Button {
selectedIndex = index
selectedTab = tab
} label: {
VStack(spacing: 0) {
Text(tab.title)
.font(.body)
.padding(.horizontal, 4)
.foregroundColor(isSelected ? Color.blue : Color.black)
if isSelected{
Color.red
.frame(height: 2)
.padding(.top, 2)
.matchedGeometryEffect(id: "underline", in: namespace, properties: .frame)
} else {
Color.clear.frame(height: 2)
}
}
.animation(.spring(), value: selectedIndex)
}
.disabled(tab.isDisabled)
.buttonStyle(.plain)
}
}
CodePudding user response:
The Color
pushes VStack
to take all available space, and to avoid this and fit to Text we can use the fixedSize
modifier, like
VStack(spacing: 0) {
Text(tab.title)
.font(.body)
.padding(.horizontal, 4)
.foregroundColor(isSelected ? Color.blue : Color.black)
if isSelected{
Color.red
.frame(height: 2)
.padding(.top, 2)
.matchedGeometryEffect(id: "underline", in: namespace, properties: .frame)
} else {
Color.clear.frame(height: 2)
}
}
.fixedSize() // << here !!
//.fixedSize(horizontal: true, vertical: false) // << or this
.animation(.spring(), value: selectedIndex)
CodePudding user response:
I think every item on your VStack(spacing: 0) { }
has same layout priority, and Color
fill all spaces of your VStack
. So please try to higher your tab.title
Text
layout priority.
Text(tab.title)
.font(.body)
.padding(.horizontal, 4)
.layoutPriority(1)
.foregroundColor(isSelected ? Color.blue : Color.black)