Skip to content

Commit

Permalink
Update with latest, update for iOS only, update for iOS 17 only
Browse files Browse the repository at this point in the history
  • Loading branch information
cjlarsen committed May 1, 2024
1 parent 6837a1e commit a1cb84d
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 34 deletions.
2 changes: 1 addition & 1 deletion CLAnimatedTabView/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
59 changes: 28 additions & 31 deletions CLAnimatedTabView/Sources/CLAnimatedTabView/CLAnimatedTabView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,19 @@
import Foundation
import SwiftUI

struct CLAnimatedTabView<Content: View>: 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<Content: View>: 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
Expand All @@ -37,16 +37,17 @@ struct CLAnimatedTabView<Content: View>: 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)
}
}
}
Expand All @@ -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) {
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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)
}

Expand All @@ -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
Original file line number Diff line number Diff line change
@@ -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
}
}

0 comments on commit a1cb84d

Please sign in to comment.