Here is what I am trying to do:
The screenshot is taken from a 14 iPhone.
I want to manage the layout like the first image, where all the spacing and circle spacing are reduced. How can I achieve the exact TabBar UI?
Code:-
import SwiftUI
struct TestReorderTabItemsPerSelection: View {
@State private var selection: String = "house"
@State private var tbHeight = CGFloat.zero
struct Item {
let title: String
let color: Color
let icon: String
}
@State var items = [
Item(title: "folder", color: .red, icon: "folder"),
Item(title: "eraser", color: .red, icon: "eraser.fill"),
Item(title: "cart", color: .red, icon: "cart"),
Item(title: "house", color: .blue, icon: "house"),
Item(title: "car", color: .green, icon: "car"),
]
var selected: Item {
items.first { $0.title == selection } ?? items[0]
}
var body: some View {
ZStack(alignment: .bottom) {
TabView(selection: $selection) {
ForEach(items, id: \.title) { item in
TabContent(height: $tbHeight) {
item.color
} .tabItem {
Image(systemName: item.icon)
Text(item.title)
}
}
}
TabSelection(height: tbHeight, item: selected)
}
}
struct TabSelection: View {
let height: CGFloat
let item: Item
var body: some View {
VStack {
Spacer()
Curve()
.frame(maxWidth: .infinity, maxHeight: height)
.foregroundColor(item.color)
}
.ignoresSafeArea()
.overlay(
Circle().foregroundColor(.black)
.frame(height: height).aspectRatio(contentMode: .fit)
.shadow(radius: 4)
.overlay(Image(systemName: item.icon)
.font(.title)
.foregroundColor(.white))
, alignment: .bottom)
}
}
struct TabContent<V: View>: View {
@Binding var height: CGFloat
@ViewBuilder var content: () -> V
var body: some View {
GeometryReader { gp in
content()
.onAppear {
height = gp.safeAreaInsets.bottom
}
.onChange(of: gp.size, {
height = gp.safeAreaInsets.bottom
})
}
}
}
struct Curve: Shape {
func path(in rect: CGRect) -> Path {
let h = rect.maxY * 0.7
return Path {
$0.move(to: .zero)
$0.addLine(to: CGPoint(x: rect.midX / 2.0, y: rect.minY))
$0.addCurve(to: CGPoint(x: rect.midX, y: h), control1: CGPoint(x: rect.midX * 0.8, y: rect.minY), control2: CGPoint(x: rect.midX * 0.7, y: h))
$0.addCurve(to: CGPoint(x: rect.midX * 3.0 / 2.0, y: rect.minY), control1: CGPoint(x: rect.midX * 1.3, y: h), control2: CGPoint(x: rect.midX * 1.2, y: rect.minY))
$0.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
}
}
}
}
struct TestReorderTabItemsPerSelection_Previews: PreviewProvider {
static var previews: some View {
TestReorderTabItemsPerSelection()
}
}
Thank You Benzy Neez And Andrei G. for your response. Here is my Updated Answer according to my requirement:-
import SwiftUI
struct TabItem: Identifiable, Equatable {
let id: UUID = UUID()
let title: String
let color: Color
let icon: String
}
struct TestReorderTabItemsPerSelection: View {
@State private var selectedTab: TabItem?
private let tabHeight: CGFloat = 50
@State var items = [
TabItem(title: "House", color: .orange, icon: "house.fill"),
TabItem(title: "Deposits", color: .cyan, icon: "wallet.pass.fill"),
TabItem(title: "Scan", color: .purple, icon: "qrcode.viewfinder"),
TabItem(title: "Finance", color: .blue, icon: "cart.circle.fill"),
TabItem(title: "Card", color: .green, icon: "creditcard.fill")
]
var body: some View {
ZStack(alignment: .bottom) {
Color.clear.ignoresSafeArea()
// Display the custom view corresponding to the selected tab
VStack(spacing: 5) {
if let selectedTab = selectedTab {
switch selectedTab.title {
case "House":
HouseView()
case "Deposits":
DepositsView()
case "Scan":
ScanView()
case "Finance":
FinanceView()
case "Card":
CardView()
default:
Text("Unknown View")
}
} else {
Text("Select a Tab")
.font(.largeTitle)
.foregroundColor(.gray)
}
HStack {
HStack {
TabItemView(item: items[0], selectedTab: $selectedTab, tabHeight: tabHeight)
TabItemView(item: items[1], selectedTab: $selectedTab, tabHeight: tabHeight)
}
Spacer()
.frame(maxWidth: .infinity, alignment: .center)
HStack {
TabItemView(item: items[3], selectedTab: $selectedTab, tabHeight: tabHeight)
TabItemView(item: items[4], selectedTab: $selectedTab, tabHeight: tabHeight)
}
}
.background(Color.white) // Set red background for tab bar
.padding(.horizontal, 10)
}
.onAppear {
selectedTab = items[0]
}
TabSelection(item: selectedTab ?? items[0], height: 80, tabHeight: tabHeight, selectedTab: $selectedTab)
.offset(y: -10)
}.background(Color.white) // Set red background for tab bar
}
}
struct TabItemView: View {
let item: TabItem
@Binding var selectedTab: TabItem?
var tabHeight: CGFloat
private var isSelected: Bool {
selectedTab == item
}
var body: some View {
Button {
selectedTab = item
} label: {
VStack {
Image(systemName: item.icon)
.imageScale(.large)
.symbolVariant(.fill)
Text(item.title)
.font(.caption)
}
.foregroundStyle(isSelected ? .orange : .gray)
}
.frame(maxWidth: .infinity, maxHeight: tabHeight)
}
}
struct TabSelection: View {
//Parameters
let item: TabItem
let height: CGFloat
var tabHeight: CGFloat
@Binding var selectedTab: TabItem?
//Body
var body: some View {
Circle()
.foregroundColor(.black)
.frame(maxWidth: .infinity, maxHeight: height)
.aspectRatio(contentMode: .fit)
.shadow(radius: 4)
.overlay {
Image(systemName: "qrcode.viewfinder")
.font(.title)
.foregroundColor(.white)
}
.background(alignment: .bottom) {
Curve()
.frame(maxWidth: .infinity, maxHeight: height, alignment: .bottom)
.foregroundColor(Color(UIColor.lightGray.withAlphaComponent(0.2)))
.offset(y: height-(tabHeight - 5))
}.onTapGesture {
selectedTab = TabItem(title: "Scan", color: .purple, icon: "qrcode.viewfinder")
}
}
}
struct Curve: Shape {
func path(in rect: CGRect) -> Path {
let h = rect.maxY * 0.7
return Path {
$0.move(to: .zero)
$0.addLine(to: CGPoint(x: rect.midX / 2.0, y: rect.minY))
$0.addCurve(to: CGPoint(x: rect.midX, y: h), control1: CGPoint(x: rect.midX * 0.8, y: rect.minY), control2: CGPoint(x: rect.midX * 0.7, y: h))
$0.addCurve(to: CGPoint(x: rect.midX * 3.0 / 2.0, y: rect.minY), control1: CGPoint(x: rect.midX * 1.3, y: h), control2: CGPoint(x: rect.midX * 1.2, y: rect.minY))
$0.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
}
}
}
struct HouseView: View {
var body: some View {
ZStack {
Color.blue
.ignoresSafeArea()
Text("House View")
.font(.largeTitle)
.foregroundColor(.white)
}
}
}
struct DepositsView: View {
var body: some View {
ZStack {
Color.red
.ignoresSafeArea()
Text("Deposits View")
.font(.largeTitle)
.foregroundColor(.white)
}
}
}
struct ScanView: View {
var body: some View {
ZStack {
Color.purple
.ignoresSafeArea()
Text("Scan View")
.font(.largeTitle)
.foregroundColor(.white)
}
}
}
struct FinanceView: View {
var body: some View {
ZStack {
Color.pink
.ignoresSafeArea()
Text("Finance View")
.font(.largeTitle)
.foregroundColor(.white)
}
}
}
struct CardView: View {
var body: some View {
ZStack {
Color.green
.ignoresSafeArea()
Text("Card View")
.font(.largeTitle)
.foregroundColor(.white)
}
}
}
// Preview
struct TestReorderTabItemsPerSelection_Previews: PreviewProvider {
static var previews: some View {
TestReorderTabItemsPerSelection()
}
}