From 298378f212a13b515ccb9c0a230c4ea0282ab58e Mon Sep 17 00:00:00 2001 From: Fernando Moya de Rivas Date: Sun, 5 Jul 2020 21:50:26 +0100 Subject: [PATCH] New modifier to change content loading policy --- .../project.pbxproj | 4 +++ .../ContentLoadingPolicy.swift | 27 +++++++++++++++++++ .../PageConfiguration/GesturePriority.swift | 5 +++- Sources/SwiftUIPager/Pager+Buildable.swift | 10 +++++++ Sources/SwiftUIPager/Pager+Helper.swift | 19 +++++++++++-- Sources/SwiftUIPager/Pager.swift | 6 ++--- SwiftUIPager.podspec | 2 +- SwiftUIPager.xcodeproj/project.pbxproj | 12 +++++++++ .../Pager+Buildable_Tests.swift | 24 ++++++++++++++++- 9 files changed, 100 insertions(+), 9 deletions(-) create mode 100644 Sources/SwiftUIPager/PageConfiguration/ContentLoadingPolicy.swift diff --git a/Example/SwiftUIPagerExample.xcodeproj/project.pbxproj b/Example/SwiftUIPagerExample.xcodeproj/project.pbxproj index 810981b..e528737 100644 --- a/Example/SwiftUIPagerExample.xcodeproj/project.pbxproj +++ b/Example/SwiftUIPagerExample.xcodeproj/project.pbxproj @@ -23,6 +23,7 @@ 6BC5EE0024866D9500E1E78C /* SizeViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BC5EDF924866D9500E1E78C /* SizeViewModifier.swift */; }; 6BC5EE0124866D9500E1E78C /* Pager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BC5EDFA24866D9500E1E78C /* Pager.swift */; }; 6BC5EE0224866D9500E1E78C /* Pager+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BC5EDFB24866D9500E1E78C /* Pager+Helper.swift */; }; + 6BCF139224B2677B00AADE74 /* ContentLoadingPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BCF139124B2677B00AADE74 /* ContentLoadingPolicy.swift */; }; 6BEA731324ACF8D7007EA8DC /* PositionAlignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BEA730F24ACF8D7007EA8DC /* PositionAlignment.swift */; }; 6BEA731424ACF8D7007EA8DC /* SwipeDirection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BEA731024ACF8D7007EA8DC /* SwipeDirection.swift */; }; 6BEA731524ACF8D7007EA8DC /* SwipeInteractionArea.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BEA731124ACF8D7007EA8DC /* SwipeInteractionArea.swift */; }; @@ -62,6 +63,7 @@ 6BC5EDF924866D9500E1E78C /* SizeViewModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SizeViewModifier.swift; sourceTree = ""; }; 6BC5EDFA24866D9500E1E78C /* Pager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Pager.swift; path = ../../Sources/SwiftUIPager/Pager.swift; sourceTree = ""; }; 6BC5EDFB24866D9500E1E78C /* Pager+Helper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Pager+Helper.swift"; path = "../../Sources/SwiftUIPager/Pager+Helper.swift"; sourceTree = ""; }; + 6BCF139124B2677B00AADE74 /* ContentLoadingPolicy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ContentLoadingPolicy.swift; path = ../../Sources/SwiftUIPager/PageConfiguration/ContentLoadingPolicy.swift; sourceTree = ""; }; 6BEA730F24ACF8D7007EA8DC /* PositionAlignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PositionAlignment.swift; path = ../../Sources/SwiftUIPager/PageConfiguration/PositionAlignment.swift; sourceTree = ""; }; 6BEA731024ACF8D7007EA8DC /* SwipeDirection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwipeDirection.swift; path = ../../Sources/SwiftUIPager/PageConfiguration/SwipeDirection.swift; sourceTree = ""; }; 6BEA731124ACF8D7007EA8DC /* SwipeInteractionArea.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwipeInteractionArea.swift; path = ../../Sources/SwiftUIPager/PageConfiguration/SwipeInteractionArea.swift; sourceTree = ""; }; @@ -157,6 +159,7 @@ 6BEA730E24ACF8BB007EA8DC /* PageConfiguration */ = { isa = PBXGroup; children = ( + 6BCF139124B2677B00AADE74 /* ContentLoadingPolicy.swift */, 6BEA731224ACF8D7007EA8DC /* GesturePriority.swift */, 6BEA730F24ACF8D7007EA8DC /* PositionAlignment.swift */, 6BEA731024ACF8D7007EA8DC /* SwipeDirection.swift */, @@ -256,6 +259,7 @@ 6B22DC81247E5C9A00EF95C5 /* NestedExampleView.swift in Sources */, 17D9E0F823D4CF6700C5AE93 /* ContentView.swift in Sources */, 6BC5EDFC24866D9500E1E78C /* Pager+Buildable.swift in Sources */, + 6BCF139224B2677B00AADE74 /* ContentLoadingPolicy.swift in Sources */, 6BEA731524ACF8D7007EA8DC /* SwipeInteractionArea.swift in Sources */, 6BC5EE0124866D9500E1E78C /* Pager.swift in Sources */, ); diff --git a/Sources/SwiftUIPager/PageConfiguration/ContentLoadingPolicy.swift b/Sources/SwiftUIPager/PageConfiguration/ContentLoadingPolicy.swift new file mode 100644 index 0000000..f05c3ac --- /dev/null +++ b/Sources/SwiftUIPager/PageConfiguration/ContentLoadingPolicy.swift @@ -0,0 +1,27 @@ +// +// File.swift +// +// +// Created by Fernando Moya de Rivas on 05/07/2020. +// + +import Foundation + +/// Policy to follow when loading content +public enum ContentLoadingPolicy: Equatable { + + /// Content is loaded on demand by applying a recycling the ratio. + /// + /// - Parameter recyclingRatio: Manages the number of items that should be displayed in the screen. + /// + /// A ratio of `5`, for instance, will load enough items in memory to fill five times the size of `Pager`. + /// - Note: `recyclingRatio` must be greather than `0`. + case lazy(recyclingRatio: UInt) + + /// Choose `eager` to load all items at once + case eager + + /// Default policy, a.k.a, `lazy(recyclingRatio: 5)` + static var `default`: ContentLoadingPolicy = .lazy(recyclingRatio: 5) +} + diff --git a/Sources/SwiftUIPager/PageConfiguration/GesturePriority.swift b/Sources/SwiftUIPager/PageConfiguration/GesturePriority.swift index 45b779a..09b2239 100644 --- a/Sources/SwiftUIPager/PageConfiguration/GesturePriority.swift +++ b/Sources/SwiftUIPager/PageConfiguration/GesturePriority.swift @@ -18,7 +18,10 @@ public enum GesturePriority { case simultaneous /// Refers to `gesture` modifier - case `default` + case normal + + /// Default value, a.k.a, `normal` + static let `default`: GesturePriority = .normal } @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) diff --git a/Sources/SwiftUIPager/Pager+Buildable.swift b/Sources/SwiftUIPager/Pager+Buildable.swift index a05e182..364f21e 100644 --- a/Sources/SwiftUIPager/Pager+Buildable.swift +++ b/Sources/SwiftUIPager/Pager+Buildable.swift @@ -11,6 +11,16 @@ import SwiftUI @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) extension Pager: Buildable { + /// Sets the policy followed to load `Pager` content. + /// + /// - Parameter value: policy to load the content. + /// + /// Choose `lazy` to load pages on demand so that the right amount of memory is used. Choose `eager` if + /// `Pager` won't hold many items or if memory isn't an issue. + public func contentLoadingPolicy(_ value: ContentLoadingPolicy) -> Self { + mutating(keyPath: \.contentLoadingPolicy, value: value) + } + /// Sets `Pager` to loop the items in a never-ending scroll. /// /// - Parameter value: `true` if `Pager` should loop the pages. `false`, otherwise. diff --git a/Sources/SwiftUIPager/Pager+Helper.swift b/Sources/SwiftUIPager/Pager+Helper.swift index e97a089..6c72070 100644 --- a/Sources/SwiftUIPager/Pager+Helper.swift +++ b/Sources/SwiftUIPager/Pager+Helper.swift @@ -11,6 +11,16 @@ import SwiftUI @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) extension Pager { + /// Manages the number of items that should be displayed in the screen. + var recyclingRatio: Int { + switch contentLoadingPolicy { + case .eager: + return numberOfPages + case .lazy(let ratio): + return max(1, Int(ratio)) + } + } + /// Work around to avoid @State keeps wrong value var page: Int { return min(pageIndex, numberOfPages - 1) @@ -106,6 +116,7 @@ extension Pager { /// Maximum number in memory at the same time. Always an even number. var maximumNumberOfPages: Int { + guard contentLoadingPolicy != .eager else { return numberOfPages } guard pageDistance != 0, numberOfPages > 0 else { return 0 } let side = isHorizontal ? size.width : size.height var number = Int((CGFloat(recyclingRatio) * side / pageDistance).rounded(.up)) @@ -139,14 +150,18 @@ extension Pager { /// Lower bound of the data displaed var lowerPageDisplayed: Int { - guard isInifinitePager else { return max(0, page - maximumNumberOfPages / 2) } + guard isInifinitePager else { + return contentLoadingPolicy == .eager ? 0 : max(0, page - maximumNumberOfPages / 2) + } guard numberOfPages > 0 else { return 0 } return ((page - maximumNumberOfPages / 2) + numberOfPages) % numberOfPages } /// Upper bound of the data displaed var upperPageDisplayed: Int { - guard isInifinitePager else { return min(numberOfPages - 1, page + maximumNumberOfPages / 2) } + guard isInifinitePager else { + return contentLoadingPolicy == .eager ? numberOfPages - 1 : min(numberOfPages - 1, page + maximumNumberOfPages / 2) + } guard numberOfPages > 0 else { return 0 } return (Int((Float(maximumNumberOfPages) / 2).rounded(.up)) + page) % numberOfPages } diff --git a/Sources/SwiftUIPager/Pager.swift b/Sources/SwiftUIPager/Pager.swift index d404339..ac9a0a7 100644 --- a/Sources/SwiftUIPager/Pager.swift +++ b/Sources/SwiftUIPager/Pager.swift @@ -42,10 +42,8 @@ public struct Pager: View where PageView: View, Element: /*** Constants ***/ - /// Manages the number of items that should be displayed in the screen. - /// A ratio of 5, for instance, would mean the items held in memory are enough - /// to cover 5 times the size of the pager - let recyclingRatio = 5 + /// Policy to be applied when loading content + var contentLoadingPolicy: ContentLoadingPolicy = .default /// Angle of rotation when should rotate let rotationDegrees: Double = 20 diff --git a/SwiftUIPager.podspec b/SwiftUIPager.podspec index bf56828..883cb34 100644 --- a/SwiftUIPager.podspec +++ b/SwiftUIPager.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "SwiftUIPager" - s.version = "1.7.0" + s.version = "1.8.0-beta.1" s.summary = "Native pager for SwiftUI. Easily to use, easy to customize." s.description = <<-DESC diff --git a/SwiftUIPager.xcodeproj/project.pbxproj b/SwiftUIPager.xcodeproj/project.pbxproj index 9b8f5b7..3e3984e 100644 --- a/SwiftUIPager.xcodeproj/project.pbxproj +++ b/SwiftUIPager.xcodeproj/project.pbxproj @@ -57,6 +57,11 @@ 6BBC3D772488DC0B004194BD /* Pager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BDE442223DE12470022A2F7 /* Pager.swift */; }; 6BBC3D782488DC0E004194BD /* Pager+Buildable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BDE442323DE12470022A2F7 /* Pager+Buildable.swift */; }; 6BBC3D792488DC11004194BD /* Pager+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BDE442823DE12480022A2F7 /* Pager+Helper.swift */; }; + 6BCF138C24B2674E00AADE74 /* ContentLoadingPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BCF138B24B2674800AADE74 /* ContentLoadingPolicy.swift */; }; + 6BCF138D24B2674F00AADE74 /* ContentLoadingPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BCF138B24B2674800AADE74 /* ContentLoadingPolicy.swift */; }; + 6BCF138E24B2674F00AADE74 /* ContentLoadingPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BCF138B24B2674800AADE74 /* ContentLoadingPolicy.swift */; }; + 6BCF138F24B2675000AADE74 /* ContentLoadingPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BCF138B24B2674800AADE74 /* ContentLoadingPolicy.swift */; }; + 6BCF139024B2675000AADE74 /* ContentLoadingPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BCF138B24B2674800AADE74 /* ContentLoadingPolicy.swift */; }; 6BDE441C23DE10C10022A2F7 /* SwiftUIPager.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BDE441A23DE10C10022A2F7 /* SwiftUIPager.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6BDE442923DE12480022A2F7 /* Pager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BDE442223DE12470022A2F7 /* Pager.swift */; }; 6BDE442A23DE12480022A2F7 /* Pager+Buildable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BDE442323DE12470022A2F7 /* Pager+Buildable.swift */; }; @@ -101,6 +106,7 @@ 6B2C3069248D747700E528F9 /* Info-Catalyst.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-Catalyst.plist"; sourceTree = ""; }; 6B3F9C052488E6FE00AF5E74 /* Info-tvOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-tvOS.plist"; sourceTree = ""; }; 6BBC3D6B2488DBE8004194BD /* SwiftUIPager.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftUIPager.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6BCF138B24B2674800AADE74 /* ContentLoadingPolicy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ContentLoadingPolicy.swift; path = Sources/SwiftUIPager/PageConfiguration/ContentLoadingPolicy.swift; sourceTree = SOURCE_ROOT; }; 6BDE441723DE10C10022A2F7 /* SwiftUIPager.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftUIPager.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6BDE441A23DE10C10022A2F7 /* SwiftUIPager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftUIPager.h; sourceTree = ""; }; 6BDE441B23DE10C10022A2F7 /* Info-iOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-iOS.plist"; sourceTree = ""; }; @@ -226,6 +232,7 @@ 6BEA730D24ACF7FF007EA8DC /* PageConfiguration */ = { isa = PBXGroup; children = ( + 6BCF138B24B2674800AADE74 /* ContentLoadingPolicy.swift */, 6BEA731A24ACF9B0007EA8DC /* GesturePriority.swift */, 6BEA731924ACF9B0007EA8DC /* PositionAlignment.swift */, 6BEA731724ACF9AF007EA8DC /* SwipeDirection.swift */, @@ -497,6 +504,7 @@ 6BEA732C24ACFA08007EA8DC /* SwipeInteractionArea.swift in Sources */, 172F4D5623DF830600FD2F15 /* Pager+Buildable.swift in Sources */, 172F4D5723DF830600FD2F15 /* View+Helper.swift in Sources */, + 6BCF138E24B2674F00AADE74 /* ContentLoadingPolicy.swift in Sources */, 172F4D5823DF830600FD2F15 /* Buildable.swift in Sources */, 172F4D5923DF830600FD2F15 /* Pager.swift in Sources */, 6BEA732724ACFA03007EA8DC /* SwipeDirection.swift in Sources */, @@ -514,6 +522,7 @@ 6BEA732D24ACFA08007EA8DC /* SwipeInteractionArea.swift in Sources */, 172F4D8423DF8B4400FD2F15 /* Pager.swift in Sources */, 172F4D8223DF8B3F00FD2F15 /* SizeViewModifier.swift in Sources */, + 6BCF138F24B2675000AADE74 /* ContentLoadingPolicy.swift in Sources */, 172F4D8023DF8B3800FD2F15 /* View+Helper.swift in Sources */, 172F4D8523DF8B4800FD2F15 /* Pager+Buildable.swift in Sources */, 6BEA732824ACFA04007EA8DC /* SwipeDirection.swift in Sources */, @@ -531,6 +540,7 @@ 6BEA732B24ACFA07007EA8DC /* SwipeInteractionArea.swift in Sources */, 6B2C305A248D740800E528F9 /* Pager+Buildable.swift in Sources */, 6B2C305B248D740800E528F9 /* View+Helper.swift in Sources */, + 6BCF138D24B2674F00AADE74 /* ContentLoadingPolicy.swift in Sources */, 6B2C305C248D740800E528F9 /* Buildable.swift in Sources */, 6B2C305E248D740800E528F9 /* Pager.swift in Sources */, 6BEA732624ACFA03007EA8DC /* SwipeDirection.swift in Sources */, @@ -548,6 +558,7 @@ 6BEA732E24ACFA09007EA8DC /* SwipeInteractionArea.swift in Sources */, 6BBC3D772488DC0B004194BD /* Pager.swift in Sources */, 6BBC3D782488DC0E004194BD /* Pager+Buildable.swift in Sources */, + 6BCF139024B2675000AADE74 /* ContentLoadingPolicy.swift in Sources */, 6BBC3D742488DC01004194BD /* View+Helper.swift in Sources */, 6BBC3D762488DC07004194BD /* SizeViewModifier.swift in Sources */, 6BEA732924ACFA04007EA8DC /* SwipeDirection.swift in Sources */, @@ -565,6 +576,7 @@ 6BEA732A24ACFA07007EA8DC /* SwipeInteractionArea.swift in Sources */, 6BDE442A23DE12480022A2F7 /* Pager+Buildable.swift in Sources */, 6BDE442B23DE12480022A2F7 /* View+Helper.swift in Sources */, + 6BCF138C24B2674E00AADE74 /* ContentLoadingPolicy.swift in Sources */, 6BDE442C23DE12480022A2F7 /* Buildable.swift in Sources */, 6BDE442923DE12480022A2F7 /* Pager.swift in Sources */, 6BEA732524ACFA02007EA8DC /* SwipeDirection.swift in Sources */, diff --git a/Tests/SwiftUIPagerTests/Pager+Buildable_Tests.swift b/Tests/SwiftUIPagerTests/Pager+Buildable_Tests.swift index 50a36ee..cf658f0 100644 --- a/Tests/SwiftUIPagerTests/Pager+Buildable_Tests.swift +++ b/Tests/SwiftUIPagerTests/Pager+Buildable_Tests.swift @@ -27,6 +27,25 @@ final class Pager_Buildable_Tests: XCTestCase { XCTAssertEqual(pager.swipeInteractionArea, .page) XCTAssertEqual(pager.minimumDistance, 15) XCTAssertEqual(pager.gesturePriority, .default) + XCTAssertEqual(pager.contentLoadingPolicy, .default) + } + + func test_GivenPager_WhenContentLoadingPolicyLazy0_ThenRecyclingRatioIs1() { + var pager = givenPager + pager = pager.contentLoadingPolicy(.lazy(recyclingRatio: 0)) + XCTAssertEqual(pager.recyclingRatio, 1) + } + + func test_GivenPager_WhenContentLoadingPolicyLazy10_ThenRecyclingRatioIs10() { + var pager = givenPager + pager = pager.contentLoadingPolicy(.lazy(recyclingRatio: 10)) + XCTAssertEqual(pager.recyclingRatio, 10) + } + + func test_GivenPager_WhenContentLoadingPolicyEager_ThenRecyclingRatioIsIntMax() { + var pager = givenPager + pager = pager.contentLoadingPolicy(.eager) + XCTAssertEqual(pager.recyclingRatio, pager.numberOfPages) } func test_GivenPager_WhenPagingPrioritySimultaneous_ThenSimultaneous() { @@ -312,6 +331,9 @@ final class Pager_Buildable_Tests: XCTestCase { ("test_GivenPager_WhenLoopPages_ThenIsInfinitePagerTrue", test_GivenPager_WhenLoopPages_ThenIsInfinitePagerTrue), ("test_GivenPager_WhenPreferredItemSize_ThenNotNil", test_GivenPager_WhenPreferredItemSize_ThenNotNil), ("test_GivenPager_WhenPagingPrioritySimultaneous_ThenSimultaneous", test_GivenPager_WhenPagingPrioritySimultaneous_ThenSimultaneous), - ("test_GivenPager_WhenPagingPriorityHigh_ThenHigh", test_GivenPager_WhenPagingPriorityHigh_ThenHigh) + ("test_GivenPager_WhenPagingPriorityHigh_ThenHigh", test_GivenPager_WhenPagingPriorityHigh_ThenHigh), + ("test_GivenPager_WhenContentLoadingPolicyLazy0_ThenRecyclingRatioIs1", test_GivenPager_WhenContentLoadingPolicyLazy0_ThenRecyclingRatioIs1), + ("test_GivenPager_WhenContentLoadingPolicyLazy10_ThenRecyclingRatioIs10", test_GivenPager_WhenContentLoadingPolicyLazy10_ThenRecyclingRatioIs10), + ("test_GivenPager_WhenContentLoadingPolicyEager_ThenRecyclingRatioIsIntMax", test_GivenPager_WhenContentLoadingPolicyEager_ThenRecyclingRatioIsIntMax) ] }