-
-
Save vantruong1094/fdbfe2441934430d8d0c411162844d6d to your computer and use it in GitHub Desktop.
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 TabBarDemo: View { | |
| @StateObject var tabItems = TabItems() | |
| var body: some View { | |
| ZStack { | |
| ///View1 | |
| NavigationView { | |
| ZStack { | |
| Color.gray | |
| Text("Home") | |
| } | |
| .navigationBarTitle("Home") | |
| } | |
| .opacity((tabItems.selectedTabIndex == 1) ? 1 : 0) | |
| ///View2 | |
| NavigationView { | |
| ZStack { | |
| Color.gray | |
| NavigationLink(destination: { | |
| ZStack { | |
| Color.gray | |
| Text("Second Search View") | |
| }.navigationBarTitle("Second View") | |
| }()) { | |
| Text("Search") | |
| } | |
| } | |
| .navigationBarTitle("Search") | |
| } | |
| .opacity((tabItems.selectedTabIndex == 2) ? 1 : 0) | |
| ///View3 | |
| NavigationView { | |
| ZStack { | |
| Color.gray | |
| Text("Add") | |
| } | |
| .navigationBarTitle("Add") | |
| } | |
| .opacity((tabItems.selectedTabIndex == 3) ? 1 : 0) | |
| ///View4 | |
| NavigationView { | |
| ZStack { | |
| Color.gray | |
| Text("Favorite") | |
| } | |
| .navigationBarTitle("Favorite") | |
| } | |
| .opacity((tabItems.selectedTabIndex == 4) ? 1 : 0) | |
| ///View5 | |
| NavigationView { | |
| ZStack { | |
| Color.gray | |
| Text("Profile") | |
| } | |
| .navigationBarTitle("Profile") | |
| } | |
| .opacity((tabItems.selectedTabIndex == 5) ? 1 : 0) | |
| TabBar(tabItems: tabItems) | |
| } | |
| } | |
| } | |
| struct TabBar: View { | |
| @ObservedObject var tabItems: TabItems | |
| let padding: CGFloat = 5 | |
| let iconeSize: CGFloat = 20 | |
| var iconFrame: CGFloat { | |
| (padding * 2) + iconeSize | |
| } | |
| var tabItemCount: CGFloat { | |
| CGFloat(tabItems.items.count) | |
| } | |
| var spacing: CGFloat { | |
| (UIScreen.main.bounds.width - (iconFrame * tabItemCount)) / (tabItemCount + 1) | |
| } | |
| var firstCenter: CGFloat { | |
| spacing + iconFrame/2 | |
| } | |
| var stepperToNextCenter: CGFloat { | |
| spacing + iconFrame //half of 1 and half of next | |
| } | |
| var body: some View { | |
| VStack { | |
| Spacer() | |
| ZStack { | |
| Bar(tabItems: tabItems, | |
| firstCenter: firstCenter, | |
| stepperToNextCenter: stepperToNextCenter) | |
| .foregroundColor(.white) | |
| .frame(width: UIScreen.main.bounds.width, height: 50) | |
| HStack(spacing: spacing) { | |
| ForEach(0..<tabItems.items.count, id: \.self) { i in | |
| ZStack { | |
| Image(systemName: self.tabItems.items[i].imageName) | |
| .resizable() | |
| .foregroundColor(Color.gray) | |
| .frame(width: self.iconeSize, height: self.iconeSize) | |
| .opacity(self.tabItems.items[i].opacity) | |
| .padding(.all, padding) | |
| .background(Color.white) | |
| .clipShape(Circle()) | |
| .onTapGesture { | |
| withAnimation(Animation.easeInOut) { | |
| self.tabItems.select(i) | |
| } | |
| } | |
| } | |
| .offset(y: self.tabItems.items[i].offset) | |
| } | |
| } | |
| .edgesIgnoringSafeArea(.all) | |
| } | |
| } | |
| } | |
| } | |
| struct Bar: Shape { | |
| @ObservedObject var tabItems: TabItems | |
| var tab: CGFloat | |
| let firstCenter: CGFloat | |
| let stepperToNextCenter: CGFloat | |
| init(tabItems: TabItems, firstCenter: CGFloat, stepperToNextCenter: CGFloat) { | |
| self.tabItems = tabItems | |
| self.tab = tabItems.selectedTabIndex | |
| self.firstCenter = firstCenter | |
| self.stepperToNextCenter = stepperToNextCenter | |
| } | |
| var animatableData: Double { | |
| get { return Double(tab) } | |
| set { tab = CGFloat(newValue) } | |
| } | |
| func path(in rect: CGRect) -> Path { | |
| let tabCenter = firstCenter + stepperToNextCenter * (tab - 1) | |
| return Path { p in | |
| p.move(to: CGPoint(x: rect.minX, y: rect.minY)) | |
| p.addLine(to: CGPoint(x: rect.minX, y: rect.maxY)) | |
| p.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY)) | |
| p.addLine(to: CGPoint(x: rect.maxX, y: rect.minY)) | |
| p.addLine(to: CGPoint(x: tabCenter + 50, y: rect.minY)) | |
| p.addCurve(to: CGPoint(x: tabCenter, y: rect.midY), | |
| control1: CGPoint(x: tabCenter + 20, y: rect.minY), | |
| control2: CGPoint(x: tabCenter + 20, y: rect.minY + 25)) | |
| p.addCurve(to: CGPoint(x: tabCenter - 50, y: rect.minY), | |
| control1: CGPoint(x: tabCenter - 20, y: rect.minY + 25), | |
| control2: CGPoint(x: tabCenter - 20, y: rect.minY)) | |
| p.addLine(to: CGPoint(x: rect.maxX - tabCenter, y: rect.minY)) | |
| } | |
| } | |
| } | |
| class TabItem: Identifiable { | |
| let id = UUID() | |
| let imageName: String | |
| var offset: CGFloat = -5 | |
| var opacity: Double = 1 | |
| init(imageName: String, offset: CGFloat) { | |
| self.imageName = imageName | |
| self.offset = offset | |
| } | |
| init(imageName: String) { | |
| self.imageName = imageName | |
| } | |
| } | |
| class TabItems: ObservableObject { | |
| @Published var items: [TabItem] = [ | |
| TabItem(imageName: "house", offset: -20), | |
| TabItem(imageName: "magnifyingglass"), | |
| TabItem(imageName: "plus.app"), | |
| TabItem(imageName: "heart"), | |
| TabItem(imageName: "person"), | |
| ] | |
| @Published var selectedTabIndex: CGFloat = 1 | |
| func select(_ index: Int) { | |
| let tabItem = items[index] | |
| tabItem.opacity = 0 | |
| tabItem.offset = 15 | |
| withAnimation(Animation.easeInOut) { | |
| selectedTabIndex = CGFloat(index + 1) | |
| for i in 0..<items.count { | |
| if i != index { | |
| items[i].offset = -5 | |
| } | |
| } | |
| } | |
| withAnimation(Animation.easeOut(duration: 0.25).delay(0.25)) { | |
| tabItem.opacity = 1 | |
| tabItem.offset = -25 | |
| } | |
| } | |
| } | |
| struct CustomTabBar_Previews: PreviewProvider { | |
| static var previews: some View { | |
| TabBarDemo() | |
| } | |
| } |
Author
vantruong1094
commented
Nov 1, 2022

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