Last active
April 10, 2023 20:40
-
-
Save ylem/6d290bf9ef368ea3ae1c84d936392039 to your computer and use it in GitHub Desktop.
Custom TabBar
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
Author
ylem
commented
Apr 10, 2023
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment