From a1cb84d56d849976898e351ceb3a45c960e12ad4 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Tue, 30 Apr 2024 20:45:25 -0500 Subject: [PATCH] Update with latest, update for iOS only, update for iOS 17 only --- CLAnimatedTabView/Package.swift | 2 +- .../CLAnimatedTabView/CLAnimatedTabView.swift | 59 +++++++------ .../CLAnimatedTabViewModel.swift | 82 ++++++++++++++++++- 3 files changed, 109 insertions(+), 34 deletions(-) diff --git a/CLAnimatedTabView/Package.swift b/CLAnimatedTabView/Package.swift index 7df58f8..cb004d4 100644 --- a/CLAnimatedTabView/Package.swift +++ b/CLAnimatedTabView/Package.swift @@ -5,7 +5,7 @@ import PackageDescription let package = Package( name: "CLAnimatedTabView", - platforms: [.iOS(.v16)], + platforms: [.iOS(.v17)], products: [ // Products define the executables and libraries a package produces, making them visible to other packages. .library( diff --git a/CLAnimatedTabView/Sources/CLAnimatedTabView/CLAnimatedTabView.swift b/CLAnimatedTabView/Sources/CLAnimatedTabView/CLAnimatedTabView.swift index d6e5699..e337319 100644 --- a/CLAnimatedTabView/Sources/CLAnimatedTabView/CLAnimatedTabView.swift +++ b/CLAnimatedTabView/Sources/CLAnimatedTabView/CLAnimatedTabView.swift @@ -15,19 +15,19 @@ import Foundation import SwiftUI -struct CLAnimatedTabView: View { - let tabBarHeight = 48.0 - let backgroundColor = Color.white - let shadowOpacity = 0.13 - let shadowRadius = 2.0 - let shadowX = 0.0 - let shadowY = 2.0 +#if os(iOS) +public struct CLAnimatedTabView: View { @State var currentTab: Int = 0 @ObservedObject var viewModel: CLAnimatedTabViewModel var views: [Content] - var body: some View { + public init(viewModel: CLAnimatedTabViewModel, _ views: Content...) { + self.viewModel = viewModel + self.views = views + } + + public var body: some View { ZStack(alignment: .top) { TabView(selection: $currentTab) { ForEach(views.indices, id: \.self) { index in @@ -37,16 +37,17 @@ struct CLAnimatedTabView: View { .tabViewStyle(.page(indexDisplayMode: .never)) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom) .animation(.easeInOut, value: currentTab) - .padding(.top, tabBarHeight) + .padding(.top, viewModel.tabBarHeight) TabBarView(currentTab: $currentTab, - tabBarItemNames: $viewModel.tabNames) - .frame(height: tabBarHeight) - .background(Color.white) - .shadow(color: Color.black.opacity(shadowOpacity), - radius: shadowRadius, - x: shadowX, - y: shadowY) + tabBarItemNames: $viewModel.tabNames, + tabBarItemViewModel: $viewModel.tabBarItemViewModel) + .frame(height: viewModel.tabBarHeight) + .cornerRadius(0) + .shadow(color: Color.black.opacity(viewModel.shadowOpacity), + radius: viewModel.shadowRadius, + x: viewModel.shadowX, + y: viewModel.shadowY) } } } @@ -59,6 +60,7 @@ struct TabBarView: View { @Binding var currentTab: Int @Namespace var namespace @Binding var tabBarItemNames: [String] + @Binding var tabBarItemViewModel: CLAnimatedTabViewItemModel var body: some View { HStack(spacing: 0) { @@ -67,6 +69,7 @@ struct TabBarView: View { content: { index, name in TabBarItem(currentTab: $currentTab, isSelected: index == 0, + viewModel: $tabBarItemViewModel, name: name, tab: index, namespace: namespace) @@ -80,16 +83,12 @@ struct TabBarView: View { struct TabBarItem: View { struct Constants { static let underlineId = "underline" - static let barHeight = 2.0 - static let font = Font.system(size: 15.0) - static let backgroundColor = Color.gray - static let textInactiveColor = Color.brown - static let textActiveColor = Color.blue static let transitionScale = 1.0 } @Binding var currentTab: Int @State var isSelected: Bool = false + @Binding var viewModel: CLAnimatedTabViewItemModel var name: String var tab: Int @@ -104,9 +103,9 @@ struct TabBarItem: View { VStack { Spacer() Text(name) - .font(Constants.font) + .font(viewModel.font) .frame(alignment: .center) - .onChange(of: currentTab) { _ in + .onChange(of: currentTab) { isSelected = (currentTab == tab) } @@ -115,33 +114,31 @@ struct TabBarItem: View { if currentTab == tab { Capsule(style: .continuous) .fill(Color.blue) - .frame(height: Constants.barHeight) + .frame(height: viewModel.capsuleHeight) .matchedGeometryEffect(id: Constants.underlineId, in: namespace) .transition(.asymmetric(insertion: .scale(scale: Constants.transitionScale), removal: .slide)) } else { Capsule() .fill(Color.clear) - .frame(height: Constants.barHeight) + .frame(height: viewModel.capsuleHeight) } } .frame(maxWidth: .infinity, alignment: .bottom) } }) - .buttonStyle(TabViewButtonStyle()) + .buttonStyle(CLTabViewButtonStyle()) .frame(maxWidth: .infinity, maxHeight: .infinity) .animation(.spring(), value: currentTab) - .background(Constants.backgroundColor) - .accentColor(isSelected ? Constants.textActiveColor : Constants.textInactiveColor) + .accentColor(isSelected ? viewModel.activeTextColor : viewModel.inactiveTextColor) } } -struct TabViewButtonStyle: ButtonStyle { +struct CLTabViewButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .contentShape(Rectangle()) .foregroundColor(.accentColor) } } - - +#endif diff --git a/CLAnimatedTabView/Sources/CLAnimatedTabView/CLAnimatedTabViewModel.swift b/CLAnimatedTabView/Sources/CLAnimatedTabView/CLAnimatedTabViewModel.swift index 0de513a..712698f 100644 --- a/CLAnimatedTabView/Sources/CLAnimatedTabView/CLAnimatedTabViewModel.swift +++ b/CLAnimatedTabView/Sources/CLAnimatedTabView/CLAnimatedTabViewModel.swift @@ -1,13 +1,91 @@ // // CLAnimatedTabViewModel.swift -// +// // // Created by Chris Larsen on 4/14/24. // import SwiftUI +/// View Model used for CLAnimatedTabView +/// - Parameters: +/// - tabBarHeight: Height for the tab bar +/// - backgroundColor: BackgroundColor for entire view +/// - displayTabBarShadow: Determines whether tab bar will have a drop shadow +/// - shadowOpacity: Opacity of tab bar drop shadow +/// - shadowRadius: Radius for tab bar drop shadow +/// - shadowX: X offset of tab bar drop shadow +/// - shadowY: Y offset of tab bar drop shadow +/// - tabNames: String array of names for tab bar items +/// - tabBarItemViewModel: View model for tab bar items public class CLAnimatedTabViewModel: ObservableObject { + /// Height for tab bar + /// # Notes # + /// Default value is 48.0 + @Published var tabBarHeight: CGFloat + /// Background color for entire view + /// # Notes # + /// Default value is white + @Published var backgroundColor: Color + /// Enable or disable tab bar shadow + /// # Notes # + /// Default set to true + @Published var displayTabBarShadow: Bool + /// Opacity for tab bar shadow + /// # Notes # + /// Default opacity 0.13 + @Published var shadowOpacity: CGFloat + /// Radius for tab bar shadow + /// # Notes # + /// Default is 2.0 + @Published var shadowRadius: CGFloat + /// X position for tab bar shadow + /// # Notes # + /// Default is 0 + @Published var shadowX: CGFloat + /// Y position for tab bar shadow + /// # Notes # + /// Default is 2.0 + @Published var shadowY: CGFloat /// Text to display in each tab of view - @Published public var tabNames: [String] = [] + @Published var tabNames: [String] + /// View model for tab bar items + @Published var tabBarItemViewModel: CLAnimatedTabViewItemModel + + init(tabBarHeight: CGFloat = 48.0, + backgroundColor: Color = .white, + displayTabBarShadow: Bool = true, + shadowOpacity: CGFloat = 0.13, + shadowRadius: CGFloat = 2.0, + shadowX: CGFloat = 0.0, + shadowY: CGFloat = 2.0, + tabNames: [String], + tabBarItemViewModel: CLAnimatedTabViewItemModel = CLAnimatedTabViewItemModel()) { + self.tabBarHeight = tabBarHeight + self.backgroundColor = backgroundColor + self.displayTabBarShadow = displayTabBarShadow + self.shadowOpacity = shadowOpacity + self.shadowRadius = shadowRadius + self.shadowX = shadowX + self.shadowY = shadowY + self.tabNames = tabNames + self.tabBarItemViewModel = tabBarItemViewModel + } +} + +public class CLAnimatedTabViewItemModel: ObservableObject { + @Published var capsuleHeight: CGFloat + @Published var font: Font + @Published var inactiveTextColor: Color + @Published var activeTextColor: Color + + init(capsuleHeight: CGFloat = 2.0, + font: Font = Font.system(size: 15.0), + inactiveTextColor: Color = Color.gray, + activeTextColor: Color = Color.blue) { + self.capsuleHeight = capsuleHeight + self.font = font + self.inactiveTextColor = inactiveTextColor + self.activeTextColor = activeTextColor + } }