From 2e96646d44b38590e476125575f826943249edfc Mon Sep 17 00:00:00 2001 From: Muukii Date: Tue, 26 Nov 2024 15:48:41 +0900 Subject: [PATCH 1/7] WIP --- .../Development.xcodeproj/project.pbxproj | 15 +- .../xcshareddata/swiftpm/Package.resolved | 11 +- Development/Development/BookScrollView.swift | 37 +++++ Development/Development/ContentView.swift | 4 + Package.swift | 16 +- .../CollectionView/TrackingScrollView.swift | 22 +++ Sources/ScrollTracking/ScrollTracking.swift | 140 ++++++++++++++++++ 7 files changed, 240 insertions(+), 5 deletions(-) create mode 100644 Development/Development/BookScrollView.swift create mode 100644 Sources/CollectionView/TrackingScrollView.swift create mode 100644 Sources/ScrollTracking/ScrollTracking.swift diff --git a/Development/Development.xcodeproj/project.pbxproj b/Development/Development.xcodeproj/project.pbxproj index 96c808d..8072d4e 100644 --- a/Development/Development.xcodeproj/project.pbxproj +++ b/Development/Development.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 4B08E0AB2CF5805500B05999 /* BookScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B08E0AA2CF5805200B05999 /* BookScrollView.swift */; }; + 4B08E0AD2CF5947100B05999 /* ScrollTracking in Frameworks */ = {isa = PBXBuildFile; productRef = 4B08E0AC2CF5947100B05999 /* ScrollTracking */; }; 4B26A67B2A33239500B75FB4 /* DevelopmentApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B26A67A2A33239500B75FB4 /* DevelopmentApp.swift */; }; 4B26A67D2A33239500B75FB4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B26A67C2A33239500B75FB4 /* ContentView.swift */; }; 4B26A67F2A33239600B75FB4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4B26A67E2A33239600B75FB4 /* Assets.xcassets */; }; @@ -39,6 +41,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 4B08E0AA2CF5805200B05999 /* BookScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookScrollView.swift; sourceTree = ""; }; 4B26A6772A33239500B75FB4 /* Development.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Development.app; sourceTree = BUILT_PRODUCTS_DIR; }; 4B26A67A2A33239500B75FB4 /* DevelopmentApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevelopmentApp.swift; sourceTree = ""; }; 4B26A67C2A33239500B75FB4 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; @@ -59,6 +62,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 4B08E0AD2CF5947100B05999 /* ScrollTracking in Frameworks */, 4BC34FAF2CDB1B9200D22811 /* CollectionView in Frameworks */, 4BEAFA4E2A3CE48800478C59 /* AsyncMultiplexImage-Nuke in Frameworks */, 4BEAFA4C2A3CE48800478C59 /* AsyncMultiplexImage in Frameworks */, @@ -91,6 +95,7 @@ 4B26A6792A33239500B75FB4 /* Development */ = { isa = PBXGroup; children = ( + 4B08E0AA2CF5805200B05999 /* BookScrollView.swift */, 4BC34FB02CDB1C0500D22811 /* BookCollectionView.swift */, 4B26A67A2A33239500B75FB4 /* DevelopmentApp.swift */, 4B26A67C2A33239500B75FB4 /* ContentView.swift */, @@ -144,6 +149,7 @@ 4BEAFA4B2A3CE48800478C59 /* AsyncMultiplexImage */, 4BEAFA4D2A3CE48800478C59 /* AsyncMultiplexImage-Nuke */, 4BC34FAE2CDB1B9200D22811 /* CollectionView */, + 4B08E0AC2CF5947100B05999 /* ScrollTracking */, ); productName = Development; productReference = 4B26A6772A33239500B75FB4 /* Development.app */; @@ -209,6 +215,7 @@ 4B3722682A33C701005FF24A /* BookUIKitBasedCompositional.swift in Sources */, 4BD04C172B2C13BB00FE41D9 /* Logger.swift in Sources */, 4BD04C192B2C15E100FE41D9 /* Color.swift in Sources */, + 4B08E0AB2CF5805500B05999 /* BookScrollView.swift in Sources */, 4BC34FB12CDB1C0C00D22811 /* BookCollectionView.swift in Sources */, 4BD04C152B2C05C600FE41D9 /* BookPlainCollectionView.swift in Sources */, 4B910EBC2A77A2F50079D26D /* BookUIKitBasedFlow.swift in Sources */, @@ -348,7 +355,7 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 16.6; + IPHONEOS_DEPLOYMENT_TARGET = 16; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -378,7 +385,7 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 16.6; + IPHONEOS_DEPLOYMENT_TARGET = 16; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -435,6 +442,10 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 4B08E0AC2CF5947100B05999 /* ScrollTracking */ = { + isa = XCSwiftPackageProductDependency; + productName = ScrollTracking; + }; 4B9981D72A34F9B500840751 /* CompositionKit */ = { isa = XCSwiftPackageProductDependency; package = 4B9981D62A34F9B500840751 /* XCRemoteSwiftPackageReference "CompositionKit" */; diff --git a/Development/Development.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Development/Development.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index ec38286..28342f6 100644 --- a/Development/Development.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Development/Development.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "e89878bf140720e29e318b93f44c5d0df6cceb734010ef6111491735cb630699", + "originHash" : "0d1e16d5e02f980d50ec8755a01765cbd1d35cba642583d30c56718e0054a945", "pins" : [ { "identity" : "compositionkit", @@ -64,6 +64,15 @@ "version" : "1.0.0" } }, + { + "identity" : "swiftui-introspect", + "kind" : "remoteSourceControl", + "location" : "https://github.com/siteline/swiftui-introspect.git", + "state" : { + "revision" : "807f73ce09a9b9723f12385e592b4e0aaebd3336", + "version" : "1.3.0" + } + }, { "identity" : "swiftui-support", "kind" : "remoteSourceControl", diff --git a/Development/Development/BookScrollView.swift b/Development/Development/BookScrollView.swift new file mode 100644 index 0000000..071b79f --- /dev/null +++ b/Development/Development/BookScrollView.swift @@ -0,0 +1,37 @@ +// +// BookScrollView.swift +// Development +// +// Created by Muukii on 2024/11/26. +// + +import SwiftUI +import ScrollTracking + + +struct OnAdditionalLoading_Previews: View, PreviewProvider { + + @State var items: [Int] = (0..<100).map { $0 } + + var body: some View { + ScrollView { + LazyVStack { + ForEach(items, id: \.self) { index in + Text("Item \(index)") + } + } + } + .onAdditionalLoading { + print("👨🏻 load") + try? await Task.sleep(for: .seconds(1)) + items.append(contentsOf: (items.count..<(items.count + 100)).map { $0 }) + print("appended") + } + } + + static var previews: some View { + Self() + } +} + + diff --git a/Development/Development/ContentView.swift b/Development/Development/ContentView.swift index 8736714..938c9c3 100644 --- a/Development/Development/ContentView.swift +++ b/Development/Development/ContentView.swift @@ -32,6 +32,10 @@ struct ContentView: View { NavigationLink("CollectionView") { BookCollectionViewSingleSection() } + + NavigationLink("ScrollView") { + OnAdditionalLoading_Previews() + } } } } diff --git a/Package.swift b/Package.swift index f89273e..fa611c8 100644 --- a/Package.swift +++ b/Package.swift @@ -15,10 +15,15 @@ let package = Package( .library( name: "CollectionView", targets: ["CollectionView"] - ) + ), + .library( + name: "ScrollTracking", + targets: ["ScrollTracking"] + ), ], dependencies: [ .package(url: "https://github.com/FluidGroup/swift-indexed-collection", from: "0.2.1"), + .package(url: "https://github.com/siteline/swiftui-introspect", from: "1.3.0"), ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. @@ -34,9 +39,16 @@ let package = Package( .product(name: "IndexedCollection", package: "swift-indexed-collection"), ] ), + .target( + name: "ScrollTracking", + dependencies: [ + .product(name: "SwiftUIIntrospect", package: "swiftui-introspect"), + ] + ), .testTarget( name: "DynamicListTests", dependencies: ["DynamicList"] ), - ] + ], + swiftLanguageModes: [.v6] ) diff --git a/Sources/CollectionView/TrackingScrollView.swift b/Sources/CollectionView/TrackingScrollView.swift new file mode 100644 index 0000000..b40f1a0 --- /dev/null +++ b/Sources/CollectionView/TrackingScrollView.swift @@ -0,0 +1,22 @@ +// +// TrackingScrollView.swift +// swift-dynamic-list +// +// Created by Muukii on 2024/11/26. +// + +import SwiftUI + +public struct TrackingScrollView: View { + + public var body: some View { + ScrollView { + LazyVStack { + ForEach(0..<100) { index in + Text("Item \(index)") + } + } + } + } +} + diff --git a/Sources/ScrollTracking/ScrollTracking.swift b/Sources/ScrollTracking/ScrollTracking.swift new file mode 100644 index 0000000..ce78a11 --- /dev/null +++ b/Sources/ScrollTracking/ScrollTracking.swift @@ -0,0 +1,140 @@ +import Combine +import SwiftUI +import SwiftUIIntrospect + +extension ScrollView { + + @ViewBuilder + public func onAdditionalLoading( + isEnabled: Bool = true, + leadingScreens: CGFloat = 2, + _ handler: @MainActor @escaping () async -> Void + ) -> some View { + + modifier( + _Modifier( + isEnabled: isEnabled, + leadingScreens: leadingScreens, + handler: handler + ) + ) + + } + +} + +private final class Controller: ObservableObject { + var scrollViewSubscription: AnyCancellable? + var currentLoadingTask: Task? +} + +private struct _Modifier: ViewModifier { + + @StateObject var controller: Controller = .init() + + private let isEnabled: Bool + private let leadingScreens: CGFloat + private let handler: @MainActor () async -> Void + + nonisolated init( + isEnabled: Bool, + leadingScreens: CGFloat, + handler: @MainActor @escaping () async -> Void + ) { + self.isEnabled = isEnabled + self.leadingScreens = leadingScreens + self.handler = handler + } + + func body(content: Content) -> some View { + + if #available(iOS 18, *) { + content.onScrollGeometryChange(for: Bool.self) { geometry in + + return calculate( + contentOffsetY: geometry.contentOffset.y, + boundsHeight: geometry.containerSize.height, + contentSizeHeight: geometry.contentSize.height, + leadingScreens: leadingScreens + ) + + } action: { oldValue, newValue in + + if newValue { + MainActor.assumeIsolated { + trigger() + } + } + + } + } else { + + content.introspect(.scrollView, on: .iOS(.v15, .v16, .v17)) { scrollView in + + controller.scrollViewSubscription?.cancel() + + controller.scrollViewSubscription = scrollView.publisher(for: \.contentOffset).sink { + [weak scrollView] offset in + + guard let scrollView else { + return + } + + let triggers = calculate( + contentOffsetY: offset.y, + boundsHeight: scrollView.bounds.height, + contentSizeHeight: scrollView.contentSize.height, + leadingScreens: leadingScreens + ) + + if triggers { + trigger() + } + + } + } + } + } + + private func trigger() { + + guard isEnabled else { + return + } + + guard controller.currentLoadingTask == nil else { + return + } + + let task = Task { @MainActor [weak controller] in + await handler() + controller?.currentLoadingTask = nil + } + + controller.currentLoadingTask = task + } + +} + +private func calculate( + contentOffsetY: CGFloat, + boundsHeight: CGFloat, + contentSizeHeight: CGFloat, + leadingScreens: CGFloat +) -> Bool { + + guard leadingScreens > 0 || boundsHeight != .zero else { + return false + } + + let viewLength = boundsHeight + let offset = contentOffsetY + let contentLength = contentSizeHeight + + let hasSmallContent = (offset == 0.0) && (contentLength < viewLength) + + let triggerDistance = viewLength * leadingScreens + let remainingDistance = contentLength - viewLength - offset + + return (hasSmallContent || remainingDistance <= triggerDistance) +} From 1e32fdbc9bf3845b06ef299f117c0ad18b7da9c1 Mon Sep 17 00:00:00 2001 From: Muukii Date: Fri, 6 Dec 2024 15:37:55 +0900 Subject: [PATCH 2/7] Update --- Development/Development/BookScrollView.swift | 9 +++- Sources/ScrollTracking/ScrollTracking.swift | 45 ++++++++++++++------ 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/Development/Development/BookScrollView.swift b/Development/Development/BookScrollView.swift index 071b79f..3f4cffa 100644 --- a/Development/Development/BookScrollView.swift +++ b/Development/Development/BookScrollView.swift @@ -12,16 +12,21 @@ import ScrollTracking struct OnAdditionalLoading_Previews: View, PreviewProvider { @State var items: [Int] = (0..<100).map { $0 } + @State var isLoading: Bool = false var body: some View { ScrollView { - LazyVStack { + LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))]) { ForEach(items, id: \.self) { index in Text("Item \(index)") + .font(.title) } } + if isLoading { + Text(isLoading ? "Loading..." : "End") + } } - .onAdditionalLoading { + .onAdditionalLoading(isLoading: $isLoading) { print("👨🏻 load") try? await Task.sleep(for: .seconds(1)) items.append(contentsOf: (items.count..<(items.count + 100)).map { $0 }) diff --git a/Sources/ScrollTracking/ScrollTracking.swift b/Sources/ScrollTracking/ScrollTracking.swift index ce78a11..3534de9 100644 --- a/Sources/ScrollTracking/ScrollTracking.swift +++ b/Sources/ScrollTracking/ScrollTracking.swift @@ -1,6 +1,7 @@ import Combine import SwiftUI import SwiftUIIntrospect +import os.lock extension ScrollView { @@ -8,13 +9,15 @@ extension ScrollView { public func onAdditionalLoading( isEnabled: Bool = true, leadingScreens: CGFloat = 2, + isLoading: Binding, _ handler: @MainActor @escaping () async -> Void ) -> some View { modifier( _Modifier( isEnabled: isEnabled, - leadingScreens: leadingScreens, + leadingScreens: leadingScreens, + isLoading: isLoading, handler: handler ) ) @@ -25,7 +28,7 @@ extension ScrollView { private final class Controller: ObservableObject { var scrollViewSubscription: AnyCancellable? - var currentLoadingTask: Task? + let currentLoadingTask: OSAllocatedUnfairLock?> = .init(initialState: nil) } private struct _Modifier: ViewModifier { @@ -34,14 +37,17 @@ private struct _Modifier: ViewModifier { private let isEnabled: Bool private let leadingScreens: CGFloat + private let isLoading: Binding private let handler: @MainActor () async -> Void nonisolated init( isEnabled: Bool, leadingScreens: CGFloat, + isLoading: Binding, handler: @MainActor @escaping () async -> Void ) { self.isEnabled = isEnabled + self.isLoading = isLoading self.leadingScreens = leadingScreens self.handler = handler } @@ -101,17 +107,32 @@ private struct _Modifier: ViewModifier { guard isEnabled else { return } + + let taskBox = controller.currentLoadingTask - guard controller.currentLoadingTask == nil else { - return - } - - let task = Task { @MainActor [weak controller] in - await handler() - controller?.currentLoadingTask = nil - } - - controller.currentLoadingTask = task + taskBox.withLockUnchecked { currentTask in + + guard currentTask == nil else { + return + } + + isLoading.wrappedValue = true + + let task = Task { @MainActor in + await withTaskCancellationHandler { + await handler() + isLoading.wrappedValue = false + taskBox.withLock { $0 = nil } + } onCancel: { + isLoading.wrappedValue = false + taskBox.withLock { $0 = nil } + } + } + + currentTask = task + + } + } } From 44abb1ad0011756c0fd65111dac66077adb4504a Mon Sep 17 00:00:00 2001 From: Muukii Date: Sat, 14 Dec 2024 20:12:51 +0900 Subject: [PATCH 3/7] Update --- .../xcshareddata/swiftpm/Package.resolved | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Development/Development.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Development/Development.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 28342f6..683ff7d 100644 --- a/Development/Development.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Development/Development.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "0d1e16d5e02f980d50ec8755a01765cbd1d35cba642583d30c56718e0054a945", + "originHash" : "5262eaf10f856dee60593c62bee81f6931c0200844ea3211d6cd5f1c6b6fede5", "pins" : [ { "identity" : "compositionkit", @@ -46,6 +46,15 @@ "version" : "0.2.1" } }, + { + "identity" : "swift-with-prerender", + "kind" : "remoteSourceControl", + "location" : "https://github.com/FluidGroup/swift-with-prerender", + "state" : { + "revision" : "83ea5d0f5a9fd0082c61e090f4b656c7b58ee0be", + "version" : "1.0.0" + } + }, { "identity" : "swiftui-async-multiplex-image", "kind" : "remoteSourceControl", From 3c14fb62331fa52b461d52db2860047861cb208f Mon Sep 17 00:00:00 2001 From: Muukii Date: Sat, 14 Dec 2024 20:12:59 +0900 Subject: [PATCH 4/7] Update --- .../xcschemes/CollectionView.xcscheme | 67 +++++++++++ .../xcschemes/DynamicList.xcscheme | 67 +++++++++++ .../xcschemes/ScrollTracking.xcscheme | 67 +++++++++++ .../swift-dynamic-list-Package.xcscheme | 107 ++++++++++++++++++ Package.resolved | 11 +- Package.swift | 2 + Sources/ScrollTracking/ScrollTracking.swift | 47 ++++---- 7 files changed, 346 insertions(+), 22 deletions(-) create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/CollectionView.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/DynamicList.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/ScrollTracking.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/swift-dynamic-list-Package.xcscheme diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/CollectionView.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/CollectionView.xcscheme new file mode 100644 index 0000000..8c46363 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/CollectionView.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/DynamicList.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/DynamicList.xcscheme new file mode 100644 index 0000000..d89bf8f --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/DynamicList.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/ScrollTracking.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/ScrollTracking.xcscheme new file mode 100644 index 0000000..a675e5e --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/ScrollTracking.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/swift-dynamic-list-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/swift-dynamic-list-Package.xcscheme new file mode 100644 index 0000000..74109fa --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/swift-dynamic-list-Package.xcscheme @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Package.resolved b/Package.resolved index 3bb4915..100e7e8 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "e30de3866a1e223d0be5f8ba41ba45f2585ba57d726d72464171f918ff5f8d48", + "originHash" : "6f507385589ccb1437137d0b13521704ef870cd7b54f66ab330ab4ffe7b0fd8b", "pins" : [ { "identity" : "swift-indexed-collection", @@ -9,6 +9,15 @@ "revision" : "9b17bf06eae73fee93dae9a0fa6de2e33900d9c5", "version" : "0.2.1" } + }, + { + "identity" : "swiftui-introspect", + "kind" : "remoteSourceControl", + "location" : "https://github.com/siteline/swiftui-introspect", + "state" : { + "revision" : "807f73ce09a9b9723f12385e592b4e0aaebd3336", + "version" : "1.3.0" + } } ], "version" : 3 diff --git a/Package.swift b/Package.swift index fa611c8..712b17c 100644 --- a/Package.swift +++ b/Package.swift @@ -24,6 +24,7 @@ let package = Package( dependencies: [ .package(url: "https://github.com/FluidGroup/swift-indexed-collection", from: "0.2.1"), .package(url: "https://github.com/siteline/swiftui-introspect", from: "1.3.0"), + .package(url: "https://github.com/FluidGroup/swift-with-prerender", from: "1.0.0") ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. @@ -43,6 +44,7 @@ let package = Package( name: "ScrollTracking", dependencies: [ .product(name: "SwiftUIIntrospect", package: "swiftui-introspect"), + .product(name: "WithPrerender", package: "swift-with-prerender"), ] ), .testTarget( diff --git a/Sources/ScrollTracking/ScrollTracking.swift b/Sources/ScrollTracking/ScrollTracking.swift index 3534de9..ef4830c 100644 --- a/Sources/ScrollTracking/ScrollTracking.swift +++ b/Sources/ScrollTracking/ScrollTracking.swift @@ -2,6 +2,7 @@ import Combine import SwiftUI import SwiftUIIntrospect import os.lock +import WithPrerender extension ScrollView { @@ -16,7 +17,7 @@ extension ScrollView { modifier( _Modifier( isEnabled: isEnabled, - leadingScreens: leadingScreens, + leadingScreens: leadingScreens, isLoading: isLoading, handler: handler ) @@ -55,18 +56,20 @@ private struct _Modifier: ViewModifier { func body(content: Content) -> some View { if #available(iOS 18, *) { - content.onScrollGeometryChange(for: Bool.self) { geometry in + content.onScrollGeometryChange(for: ScrollGeometry.self) { geometry in - return calculate( + return geometry + + } action: { _, geometry in + + let triggers = calculate( contentOffsetY: geometry.contentOffset.y, boundsHeight: geometry.containerSize.height, contentSizeHeight: geometry.contentSize.height, leadingScreens: leadingScreens ) - } action: { oldValue, newValue in - - if newValue { + if triggers { MainActor.assumeIsolated { trigger() } @@ -94,7 +97,7 @@ private struct _Modifier: ViewModifier { ) if triggers { - trigger() + trigger() } } @@ -107,32 +110,34 @@ private struct _Modifier: ViewModifier { guard isEnabled else { return } - - let taskBox = controller.currentLoadingTask + let taskBox = controller.currentLoadingTask + taskBox.withLockUnchecked { currentTask in - + guard currentTask == nil else { - return + return } - - isLoading.wrappedValue = true - + + withPrerender { + isLoading.wrappedValue = true + } + let task = Task { @MainActor in - await withTaskCancellationHandler { + await withTaskCancellationHandler { await handler() isLoading.wrappedValue = false taskBox.withLock { $0 = nil } - } onCancel: { + } onCancel: { isLoading.wrappedValue = false taskBox.withLock { $0 = nil } - } + } } - + currentTask = task - - } - + + } + } } From e7562daa1a95290fc810a3c97e62170007a05c2b Mon Sep 17 00:00:00 2001 From: Muukii Date: Sat, 14 Dec 2024 20:14:59 +0900 Subject: [PATCH 5/7] Update --- Package.resolved | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Package.resolved b/Package.resolved index 100e7e8..60e386b 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "6f507385589ccb1437137d0b13521704ef870cd7b54f66ab330ab4ffe7b0fd8b", + "originHash" : "b7bce46448d0e06c904fcba0f0481bdb3de7ca4595549fef018391002bba99b9", "pins" : [ { "identity" : "swift-indexed-collection", @@ -10,6 +10,15 @@ "version" : "0.2.1" } }, + { + "identity" : "swift-with-prerender", + "kind" : "remoteSourceControl", + "location" : "https://github.com/FluidGroup/swift-with-prerender", + "state" : { + "revision" : "83ea5d0f5a9fd0082c61e090f4b656c7b58ee0be", + "version" : "1.0.0" + } + }, { "identity" : "swiftui-introspect", "kind" : "remoteSourceControl", From b37dc94322ed9a6eeb9cee7b22e9b3a0753a6ce1 Mon Sep 17 00:00:00 2001 From: Muukii Date: Sat, 14 Dec 2024 20:15:54 +0900 Subject: [PATCH 6/7] Update --- .../xcschemes/CollectionView.xcscheme | 67 ----------- .../xcschemes/DynamicList.xcscheme | 67 ----------- .../xcschemes/ScrollTracking.xcscheme | 67 ----------- .../swift-dynamic-list-Package.xcscheme | 107 ------------------ 4 files changed, 308 deletions(-) delete mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/CollectionView.xcscheme delete mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/DynamicList.xcscheme delete mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/ScrollTracking.xcscheme delete mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/swift-dynamic-list-Package.xcscheme diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/CollectionView.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/CollectionView.xcscheme deleted file mode 100644 index 8c46363..0000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/CollectionView.xcscheme +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/DynamicList.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/DynamicList.xcscheme deleted file mode 100644 index d89bf8f..0000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/DynamicList.xcscheme +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/ScrollTracking.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/ScrollTracking.xcscheme deleted file mode 100644 index a675e5e..0000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/ScrollTracking.xcscheme +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/swift-dynamic-list-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/swift-dynamic-list-Package.xcscheme deleted file mode 100644 index 74109fa..0000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/swift-dynamic-list-Package.xcscheme +++ /dev/null @@ -1,107 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 84d065fc6c4ebcd1cc5c0b365df0e5de0c477c85 Mon Sep 17 00:00:00 2001 From: Muukii Date: Sat, 14 Dec 2024 20:16:35 +0900 Subject: [PATCH 7/7] Update --- .../CollectionView/TrackingScrollView.swift | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 Sources/CollectionView/TrackingScrollView.swift diff --git a/Sources/CollectionView/TrackingScrollView.swift b/Sources/CollectionView/TrackingScrollView.swift deleted file mode 100644 index b40f1a0..0000000 --- a/Sources/CollectionView/TrackingScrollView.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// TrackingScrollView.swift -// swift-dynamic-list -// -// Created by Muukii on 2024/11/26. -// - -import SwiftUI - -public struct TrackingScrollView: View { - - public var body: some View { - ScrollView { - LazyVStack { - ForEach(0..<100) { index in - Text("Item \(index)") - } - } - } - } -} -