diff --git a/Sample.xcodeproj/project.pbxproj b/Sample.xcodeproj/project.pbxproj index 73a2468..4f21ba8 100644 --- a/Sample.xcodeproj/project.pbxproj +++ b/Sample.xcodeproj/project.pbxproj @@ -13,7 +13,7 @@ 17D9E0FA23D4CF6900C5AE93 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 17D9E0F923D4CF6900C5AE93 /* Assets.xcassets */; }; 17D9E0FD23D4CF6900C5AE93 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 17D9E0FC23D4CF6900C5AE93 /* Assets.xcassets */; }; 17D9E10023D4CF6900C5AE93 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 17D9E0FE23D4CF6900C5AE93 /* LaunchScreen.storyboard */; }; - 6B3489BC23D85D46002601D8 /* SwiftUIPager in Frameworks */ = {isa = PBXBuildFile; productRef = 6B3489BB23D85D46002601D8 /* SwiftUIPager */; }; + 6BB892E023D9F13C00AC9331 /* SwiftUIPager in Frameworks */ = {isa = PBXBuildFile; productRef = 6BB892DF23D9F13C00AC9331 /* SwiftUIPager */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -32,7 +32,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 6B3489BC23D85D46002601D8 /* SwiftUIPager in Frameworks */, + 6BB892E023D9F13C00AC9331 /* SwiftUIPager in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -94,7 +94,7 @@ ); name = Sample; packageProductDependencies = ( - 6B3489BB23D85D46002601D8 /* SwiftUIPager */, + 6BB892DF23D9F13C00AC9331 /* SwiftUIPager */, ); productName = SwiftUIPager; productReference = 17D9E0F023D4CF6700C5AE93 /* Sample.app */; @@ -125,7 +125,7 @@ ); mainGroup = 17D9E0E723D4CF6700C5AE93; packageReferences = ( - 6B3489BA23D85D46002601D8 /* XCRemoteSwiftPackageReference "SwiftUIPager" */, + 6BB892DE23D9F13C00AC9331 /* XCRemoteSwiftPackageReference "SwiftUIPager" */, ); productRefGroup = 17D9E0F123D4CF6700C5AE93 /* Products */; projectDirPath = ""; @@ -352,20 +352,20 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 6B3489BA23D85D46002601D8 /* XCRemoteSwiftPackageReference "SwiftUIPager" */ = { + 6BB892DE23D9F13C00AC9331 /* XCRemoteSwiftPackageReference "SwiftUIPager" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/fermoya/SwiftUIPager"; requirement = { - branch = "feat/vertical-pagintation"; + branch = "fix/page-offset"; kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 6B3489BB23D85D46002601D8 /* SwiftUIPager */ = { + 6BB892DF23D9F13C00AC9331 /* SwiftUIPager */ = { isa = XCSwiftPackageProductDependency; - package = 6B3489BA23D85D46002601D8 /* XCRemoteSwiftPackageReference "SwiftUIPager" */; + package = 6BB892DE23D9F13C00AC9331 /* XCRemoteSwiftPackageReference "SwiftUIPager" */; productName = SwiftUIPager; }; /* End XCSwiftPackageProductDependency section */ diff --git a/Sample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Sample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index fcde048..bea2a7f 100644 --- a/Sample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Sample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,8 +5,8 @@ "package": "SwiftUIPager", "repositoryURL": "https://github.com/fermoya/SwiftUIPager", "state": { - "branch": "feat/vertical-pagintation", - "revision": "824efeeaac08717ef7d1aefc5037c3e3f32736e6", + "branch": "fix/page-offset", + "revision": "3b05829020ec5e10a0997455b2a7fb18dfb4af53", "version": null } } diff --git a/Sample/ContentView.swift b/Sample/ContentView.swift index 65eec2a..a631980 100644 --- a/Sample/ContentView.swift +++ b/Sample/ContentView.swift @@ -14,34 +14,48 @@ extension Int: Identifiable { } struct ContentView: View { - + + @State var isPresented: Bool = false @State var pageIndex: Int = 0 var data: [Int] = Array((0...20)) var body: some View { + Button(action: { + self.isPresented.toggle() + }, label: { + Text("Tap me") + }).sheet(isPresented: $isPresented, content: { + self.presentedView + }) + } + + var presentedView: some View { GeometryReader { proxy in - VStack { - Pager(page: self.$pageIndex, - data: self.data, - content: { index in - self.pageView(index) - .cornerRadius(10) - .shadow(radius: 5) - }) - .interactive(0.8) - .itemSpacing(10) - .padding(8) - .itemAspectRatio(1.5) - .vertical() - .border(Color.red, width: 2) - .frame(width: min(proxy.size.width, - proxy.size.height), - height: min(proxy.size.width, - proxy.size.height)) - Spacer() - Text("Page: \(self.pageIndex)") - .bold() - Spacer() + ScrollView { + VStack { + Pager(page: self.$pageIndex, + data: self.data, + content: { index in + self.pageView(index) + .cornerRadius(10) + .shadow(radius: 5) + }) + .interactive(0.8) + .itemSpacing(10) + .padding(8) + .itemAspectRatio(0.8) + .itemTappable(true) + .frame(width: min(proxy.size.width, + proxy.size.height), + height: min(proxy.size.width, + proxy.size.height)) + .border(Color.red, width: 2) + ForEach(self.data) { i in + Text("Page: \(i)") + .bold() + .padding() + } + } } } } diff --git a/Sources/SwiftUIPager/Helpers/SizeViewModifier.swift b/Sources/SwiftUIPager/Helpers/SizeViewModifier.swift index b4a9057..d678099 100644 --- a/Sources/SwiftUIPager/Helpers/SizeViewModifier.swift +++ b/Sources/SwiftUIPager/Helpers/SizeViewModifier.swift @@ -8,15 +8,6 @@ import SwiftUI -/// Tracks the size of the view -struct SizePreferenceKey: PreferenceKey { - static var defaultValue: CGSize = .zero - - static func reduce(value: inout CGSize, nextValue: () -> CGSize) { - value = nextValue() - } -} - /// This modifier wraps a view into a `GeometryReader` and tracks the available space by using `SizePreferenceKey` on the content struct SizeViewModifier: ViewModifier { @@ -25,12 +16,11 @@ struct SizeViewModifier: ViewModifier { func body(content: Content) -> some View { GeometryReader { proxy in content - .preference(key: SizePreferenceKey.self, - value: proxy.size) + .frame(width: proxy.size.width, height: proxy.size.height) + .onAppear (perform: { + self.size = proxy.size + }) } - .onPreferenceChange(SizePreferenceKey.self, perform: { (newSize) in - self.size = newSize - }) .clipped() } } diff --git a/Sources/SwiftUIPager/Pager+Buildable.swift b/Sources/SwiftUIPager/Pager+Buildable.swift index 5bee404..de7397b 100644 --- a/Sources/SwiftUIPager/Pager+Buildable.swift +++ b/Sources/SwiftUIPager/Pager+Buildable.swift @@ -10,6 +10,11 @@ import SwiftUI extension Pager: Buildable { + /// Adds a `TapGesture` to the items to bring them to focus + public func itemTappable(_ value: Bool) -> Self { + mutating(keyPath: \.isItemTappable, value: value) + } + /// Returns a horizontal pager public func horizontal() -> Self { mutating(keyPath: \.isHorizontal, value: true) @@ -39,10 +44,9 @@ extension Pager: Buildable { .mutating(keyPath: \.shouldRotate, value: value) } - /// Provides an offset to modify the - public func contentOffset(_ pageOffset: Double) -> Self { - let contentOffset = CGFloat(pageOffset) * pageDistance - return mutating(keyPath: \.contentOffset, value: contentOffset) + /// Provides an increment to the page index offset. Use this to modify the scroll offset + public func pageOffset(_ pageOffset: Double) -> Self { + mutating(keyPath: \.pageOffset, value: pageOffset) } /// Adds space between each page diff --git a/Sources/SwiftUIPager/Pager+Helper.swift b/Sources/SwiftUIPager/Pager+Helper.swift index 4731442..6a2bd5b 100644 --- a/Sources/SwiftUIPager/Pager+Helper.swift +++ b/Sources/SwiftUIPager/Pager+Helper.swift @@ -15,6 +15,11 @@ extension Pager { return !isHorizontal } + /// `pageOffset` converted to scrollable offset + var contentOffset: CGFloat { + -CGFloat(pageOffset) * pageDistance + } + /// Size increment to be applied to a unfocs item when it comes to focus var scaleIncrement: CGFloat { 1 - interactiveScale } @@ -88,7 +93,7 @@ extension Pager { } /// Data that is being displayed at the moment - var dataDisplayed: [Data] { + var dataDisplayed: [Element] { Array(data[lowerPageDisplayed.. Angle { + func angle(for item: Element) -> Angle { guard shouldRotate else { return .zero } guard let index = data.firstIndex(of: item) else { return .zero } @@ -128,7 +133,7 @@ extension Pager { } /// Axis for the rotations effect - func axis(for item: Data) -> (CGFloat, CGFloat, CGFloat) { + func axis(for item: Element) -> (CGFloat, CGFloat, CGFloat) { guard shouldRotate else { return (0, 0, 0) } guard let index = data.firstIndex(of: item) else { return (0, 0, 0) } @@ -137,7 +142,7 @@ extension Pager { } /// Scale that applies to a particular item - func scale(for item: Data) -> CGFloat { + func scale(for item: Element) -> CGFloat { guard isDragging else { return isFocused(item) ? 1 : interactiveScale } let totalIncrement = abs(totalOffset / pageDistance) @@ -159,7 +164,7 @@ extension Pager { } /// Returns true if the item is focused on the screen. - func isFocused(_ item: Data) -> Bool { + func isFocused(_ item: Element) -> Bool { data.firstIndex(of: item) == currentPage } diff --git a/Sources/SwiftUIPager/Pager.swift b/Sources/SwiftUIPager/Pager.swift index b2947e9..81d840e 100644 --- a/Sources/SwiftUIPager/Pager.swift +++ b/Sources/SwiftUIPager/Pager.swift @@ -29,7 +29,7 @@ import SwiftUI /// - 30 px of vertical insets /// - 0.6 shrink ratio for items that aren't focused. /// -public struct Pager: View where Content: View, Data: Identifiable & Equatable { +public struct Pager: View where PageView: View, Element: Identifiable & Equatable { /// `Direction` determines the direction of the swipe gesture enum Direction { @@ -58,13 +58,17 @@ public struct Pager: View where Content: View, Data: Identifiabl /*** Dependencies ***/ /// `ViewBuilder` block to create each page - let content: (Data) -> Content + let content: (Element) -> PageView /// Array of items that will populate each page - var data: [Data] + var data: [Element] /*** ViewModified properties ***/ + /// `true` if items are tapable + var isItemTappable: Bool = false + + /// `true` if the pager is horizontal var isHorizontal: Bool = true /// Shrink ratio that affects the items that aren't focused @@ -74,7 +78,7 @@ public struct Pager: View where Content: View, Data: Identifiabl var shouldRotate: Bool = false /// Used to modify `Pager` offset outside this view - var contentOffset: CGFloat = 0 + var pageOffset: Double = 0 /// Vertical padding var sideInsets: CGFloat = 0 @@ -111,12 +115,12 @@ public struct Pager: View where Content: View, Data: Identifiabl /// - Parameter page: Binding to the index of the focused page /// - Parameter data: Array of items to populate the content /// - Parameter content: Factory method to build new pages - public init(page: Binding, data: [Data], @ViewBuilder content: @escaping (Data) -> Content) { + public init(page: Binding, data: [Element], @ViewBuilder content: @escaping (Element) -> PageView) { self._page = page self.data = data self.content = content } - + public var body: some View { HStack(spacing: self.interactiveItemSpacing) { ForEach(self.dataDisplayed) { item in @@ -127,11 +131,8 @@ public struct Pager: View where Content: View, Data: Identifiabl axis: (0, 0, 1)) .rotation3DEffect(self.angle(for: item), axis: self.axis(for: item)) - .onTapGesture (perform: { - withAnimation(.spring()) { - self.scrollToItem(item) - } - }) + .gesture(self.tapGesture(for: item)) + .disabled(self.isFocused(item) || !self.isItemTappable) } .offset(x: self.xOffset, y : 0) } @@ -147,11 +148,20 @@ public struct Pager: View where Content: View, Data: Identifiabl extension Pager { /// Helper function to scroll to a specific item. - func scrollToItem(_ item: Data) { + func scrollToItem(_ item: Element) { guard let index = data.firstIndex(of: item) else { return } self.page = index } + func tapGesture(for item: Element) -> some Gesture { + TapGesture(count: 1) + .onEnded({ _ in + withAnimation(.spring()) { + self.scrollToItem(item) + } + }) + } + /// `DragGesture` customized to work with `Pager` var swipeGesture: some Gesture { DragGesture() diff --git a/SwiftUIPager.podspec b/SwiftUIPager.podspec index 6c0907f..806c24d 100644 --- a/SwiftUIPager.podspec +++ b/SwiftUIPager.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "SwiftUIPager" - s.version = "1.1.0" + s.version = "1.1.1" s.summary = "Native pager for SwiftUI. Easily to use, easy to customize." s.description = <<-DESC