Skip to content

Instantly share code, notes, and snippets.

@ylem
Last active April 10, 2023 20:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ylem/6d290bf9ef368ea3ae1c84d936392039 to your computer and use it in GitHub Desktop.
Save ylem/6d290bf9ef368ea3ae1c84d936392039 to your computer and use it in GitHub Desktop.
Custom TabBar
import SwiftUI
struct CustomTabBar: View {
@State private var selectedTab: TabItem = .home
private let height: CGFloat = 80
var body: some View {
ZStack(alignment: .bottom) {
selectedTab.view
.frame(maxWidth: .infinity, maxHeight: .infinity)
.padding(.bottom, height)
TabClipperShape(radius: height / 2)
.fill(.white)
.frame(height: height, alignment: .top)
.shadow(color: Color.black.opacity(0.1), radius: 4, x: 0, y: -1)
.overlay(bottomBar)
}
.background(Color("Background"))
.ignoresSafeArea()
}
var bottomBar: some View {
HStack(spacing: 0) {
Spacer()
ForEach(TabItem.allCases) { tabItem in
Button {
selectedTab = tabItem
} label: {
if tabItem.isButton {
tabItem.icon
.bold()
.frame(width: 56, height: 56)
.foregroundColor(.white)
.background(
Circle()
.fill(LinearGradient(gradient: Gradient(colors: [
Self.gradientStart,
Self.gradientEnd
]), startPoint: .topLeading, endPoint: .bottomTrailing))
.shadow(color: Color.accentColor.opacity(0.2), radius: 10, x: 0, y: 8)
)
.offset(y: -36)
} else {
VStack(spacing: 2) {
tabItem.icon
.bold()
Text(tabItem.rawValue)
.font(.caption2)
.lineLimit(1)
}
}
}
.foregroundColor(selectedTab == tabItem ? .accentColor : .secondary)
.frame(maxWidth: .infinity)
Spacer()
}
}
.frame(height: height, alignment: .top)
.padding(.top, 14)
}
static let gradientStart = Color(red: 50.0 / 255, green: 66.0 / 255, blue: 202.0 / 255)
static let gradientEnd = Color(red: 94.0 / 255, green: 122.0 / 255, blue: 224.0 / 255)
}
struct TabClipperShape: Shape {
var radius = 40.0
func path(in rect: CGRect) -> Path {
var path = Path()
let v = radius * 2
path.move(to: CGPoint(x: 0, y: 0))
path.addArc(center: CGPoint(x: radius/2, y: radius/2), radius: radius/2, startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 180+90), clockwise: false)
path.addArc(center: CGPoint(x: ((rect.size.width / 2) - radius) - radius + v * 0.04 + radius/2, y: radius/2), radius: radius/2, startAngle: Angle(degrees: 270), endAngle: Angle(degrees: 270+70), clockwise: false)
path.addArc(center: CGPoint(x: rect.size.width / 2, y: 0), radius: v/2, startAngle: Angle(degrees: 160), endAngle: Angle(degrees: 20), clockwise: true)
path.addArc(center: CGPoint(x: (rect.size.width - ((rect.size.width / 2) - radius)) - v * 0.04 + radius/2, y: radius/2), radius: radius/2, startAngle: Angle(degrees: 200), endAngle: Angle(degrees: 200+70), clockwise: false)
path.addArc(center: CGPoint(x: rect.size.width - radius/2, y: radius/2), radius: radius/2, startAngle: Angle(degrees: 270), endAngle: Angle(degrees: 270+90), clockwise: false)
path.addLine(to: CGPoint(x: rect.size.width, y: rect.size.height))
path.addLine(to: CGPoint(x: 0, y: rect.size.height))
return path
}
}
enum TabItem: String, CaseIterable, Identifiable {
case home = "Home"
case reports = "Reports"
case plus = "Plus"
case analytics = "Analytics"
case profile = "Profile"
var id: String {
self.rawValue
}
var icon: Image {
switch self {
case .home: return Image(systemName: "house.fill")
case .reports: return Image(systemName: "list.bullet.clipboard.fill")
case .plus: return Image(systemName: "plus")
case .analytics: return Image(systemName: "chart.xyaxis.line")
case .profile: return Image(systemName: "person.fill")
}
}
@ViewBuilder var view: some View {
switch self {
case .home:
Text("home")
case .reports:
Text("reports")
case .analytics:
Text("analytics")
case .profile:
Text("profile")
case .plus:
Text("addForm")
}
}
var isButton: Bool {
self == .plus
}
}
@ylem
Copy link
Author

ylem commented Apr 10, 2023

Screenshot 2023-04-10 at 21 39 36

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment