From 61aa87452154c85d158f71ba9734f67b555c0196 Mon Sep 17 00:00:00 2001 From: Alex Ehlke Date: Thu, 16 Feb 2023 21:49:24 -0500 Subject: [PATCH 1/4] Downgrade platform requirements --- Package.swift | 2 +- Sources/NavigationBarStyles/FixedSizeNavBarView.swift | 2 ++ Sources/NavigationBarStyles/ScrollableNavBarView.swift | 2 ++ Sources/PagerTabStripView.swift | 2 +- 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 69b9f8b..42a8459 100644 --- a/Package.swift +++ b/Package.swift @@ -3,7 +3,7 @@ import PackageDescription let package = Package( name: "PagerTabStripView", - platforms: [.iOS(.v16), .macOS(.v13)], + platforms: [.iOS(.v15), .macOS(.v12)], products: [ .library(name: "PagerTabStripView", targets: ["PagerTabStripView"]) ], diff --git a/Sources/NavigationBarStyles/FixedSizeNavBarView.swift b/Sources/NavigationBarStyles/FixedSizeNavBarView.swift index 98f0ef3..321ea1c 100644 --- a/Sources/NavigationBarStyles/FixedSizeNavBarView.swift +++ b/Sources/NavigationBarStyles/FixedSizeNavBarView.swift @@ -8,6 +8,7 @@ import Foundation import SwiftUI +@available(iOS 16.0, *) internal struct FixedSizeNavBarView: View where SelectionType: Hashable { @Binding private var selection: SelectionType @@ -50,6 +51,7 @@ internal struct FixedSizeNavBarView: View where SelectionType: Ha } +@available(iOS 16.0, *) struct FixedSizeNavBarViewLayout: Layout { let spacing: CGFloat diff --git a/Sources/NavigationBarStyles/ScrollableNavBarView.swift b/Sources/NavigationBarStyles/ScrollableNavBarView.swift index 5c7b2c7..2da1a61 100644 --- a/Sources/NavigationBarStyles/ScrollableNavBarView.swift +++ b/Sources/NavigationBarStyles/ScrollableNavBarView.swift @@ -8,6 +8,7 @@ import Foundation import SwiftUI +@available(iOS 16.0, *) internal struct ScrollableNavBarView: View where SelectionType: Hashable { @Binding var selection: SelectionType @@ -66,6 +67,7 @@ internal struct ScrollableNavBarView: View where SelectionType: H } } +@available(iOS 16.0, *) struct ScrollableNavBarViewLayout: Layout { private let spacing: CGFloat diff --git a/Sources/PagerTabStripView.swift b/Sources/PagerTabStripView.swift index d1ad598..ca05148 100644 --- a/Sources/PagerTabStripView.swift +++ b/Sources/PagerTabStripView.swift @@ -27,7 +27,7 @@ public struct HorizontalContainerEdge: OptionSet { public static let both: HorizontalContainerEdge = [.left, .right] } -@available(iOS 16.0, *) +@available(iOS 15.0, *) public struct PagerTabStripView: View where SelectionType: Hashable, Content: View { private var content: () -> Content private var swipeGestureEnabled: Binding From f8b8bb6cac7db8f9e99a4ea283037c099b1fd3dc Mon Sep 17 00:00:00 2001 From: Alex Ehlke Date: Thu, 16 Feb 2023 21:52:52 -0500 Subject: [PATCH 2/4] Update NavBarModifier.swift --- Sources/NavBarModifier.swift | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Sources/NavBarModifier.swift b/Sources/NavBarModifier.swift index f8274f0..90fcd12 100644 --- a/Sources/NavBarModifier.swift +++ b/Sources/NavBarModifier.swift @@ -42,10 +42,14 @@ private struct NavBarWrapperView: View where SelectionType: Hasha case is SegmentedControlStyle: SegmentedNavBarView(selection: $selection) case let indicatorStyle as BarButtonStyle: - if indicatorStyle.scrollable { - ScrollableNavBarView(selection: $selection) + if #available(iOS 16, *) { + if indicatorStyle.scrollable { + ScrollableNavBarView(selection: $selection) + } else { + FixedSizeNavBarView(selection: $selection) + } } else { - FixedSizeNavBarView(selection: $selection) + SegmentedNavBarView(selection: $selection) } default: SegmentedNavBarView(selection: $selection) From 5e2df6dd00f0c79efcf004bb321c2758e194331a Mon Sep 17 00:00:00 2001 From: Alex Ehlke Date: Thu, 6 Apr 2023 19:24:53 -0400 Subject: [PATCH 3/4] fixes --- Sources/NavBarItem.swift | 6 +-- Sources/NavBarModifier.swift | 16 ++++---- .../FixedSizeNavBarView.swift | 11 ++++-- .../ScrollableNavBarView.swift | 11 ++++-- .../SegmentedNavBarView.swift | 10 ++++- Sources/PagerSettings.swift | 16 +++++--- Sources/PagerStyle.swift | 6 ++- Sources/PagerTabItemModifier.swift | 20 +++++++--- Sources/PagerTabStripView.swift | 38 ++++++++++++++----- Sources/View+Modifiers.swift | 4 ++ 10 files changed, 97 insertions(+), 41 deletions(-) diff --git a/Sources/NavBarItem.swift b/Sources/NavBarItem.swift index 1225fbf..0d48760 100644 --- a/Sources/NavBarItem.swift +++ b/Sources/NavBarItem.swift @@ -8,16 +8,16 @@ import SwiftUI struct NavBarItem: View, Identifiable where SelectionType: Hashable { - + var id: SelectionType @Binding private var selection: SelectionType @EnvironmentObject private var pagerSettings: PagerSettings - + public init(id: SelectionType, selection: Binding) { self.id = id self._selection = selection } - + @MainActor var body: some View { if let dataItem = pagerSettings.items[id] { dataItem.view diff --git a/Sources/NavBarModifier.swift b/Sources/NavBarModifier.swift index 90fcd12..9d10d2f 100644 --- a/Sources/NavBarModifier.swift +++ b/Sources/NavBarModifier.swift @@ -5,6 +5,7 @@ // Copyright © 2022 Xmartlabs SRL. All rights reserved. // +#if os(iOS) import SwiftUI struct NavBarModifier: ViewModifier where SelectionType: Hashable { @@ -17,12 +18,12 @@ struct NavBarModifier: ViewModifier where SelectionType: Hashable @MainActor func body(content: Content) -> some View { VStack(alignment: .leading, spacing: 0) { if !style.placedInToolbar { - NavBarWrapperView(selection: $selection) + NavBarWrapperView(selection: $selection) content } else { content.toolbar(content: { ToolbarItem(placement: .principal) { - NavBarWrapperView(selection: $selection) + NavBarWrapperView(selection: $selection) } }) } @@ -40,21 +41,22 @@ private struct NavBarWrapperView: View where SelectionType: Hasha case let barStyle as BarStyle: IndicatorBarView(selection: $selection, indicator: barStyle.indicatorView) case is SegmentedControlStyle: - SegmentedNavBarView(selection: $selection) + SegmentedNavBarView(selection: $selection) case let indicatorStyle as BarButtonStyle: if #available(iOS 16, *) { if indicatorStyle.scrollable { - ScrollableNavBarView(selection: $selection) + ScrollableNavBarView(selection: $selection) } else { - FixedSizeNavBarView(selection: $selection) + FixedSizeNavBarView(selection: $selection) } } else { - SegmentedNavBarView(selection: $selection) + SegmentedNavBarView(selection: $selection) } default: - SegmentedNavBarView(selection: $selection) + SegmentedNavBarView(selection: $selection) } } @Environment(\.pagerStyle) var style: PagerStyle } +#endif diff --git a/Sources/NavigationBarStyles/FixedSizeNavBarView.swift b/Sources/NavigationBarStyles/FixedSizeNavBarView.swift index 321ea1c..cdbc02e 100644 --- a/Sources/NavigationBarStyles/FixedSizeNavBarView.swift +++ b/Sources/NavigationBarStyles/FixedSizeNavBarView.swift @@ -8,7 +8,7 @@ import Foundation import SwiftUI -@available(iOS 16.0, *) +@available(iOS 16.0, macOS 13.0, *) internal struct FixedSizeNavBarView: View where SelectionType: Hashable { @Binding private var selection: SelectionType @@ -25,8 +25,11 @@ internal struct FixedSizeNavBarView: View where SelectionType: Ha Group { FixedSizeNavBarViewLayout(spacing: internalStyle.tabItemSpacing) { ForEach(pagerSettings.itemsOrderedByIndex, id: \.self) { tag in - NavBarItem(id: tag, selection: $selection) - .tag(tag) + if let dataItem = pagerSettings.items[tag] { + NavBarItem(id: tag, selection: $selection) + .tag(tag) + .id(dataItem.id) + } } internalStyle.indicatorView() .frame(height: internalStyle.indicatorViewHeight) @@ -51,7 +54,7 @@ internal struct FixedSizeNavBarView: View where SelectionType: Ha } -@available(iOS 16.0, *) +@available(iOS 16.0, macOS 13.0, *) struct FixedSizeNavBarViewLayout: Layout { let spacing: CGFloat diff --git a/Sources/NavigationBarStyles/ScrollableNavBarView.swift b/Sources/NavigationBarStyles/ScrollableNavBarView.swift index 2da1a61..a3d3631 100644 --- a/Sources/NavigationBarStyles/ScrollableNavBarView.swift +++ b/Sources/NavigationBarStyles/ScrollableNavBarView.swift @@ -8,7 +8,7 @@ import Foundation import SwiftUI -@available(iOS 16.0, *) +@available(iOS 16.0, macOS 13.0, *) internal struct ScrollableNavBarView: View where SelectionType: Hashable { @Binding var selection: SelectionType @@ -26,8 +26,11 @@ internal struct ScrollableNavBarView: View where SelectionType: H ScrollView(.horizontal, showsIndicators: false) { ScrollableNavBarViewLayout(spacing: internalStyle.tabItemSpacing) { ForEach(pagerSettings.itemsOrderedByIndex, id: \.self) { tag in - NavBarItem(id: tag, selection: $selection) - .tag(tag) + if let dataItem = pagerSettings.items[tag] { + NavBarItem(id: tag, selection: $selection) + .tag(tag) + .id(dataItem.id) + } } internalStyle.indicatorView() .frame(height: internalStyle.indicatorViewHeight) @@ -67,7 +70,7 @@ internal struct ScrollableNavBarView: View where SelectionType: H } } -@available(iOS 16.0, *) +@available(iOS 16.0, macOS 13.0, *) struct ScrollableNavBarViewLayout: Layout { private let spacing: CGFloat diff --git a/Sources/NavigationBarStyles/SegmentedNavBarView.swift b/Sources/NavigationBarStyles/SegmentedNavBarView.swift index cc5c264..f63e781 100644 --- a/Sources/NavigationBarStyles/SegmentedNavBarView.swift +++ b/Sources/NavigationBarStyles/SegmentedNavBarView.swift @@ -21,8 +21,14 @@ internal struct SegmentedNavBarView: View where SelectionType: Ha Picker("SegmentedNavBarView", selection: $selection) { if pagerSettings.items.count > 0 && pagerSettings.width > 0 { ForEach(pagerSettings.itemsOrderedByIndex, id: \.self) { tag in - NavBarItem(id: tag, selection: $selection) - .tag(tag) + if let dataItem = pagerSettings.items[tag] { + NavBarItem(id: tag, selection: $selection) + .tag(tag) + .id(dataItem.id) + } else { + NavBarItem(id: tag, selection: $selection) + .tag(tag) + } } } } diff --git a/Sources/PagerSettings.swift b/Sources/PagerSettings.swift index c529836..4695a56 100644 --- a/Sources/PagerSettings.swift +++ b/Sources/PagerSettings.swift @@ -8,18 +8,20 @@ import SwiftUI struct DataItem: Identifiable, Equatable where SelectedType: Hashable { - static func == (lhs: DataItem, rhs: DataItem) -> Bool { - return lhs.tag == rhs.tag + return lhs.tag == rhs.tag && lhs.id == rhs.id } private(set) var tag: SelectedType + fileprivate(set) var id: String? fileprivate(set) var view: AnyView fileprivate(set) var index: Int - var id: SelectedType { tag } +// var id: SelectedType { tag } +// var id: SelectedType { tag } - fileprivate init(tag: SelectedType, index: Int, view: AnyView) { + fileprivate init(tag: SelectedType, id: String?, index: Int, view: AnyView) { self.tag = tag + self.id = id self.index = index self.view = view } @@ -127,19 +129,21 @@ public class PagerSettings: ObservableObject where SelectionType: @Published private(set) var itemsOrderedByIndex = [SelectionType]() private func recalculateTransition() { + guard width > 0 else { return } let indexAndPercentage = -contentOffset / width let percentage = (indexAndPercentage + 1).truncatingRemainder(dividingBy: 1) let lowIndex = Int(floor(indexAndPercentage)) transition = TransitionProgress(from: itemsOrderedByIndex[safe: lowIndex], to: itemsOrderedByIndex[safe: lowIndex+1], percentage: percentage) } - func createOrUpdate(tag: SelectionType, index: Int, view: TabView) { + func createOrUpdate(tag: SelectionType, id: String?, index: Int, view: TabView) { if var dataItem = items[tag] { + dataItem.id = id dataItem.index = index dataItem.view = AnyView(view) items[tag] = dataItem } else { - items[tag] = DataItem(tag: tag, index: index, view: AnyView(view)) + items[tag] = DataItem(tag: tag, id: id, index: index, view: AnyView(view)) } } diff --git a/Sources/PagerStyle.swift b/Sources/PagerStyle.swift index 3522c48..ee44e00 100644 --- a/Sources/PagerStyle.swift +++ b/Sources/PagerStyle.swift @@ -44,11 +44,13 @@ extension PagerStyle where Self == SegmentedControlStyle { public static func segmentedControl(placedInToolbar: Bool = false, pagerAnimationOnTap: Animation? = DefaultPagerAnimation.onTap, pagerAnimationOnSwipe: Animation? = DefaultPagerAnimation.onSwipe, + navBarID: String? = nil, backgroundColor: Color = .white, padding: EdgeInsets = EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10)) -> SegmentedControlStyle { return SegmentedControlStyle(placedInToolbar: placedInToolbar, pagerAnimationOnTap: pagerAnimationOnTap, pagerAnimationOnSwipe: pagerAnimationOnSwipe, + navBarID: navBarID, backgroundColor: backgroundColor, padding: padding) } @@ -101,21 +103,23 @@ public struct SegmentedControlStyle: PagerStyle { public var placedInToolbar: Bool public var pagerAnimationOnTap: Animation? public var pagerAnimationOnSwipe: Animation? + public var navBarID: String? = nil public var backgroundColor: Color public var padding: EdgeInsets = EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0) public init(placedInToolbar: Bool = false, pagerAnimationOnTap: Animation? = DefaultPagerAnimation.onTap, pagerAnimationOnSwipe: Animation? = DefaultPagerAnimation.onSwipe, + navBarID: String? = nil, backgroundColor: Color = .white, padding: EdgeInsets = EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) { self.backgroundColor = backgroundColor + self.navBarID = navBarID self.placedInToolbar = placedInToolbar self.padding = padding self.pagerAnimationOnTap = pagerAnimationOnTap self.pagerAnimationOnSwipe = pagerAnimationOnSwipe } - } public struct BarStyle: PagerWithIndicatorStyle { diff --git a/Sources/PagerTabItemModifier.swift b/Sources/PagerTabItemModifier.swift index 1cf0400..b7eb6a5 100644 --- a/Sources/PagerTabItemModifier.swift +++ b/Sources/PagerTabItemModifier.swift @@ -11,9 +11,11 @@ struct PagerTabItemModifier: ViewModifier where Selec let navTabView: () -> NavTabView let tag: SelectionType - - init(tag: SelectionType, navTabView: @escaping () -> NavTabView) { + var id: String? + + init(tag: SelectionType, id: String? = nil, navTabView: @escaping () -> NavTabView) { self.tag = tag + self.id = id self.navTabView = navTabView } @@ -23,16 +25,24 @@ struct PagerTabItemModifier: ViewModifier where Selec .onAppear { let frame = geometryProxy.frame(in: .named("PagerViewScrollView")) index = Int(round(frame.minX / frame.width)) - pagerSettings.createOrUpdate(tag: tag, index: index, view: navTabView()) - }.onDisappear { + pagerSettings.createOrUpdate(tag: tag, id: id, index: index, view: navTabView()) + } + .onDisappear { pagerSettings.remove(tag: tag) } .onChange(of: geometryProxy.frame(in: .named("PagerViewScrollView"))) { newFrame in index = Int(round(newFrame.minX / newFrame.width)) } .onChange(of: index) { newIndex in - pagerSettings.createOrUpdate(tag: tag, index: newIndex, view: navTabView()) + pagerSettings.createOrUpdate(tag: tag, id: id, index: newIndex, view: navTabView()) + } + .onChange(of: id) { newID in + pagerSettings.createOrUpdate(tag: tag, id: newID, index: index, view: navTabView()) + } + .task { + pagerSettings.createOrUpdate(tag: tag, id: id, index: index, view: navTabView()) } + .id(id) } } diff --git a/Sources/PagerTabStripView.swift b/Sources/PagerTabStripView.swift index ca05148..26fb58b 100644 --- a/Sources/PagerTabStripView.swift +++ b/Sources/PagerTabStripView.swift @@ -4,6 +4,7 @@ // // Copyright © 2022 Xmartlabs SRL. All rights reserved. // +#if os(iOS) import SwiftUI class SelectionState: ObservableObject { @@ -37,7 +38,8 @@ public struct PagerTabStripView: View where SelectionTyp public init(swipeGestureEnabled: Binding = .constant(true), edgeSwipeGestureDisabled: Binding = .constant([]), - selection: Binding, @ViewBuilder content: @escaping () -> Content) { + selection: Binding, + @ViewBuilder content: @escaping () -> Content) { self.swipeGestureEnabled = swipeGestureEnabled self.edgeSwipeGestureDisabled = edgeSwipeGestureDisabled self._selectionState = StateObject(wrappedValue: SelectionState(selection: selection.wrappedValue)) @@ -48,12 +50,12 @@ public struct PagerTabStripView: View where SelectionTyp @MainActor public var body: some View { WrapperPagerTabStripView(swipeGestureEnabled: swipeGestureEnabled, edgeSwipeGestureDisabled: edgeSwipeGestureDisabled, - selection: selection, content: content) + selection: selection, + content: content) } } extension PagerTabStripView where SelectionType == Int { - public init(swipeGestureEnabled: Binding = .constant(true), edgeSwipeGestureDisabled: Binding = .constant([]), @ViewBuilder content: @escaping () -> Content) { @@ -71,7 +73,6 @@ extension PagerTabStripView where SelectionType == Int { } private struct WrapperPagerTabStripView: View where SelectionType: Hashable, Content: View { - private var content: Content @StateObject private var pagerSettings = PagerSettings() @Environment(\.pagerStyle) var style: PagerStyle @@ -80,10 +81,13 @@ private struct WrapperPagerTabStripView: View where Sele @Binding private var swipeGestureEnabled: Bool @Binding private var edgeSwipeGestureDisabled: HorizontalContainerEdge @State private var swipeOn: Bool = true + @State private var selectionOffset: CGFloat = 0 + @State private var opacity: CGFloat = 0 public init(swipeGestureEnabled: Binding, edgeSwipeGestureDisabled: Binding, - selection: Binding, @ViewBuilder content: @escaping () -> Content) { + selection: Binding, + @ViewBuilder content: @escaping () -> Content) { self._swipeGestureEnabled = swipeGestureEnabled self._edgeSwipeGestureDisabled = edgeSwipeGestureDisabled self._selection = selection @@ -97,9 +101,10 @@ private struct WrapperPagerTabStripView: View where Sele .frame(width: geometryProxy.size.width) } .coordinateSpace(name: "PagerViewScrollView") - .offset(x: -CGFloat(pagerSettings.indexOf(tag: selection) ?? 0) * geometryProxy.size.width) +// .offset(x: -CGFloat(pagerSettings.indexOf(tag: selection) ?? 0) * geometryProxy.size.width) + .offset(x: selectionOffset) .offset(x: translation) - .animation(style.pagerAnimationOnTap, value: selection) +// .animation(style.pagerAnimationOnTap, value: selection) .animation(style.pagerAnimationOnSwipe, value: translation) .gesture(swipeGestureEnabled && swipeOn ? DragGesture(minimumDistance: 25).onChanged { value in @@ -147,18 +152,33 @@ private struct WrapperPagerTabStripView: View where Sele pagerSettings.contentOffset = -(CGFloat(index)) * geometry.width } } - .onChange(of: selection) { newSelection in + .onChange(of: selection) { [oldSelection = selection] newSelection in pagerSettings.contentOffset = -(CGFloat(pagerSettings.indexOf(tag: newSelection) ?? 0) * geometryProxy.size.width) swipeOn = true + if pagerSettings.indexOf(tag: oldSelection) == nil { + selectionOffset = -CGFloat(pagerSettings.indexOf(tag: newSelection) ?? 0) * geometryProxy.size.width + } else { + withAnimation(style.pagerAnimationOnTap) { + selectionOffset = -CGFloat(pagerSettings.indexOf(tag: newSelection) ?? 0) * geometryProxy.size.width + } + } + withAnimation(.easeOut) { + opacity = pagerSettings.indexOf(tag: selection) == nil ? 0 : 1 + } } .onChange(of: translation) { _ in pagerSettings.contentOffset = translation - (CGFloat(pagerSettings.indexOf(tag: selection) ?? 0) * geometryProxy.size.width) swipeOn = true } + .task { + selectionOffset = -CGFloat(pagerSettings.indexOf(tag: selection) ?? 0) * geometryProxy.size.width + opacity = pagerSettings.indexOf(tag: selection) == nil ? 0 : 1 + } + .opacity(opacity) } .modifier(NavBarModifier(selection: $selection)) .environmentObject(pagerSettings) .clipped() } - } +#endif diff --git a/Sources/View+Modifiers.swift b/Sources/View+Modifiers.swift index 7ac72fc..c2e9b19 100644 --- a/Sources/View+Modifiers.swift +++ b/Sources/View+Modifiers.swift @@ -12,6 +12,10 @@ extension View { /// Sets the navigation bar item associated with this page. /// /// - Parameter pagerTabView: The navigation bar item to associate with this page. + public func pagerTabItem(tag: TagType, id: String, @ViewBuilder _ pagerTabView: @escaping () -> V) -> some View where TagType: Hashable, V: View { + return self.modifier(PagerTabItemModifier(tag: tag, id: id, navTabView: pagerTabView)) + } + public func pagerTabItem(tag: TagType, @ViewBuilder _ pagerTabView: @escaping () -> V) -> some View where TagType: Hashable, V: View { return self.modifier(PagerTabItemModifier(tag: tag, navTabView: pagerTabView)) } From 7f6018177694fc840efc6e732f40f2f190f11f49 Mon Sep 17 00:00:00 2001 From: Alex Ehlke Date: Wed, 1 Nov 2023 20:34:47 -0400 Subject: [PATCH 4/4] wip --- Sources/PagerSettings.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/PagerSettings.swift b/Sources/PagerSettings.swift index 4695a56..7cb9201 100644 --- a/Sources/PagerSettings.swift +++ b/Sources/PagerSettings.swift @@ -130,7 +130,7 @@ public class PagerSettings: ObservableObject where SelectionType: private func recalculateTransition() { guard width > 0 else { return } - let indexAndPercentage = -contentOffset / width + let indexAndPercentage = width == 0 ? 0 : -contentOffset / width let percentage = (indexAndPercentage + 1).truncatingRemainder(dividingBy: 1) let lowIndex = Int(floor(indexAndPercentage)) transition = TransitionProgress(from: itemsOrderedByIndex[safe: lowIndex], to: itemsOrderedByIndex[safe: lowIndex+1], percentage: percentage)