├── images
├── 1.gif
└── 2.gif
├── Examples
├── Assets.xcassets
│ ├── Contents.json
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── photo.imageset
│ │ └── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── RefresherTest.xcodeproj
│ └── project.xcworkspace
│ │ └── contents.xcworkspacedata
├── ScrollViewLoaderTest.xcodeproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── swiftpm
│ │ │ └── Package.resolved
│ └── project.pbxproj
└── ContentView.swift
├── .gitignore
├── Package.resolved
├── LICENSE
├── Package.swift
├── README.md
└── Sources
└── ScrollViewLoader
└── ScrollViewLoader.swift
/images/1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gh123man/ScrollViewLoader/HEAD/images/1.gif
--------------------------------------------------------------------------------
/images/2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gh123man/ScrollViewLoader/HEAD/images/2.gif
--------------------------------------------------------------------------------
/Examples/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/Examples/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Examples/RefresherTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Examples/ScrollViewLoaderTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Examples/ScrollViewLoaderTest.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "swiftui-introspect",
6 | "repositoryURL": "https://github.com/siteline/SwiftUI-Introspect.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "c2b0625d0ef385994e4c6bc36116c0e7bfa6dafa",
10 | "version": "1.4.0-beta.2"
11 | }
12 | }
13 | ]
14 | },
15 | "version": 1
16 | }
17 |
--------------------------------------------------------------------------------
/Examples/Assets.xcassets/photo.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "799D7BF6-CBD8-4409-BE54-863D25D4FC45.jpeg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Examples/ScrollViewLoaderTest.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "swiftui-introspect",
6 | "repositoryURL": "https://github.com/siteline/SwiftUI-Introspect.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "712db7f70540eb0d09d72a7a34c6fa6092c9a142",
10 | "version": "26.0.0-rc.1"
11 | }
12 | }
13 | ]
14 | },
15 | "version": 1
16 | }
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Brian Floersch
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.5
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "ScrollViewLoader",
8 | platforms: [
9 | .iOS(.v14)
10 | ],
11 | products: [
12 | // Products define the executables and libraries a package produces, and make them visible to other packages.
13 | .library(
14 | name: "ScrollViewLoader",
15 | targets: ["ScrollViewLoader"]),
16 | ],
17 | dependencies: [
18 | // Dependencies declare other packages that this package depends on.
19 | .package(name: "SwiftUIIntrospect", url: "https://github.com/siteline/SwiftUI-Introspect.git", from: "26.0.0-rc.1"),
20 | ],
21 | targets: [
22 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
23 | // Targets can depend on other targets in this package, and on products in packages this package depends on.
24 | .target(
25 | name: "ScrollViewLoader",
26 | dependencies: ["SwiftUIIntrospect"]),
27 | ]
28 | )
29 |
--------------------------------------------------------------------------------
/Examples/ContentView.swift:
--------------------------------------------------------------------------------
1 |
2 | import SwiftUI
3 | import ScrollViewLoader
4 |
5 | @main
6 | struct ScrollViewLoaderTestApp: App {
7 | var body: some Scene {
8 | WindowGroup {
9 | ContentView()
10 | }
11 | }
12 | }
13 |
14 | struct ContentView: View {
15 |
16 |
17 | var body: some View {
18 | NavigationView {
19 | NavigationLink(destination: DetailsSearch()) {
20 | Text("Go to details")
21 | .padding()
22 | }
23 | }
24 |
25 |
26 | }
27 | }
28 |
29 | struct DetailsSearch: View {
30 | @State var data: [Int] = Array(0..<1)
31 |
32 | var body: some View {
33 | ScrollView {
34 | LazyVStack {
35 | ForEach(data, id: \.self) { i in
36 | Text("\(i)")
37 | .font(.title)
38 | .frame(maxWidth: .infinity)
39 | }
40 | ProgressView()
41 | .scaleEffect(2)
42 | }
43 | }
44 | // .shouldLoadMore { done in
45 | // DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(200)) {
46 | // data.append(data.last! + 1)
47 | // print("foo")
48 | // done()
49 | // }
50 | // }
51 | .shouldLoadMore {
52 | await Task.sleep(seconds: 0.1)
53 | // data.append(contentsOf: (data.last! + 1)...data.last! + 100)
54 | data.append(data.last! + 1)
55 | }
56 | }
57 | }
58 |
59 |
60 | extension Task where Success == Never, Failure == Never {
61 | static func sleep(seconds: Double) async {
62 | let duration = UInt64(seconds * 1_000_000_000)
63 | try! await Task.sleep(nanoseconds: duration)
64 | }
65 | }
66 |
67 |
68 | #Preview {
69 | ContentView()
70 | }
71 |
--------------------------------------------------------------------------------
/Examples/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | },
93 | {
94 | "idiom" : "mac",
95 | "scale" : "1x",
96 | "size" : "16x16"
97 | },
98 | {
99 | "idiom" : "mac",
100 | "scale" : "2x",
101 | "size" : "16x16"
102 | },
103 | {
104 | "idiom" : "mac",
105 | "scale" : "1x",
106 | "size" : "32x32"
107 | },
108 | {
109 | "idiom" : "mac",
110 | "scale" : "2x",
111 | "size" : "32x32"
112 | },
113 | {
114 | "idiom" : "mac",
115 | "scale" : "1x",
116 | "size" : "128x128"
117 | },
118 | {
119 | "idiom" : "mac",
120 | "scale" : "2x",
121 | "size" : "128x128"
122 | },
123 | {
124 | "idiom" : "mac",
125 | "scale" : "1x",
126 | "size" : "256x256"
127 | },
128 | {
129 | "idiom" : "mac",
130 | "scale" : "2x",
131 | "size" : "256x256"
132 | },
133 | {
134 | "idiom" : "mac",
135 | "scale" : "1x",
136 | "size" : "512x512"
137 | },
138 | {
139 | "idiom" : "mac",
140 | "scale" : "2x",
141 | "size" : "512x512"
142 | }
143 | ],
144 | "info" : {
145 | "author" : "xcode",
146 | "version" : 1
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ScrollViewLoader
2 |
3 | A simple utility to assist with loading more content in a `ScrollView` in SwiftUI.
4 |
5 | ### Usage
6 |
7 | Add `.shouldLoadMore` to any `ScrollView`. By default it will be triggered when the content at the bottom of the `ScrollView` is close to being in view.
8 |
9 |
10 | ## See it in action
11 | If you want to see it in a real app, check out [dateit](https://apps.apple.com/us/app/dateit/id1610780514)
12 |
13 | Also works well with [SwiftUI-Refresher](https://github.com/gh123man/SwiftUI-Refresher)
14 |
15 | 
16 |
17 |
18 | ## Usage
19 | First add the package to your project.
20 |
21 | ```swift
22 | import ScrollViewLoader
23 |
24 | struct ContentView: View {
25 |
26 | @State var data: [Int] = Array(0..<1)
27 |
28 | var body: some View {
29 | ScrollView {
30 | LazyVStack {
31 | ForEach(data, id: \.self) { i in
32 | Text("\(i)")
33 | .font(.title)
34 | .frame(maxWidth: .infinity)
35 | }
36 | ProgressView()
37 | .scaleEffect(2)
38 | }
39 | }
40 | .shouldLoadMore {
41 | await Task.sleep(seconds: 0.05)
42 | data.append(data.last! + 1)
43 | }
44 | }
45 | }
46 | ```
47 |
48 | ## Customization
49 |
50 | By default, the callback will be triggered when distance to the bottom of the scrollable content is less than `50%` of the visible hight of the scroll view. You can customize this
51 |
52 | Set the relative offset to `20%` instead of the default `50%`:
53 | ```swift
54 | .shouldLoadMore(bottomDistance: .relative(0.2)) {
55 | // Load more
56 | }
57 | ```
58 |
59 | Set the absolute offset to a fixed value:
60 | ```swift
61 | .shouldLoadMore(bottomDistance: .absolute(200)) {
62 | // Load more
63 | }
64 | ```
65 |
66 | ### `waitForHeightChange`
67 |
68 | It may be desirable for `shouldLoadMore` to be called whenever the user scrolls - even if the scroll view content didn't change. You can change this behavior with `waitForHeightChange`:
69 | ```swift
70 | .shouldLoadMore(waitForHeightChange: .never) {
71 | // Will be called regardless of if the content height changed from a previous update
72 | }
73 | ```
74 |
75 | ```swift
76 | .shouldLoadMore(waitForHeightChange: .always) {
77 | // Will only be called if the content height changed since last time
78 | }
79 | ```
80 |
81 | ```swift
82 | .shouldLoadMore(waitForHeightChange: .until(2)) {
83 | // Will only be called if the content height changed since last time or after 2 seconds of no change
84 | }
85 | ```
86 | By default `waitForHeightChange` is `.until(2)` so the function doesn't get called in quick succession when no content updates are made.
87 |
88 | ## More details
89 |
90 | - The callback will only be called once when the bottom approaches.
91 | - If you scroll back up out of the trigger zone, it will be called again when you scroll back down.
92 | - It is up to you to synchronize and de-duplicate multiple scroll triggers by the user (depending on the kind of data you are loading)
93 | - Loading conditions will be re-evaluated if the scroll view content changes in any way.
94 |
95 | # More Examples
96 |
97 | ## Using a completion handler instead of `async`
98 |
99 | ```swift
100 | .shouldLoadMore { done in
101 | loadYourContent {
102 | data.append(data.last! + 1)
103 | done() // Call done so shouldLoadMore can be called again later
104 | }
105 | }
106 | ```
107 |
108 | Larger batching
109 |
110 | 
111 |
--------------------------------------------------------------------------------
/Sources/ScrollViewLoader/ScrollViewLoader.swift:
--------------------------------------------------------------------------------
1 |
2 | import SwiftUI
3 | import UIKit
4 | import SwiftUIIntrospect
5 |
6 | public enum HeightChangeConfig {
7 | case always
8 | case until(TimeInterval)
9 | case never
10 | }
11 |
12 | public enum OffsetTrigger {
13 | case relative(CGFloat)
14 | case absolute(CGFloat)
15 | }
16 |
17 | extension View {
18 | public func shouldLoadMore(bottomDistance offsetTrigger: OffsetTrigger = .relative(0.5),
19 | waitForHeightChange: HeightChangeConfig = .until(2),
20 | shouldLoadMore: @escaping () async -> ()) -> some View {
21 | return DelegateHolder(offsetNotifier: ScrollOffsetNotifier(offsetTrigger: offsetTrigger,
22 | waitForHeightChange: waitForHeightChange,
23 | onNotify: shouldLoadMore),
24 | content: self)
25 | }
26 |
27 | public func shouldLoadMore(bottomDistance offsetTrigger: OffsetTrigger = .relative(0.5),
28 | waitForHeightChange: HeightChangeConfig = .until(2),
29 | shouldLoadMore: @escaping (_ done: @escaping () -> ()) -> ()) -> some View {
30 | return DelegateHolder(offsetNotifier: ScrollOffsetNotifier(offsetTrigger: offsetTrigger,
31 | waitForHeightChange: waitForHeightChange,
32 | onNotify: {
33 | await withCheckedContinuation { continuation in
34 | shouldLoadMore {
35 | continuation.resume()
36 | }
37 | }
38 | }),
39 | content: self)
40 | }
41 | }
42 |
43 | struct DelegateHolder: View {
44 |
45 | @StateObject var offsetNotifier: ScrollOffsetNotifier
46 | var content: Content
47 |
48 | var body: some View {
49 | content
50 | .introspect(.scrollView, on: .iOS(.v15, .v16, .v17, .v18, .v26)) { scrollView in
51 | scrollView.delegate = offsetNotifier
52 | offsetNotifier.scrollView = scrollView
53 | offsetNotifier.scrollViewDidScroll(scrollView)
54 | }
55 | }
56 | }
57 |
58 | class ScrollOffsetNotifier: NSObject, UIScrollViewDelegate, ObservableObject {
59 |
60 | let onNotify: () async -> ()
61 | private var canNotify = true
62 | private var trigger: OffsetTrigger
63 | private var oldContentHeight: Double = 0
64 | private var waitForHeightChange: HeightChangeConfig
65 | private var isTimerRunning = false
66 | weak var scrollView: UIScrollView? {
67 | didSet {
68 | scrollView?.addObserver(self, forKeyPath: "contentSize", context: nil)
69 | }
70 | }
71 |
72 | init(offsetTrigger: OffsetTrigger, waitForHeightChange: HeightChangeConfig, onNotify: @escaping () async -> ()) {
73 | self.trigger = offsetTrigger
74 | self.onNotify = onNotify
75 | self.waitForHeightChange = waitForHeightChange
76 | }
77 |
78 | deinit {
79 | scrollView?.removeObserver(self, forKeyPath: "contentSize")
80 | }
81 |
82 | override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
83 | guard let scrollView = object as? UIScrollView else {
84 | return
85 | }
86 |
87 | scrollViewDidScroll(scrollView)
88 | }
89 |
90 | func scrollViewDidScroll(_ scrollView: UIScrollView) {
91 |
92 | let triggerHeight: CGFloat
93 |
94 | switch trigger {
95 | case let .absolute(offset):
96 | triggerHeight = offset
97 | case let .relative(percent):
98 | triggerHeight = scrollView.visibleSize.height * percent
99 | }
100 |
101 | let bottomOffset = (scrollView.contentSize.height + scrollView.contentInset.bottom) - (scrollView.contentOffset.y + scrollView.visibleSize.height)
102 | var heightChanged = false
103 |
104 | switch waitForHeightChange {
105 | case .always:
106 | heightChanged = oldContentHeight != scrollView.contentSize.height
107 | case .until(let timeInterval):
108 | heightChanged = oldContentHeight != scrollView.contentSize.height
109 | if !isTimerRunning {
110 | isTimerRunning = true
111 | DispatchQueue.main.asyncAfter(deadline: .now() + timeInterval) {
112 | self.oldContentHeight = 0
113 | self.isTimerRunning = false
114 | }
115 | }
116 | case .never:
117 | heightChanged = true
118 | }
119 |
120 | Task { @MainActor in
121 | guard canNotify else { return }
122 | if bottomOffset < triggerHeight, heightChanged {
123 | oldContentHeight = scrollView.contentSize.height
124 | canNotify = false
125 | await onNotify()
126 | canNotify = true
127 | }
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/Examples/ScrollViewLoaderTest.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 55;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | D395B43C2A4A6C1A00583F22 /* ScrollViewLoader in Frameworks */ = {isa = PBXBuildFile; productRef = D395B43B2A4A6C1A00583F22 /* ScrollViewLoader */; };
11 | D3A3F304280CBFEF00BB28F1 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3A3F2DB280CBFEE00BB28F1 /* ContentView.swift */; };
12 | D3A3F306280CBFEF00BB28F1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D3A3F2DC280CBFEF00BB28F1 /* Assets.xcassets */; };
13 | /* End PBXBuildFile section */
14 |
15 | /* Begin PBXContainerItemProxy section */
16 | D3A3F2EF280CBFEF00BB28F1 /* PBXContainerItemProxy */ = {
17 | isa = PBXContainerItemProxy;
18 | containerPortal = D3A3F2D5280CBFEE00BB28F1 /* Project object */;
19 | proxyType = 1;
20 | remoteGlobalIDString = D3A3F2E0280CBFEF00BB28F1;
21 | remoteInfo = "ScrollViewLoaderTest (iOS)";
22 | };
23 | /* End PBXContainerItemProxy section */
24 |
25 | /* Begin PBXFileReference section */
26 | D3A3F2DB280CBFEE00BB28F1 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
27 | D3A3F2DC280CBFEF00BB28F1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
28 | D3A3F2E1280CBFEF00BB28F1 /* ScrollViewLoaderTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ScrollViewLoaderTest.app; sourceTree = BUILT_PRODUCTS_DIR; };
29 | D3A3F2EE280CBFEF00BB28F1 /* Tests iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tests iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
30 | D3A3F331280F185E00BB28F1 /* ScrollViewLoader */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = ScrollViewLoader; path = ..; sourceTree = ""; };
31 | /* End PBXFileReference section */
32 |
33 | /* Begin PBXFrameworksBuildPhase section */
34 | D3A3F2DE280CBFEF00BB28F1 /* Frameworks */ = {
35 | isa = PBXFrameworksBuildPhase;
36 | buildActionMask = 2147483647;
37 | files = (
38 | D395B43C2A4A6C1A00583F22 /* ScrollViewLoader in Frameworks */,
39 | );
40 | runOnlyForDeploymentPostprocessing = 0;
41 | };
42 | D3A3F2EB280CBFEF00BB28F1 /* Frameworks */ = {
43 | isa = PBXFrameworksBuildPhase;
44 | buildActionMask = 2147483647;
45 | files = (
46 | );
47 | runOnlyForDeploymentPostprocessing = 0;
48 | };
49 | /* End PBXFrameworksBuildPhase section */
50 |
51 | /* Begin PBXGroup section */
52 | D3A3F2D4280CBFEE00BB28F1 = {
53 | isa = PBXGroup;
54 | children = (
55 | D3A3F2DB280CBFEE00BB28F1 /* ContentView.swift */,
56 | D3A3F2DC280CBFEF00BB28F1 /* Assets.xcassets */,
57 | D3A3F331280F185E00BB28F1 /* ScrollViewLoader */,
58 | D3A3F31C280CC88500BB28F1 /* Packages */,
59 | D3A3F2E2280CBFEF00BB28F1 /* Products */,
60 | D3A3F321280CCA3300BB28F1 /* Frameworks */,
61 | );
62 | sourceTree = "";
63 | };
64 | D3A3F2E2280CBFEF00BB28F1 /* Products */ = {
65 | isa = PBXGroup;
66 | children = (
67 | D3A3F2E1280CBFEF00BB28F1 /* ScrollViewLoaderTest.app */,
68 | D3A3F2EE280CBFEF00BB28F1 /* Tests iOS.xctest */,
69 | );
70 | name = Products;
71 | sourceTree = "";
72 | };
73 | D3A3F31C280CC88500BB28F1 /* Packages */ = {
74 | isa = PBXGroup;
75 | children = (
76 | );
77 | name = Packages;
78 | sourceTree = "";
79 | };
80 | D3A3F321280CCA3300BB28F1 /* Frameworks */ = {
81 | isa = PBXGroup;
82 | children = (
83 | );
84 | name = Frameworks;
85 | sourceTree = "";
86 | };
87 | /* End PBXGroup section */
88 |
89 | /* Begin PBXNativeTarget section */
90 | D3A3F2E0280CBFEF00BB28F1 /* ScrollViewLoaderTest (iOS) */ = {
91 | isa = PBXNativeTarget;
92 | buildConfigurationList = D3A3F30A280CBFEF00BB28F1 /* Build configuration list for PBXNativeTarget "ScrollViewLoaderTest (iOS)" */;
93 | buildPhases = (
94 | D3A3F2DD280CBFEF00BB28F1 /* Sources */,
95 | D3A3F2DE280CBFEF00BB28F1 /* Frameworks */,
96 | D3A3F2DF280CBFEF00BB28F1 /* Resources */,
97 | );
98 | buildRules = (
99 | );
100 | dependencies = (
101 | );
102 | name = "ScrollViewLoaderTest (iOS)";
103 | packageProductDependencies = (
104 | D395B43B2A4A6C1A00583F22 /* ScrollViewLoader */,
105 | );
106 | productName = "ScrollViewLoaderTest (iOS)";
107 | productReference = D3A3F2E1280CBFEF00BB28F1 /* ScrollViewLoaderTest.app */;
108 | productType = "com.apple.product-type.application";
109 | };
110 | D3A3F2ED280CBFEF00BB28F1 /* Tests iOS */ = {
111 | isa = PBXNativeTarget;
112 | buildConfigurationList = D3A3F310280CBFEF00BB28F1 /* Build configuration list for PBXNativeTarget "Tests iOS" */;
113 | buildPhases = (
114 | D3A3F2EA280CBFEF00BB28F1 /* Sources */,
115 | D3A3F2EB280CBFEF00BB28F1 /* Frameworks */,
116 | D3A3F2EC280CBFEF00BB28F1 /* Resources */,
117 | );
118 | buildRules = (
119 | );
120 | dependencies = (
121 | D3A3F2F0280CBFEF00BB28F1 /* PBXTargetDependency */,
122 | );
123 | name = "Tests iOS";
124 | productName = "Tests iOS";
125 | productReference = D3A3F2EE280CBFEF00BB28F1 /* Tests iOS.xctest */;
126 | productType = "com.apple.product-type.bundle.ui-testing";
127 | };
128 | /* End PBXNativeTarget section */
129 |
130 | /* Begin PBXProject section */
131 | D3A3F2D5280CBFEE00BB28F1 /* Project object */ = {
132 | isa = PBXProject;
133 | attributes = {
134 | BuildIndependentTargetsInParallel = 1;
135 | LastSwiftUpdateCheck = 1330;
136 | LastUpgradeCheck = 1330;
137 | TargetAttributes = {
138 | D3A3F2E0280CBFEF00BB28F1 = {
139 | CreatedOnToolsVersion = 13.3.1;
140 | };
141 | D3A3F2ED280CBFEF00BB28F1 = {
142 | CreatedOnToolsVersion = 13.3.1;
143 | TestTargetID = D3A3F2E0280CBFEF00BB28F1;
144 | };
145 | };
146 | };
147 | buildConfigurationList = D3A3F2D8280CBFEE00BB28F1 /* Build configuration list for PBXProject "ScrollViewLoaderTest" */;
148 | compatibilityVersion = "Xcode 13.0";
149 | developmentRegion = en;
150 | hasScannedForEncodings = 0;
151 | knownRegions = (
152 | en,
153 | Base,
154 | );
155 | mainGroup = D3A3F2D4280CBFEE00BB28F1;
156 | productRefGroup = D3A3F2E2280CBFEF00BB28F1 /* Products */;
157 | projectDirPath = "";
158 | projectRoot = "";
159 | targets = (
160 | D3A3F2E0280CBFEF00BB28F1 /* ScrollViewLoaderTest (iOS) */,
161 | D3A3F2ED280CBFEF00BB28F1 /* Tests iOS */,
162 | );
163 | };
164 | /* End PBXProject section */
165 |
166 | /* Begin PBXResourcesBuildPhase section */
167 | D3A3F2DF280CBFEF00BB28F1 /* Resources */ = {
168 | isa = PBXResourcesBuildPhase;
169 | buildActionMask = 2147483647;
170 | files = (
171 | D3A3F306280CBFEF00BB28F1 /* Assets.xcassets in Resources */,
172 | );
173 | runOnlyForDeploymentPostprocessing = 0;
174 | };
175 | D3A3F2EC280CBFEF00BB28F1 /* Resources */ = {
176 | isa = PBXResourcesBuildPhase;
177 | buildActionMask = 2147483647;
178 | files = (
179 | );
180 | runOnlyForDeploymentPostprocessing = 0;
181 | };
182 | /* End PBXResourcesBuildPhase section */
183 |
184 | /* Begin PBXSourcesBuildPhase section */
185 | D3A3F2DD280CBFEF00BB28F1 /* Sources */ = {
186 | isa = PBXSourcesBuildPhase;
187 | buildActionMask = 2147483647;
188 | files = (
189 | D3A3F304280CBFEF00BB28F1 /* ContentView.swift in Sources */,
190 | );
191 | runOnlyForDeploymentPostprocessing = 0;
192 | };
193 | D3A3F2EA280CBFEF00BB28F1 /* Sources */ = {
194 | isa = PBXSourcesBuildPhase;
195 | buildActionMask = 2147483647;
196 | files = (
197 | );
198 | runOnlyForDeploymentPostprocessing = 0;
199 | };
200 | /* End PBXSourcesBuildPhase section */
201 |
202 | /* Begin PBXTargetDependency section */
203 | D3A3F2F0280CBFEF00BB28F1 /* PBXTargetDependency */ = {
204 | isa = PBXTargetDependency;
205 | target = D3A3F2E0280CBFEF00BB28F1 /* ScrollViewLoaderTest (iOS) */;
206 | targetProxy = D3A3F2EF280CBFEF00BB28F1 /* PBXContainerItemProxy */;
207 | };
208 | /* End PBXTargetDependency section */
209 |
210 | /* Begin XCBuildConfiguration section */
211 | D3A3F308280CBFEF00BB28F1 /* Debug */ = {
212 | isa = XCBuildConfiguration;
213 | buildSettings = {
214 | ALWAYS_SEARCH_USER_PATHS = NO;
215 | CLANG_ANALYZER_NONNULL = YES;
216 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
217 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
218 | CLANG_ENABLE_MODULES = YES;
219 | CLANG_ENABLE_OBJC_ARC = YES;
220 | CLANG_ENABLE_OBJC_WEAK = YES;
221 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
222 | CLANG_WARN_BOOL_CONVERSION = YES;
223 | CLANG_WARN_COMMA = YES;
224 | CLANG_WARN_CONSTANT_CONVERSION = YES;
225 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
226 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
227 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
228 | CLANG_WARN_EMPTY_BODY = YES;
229 | CLANG_WARN_ENUM_CONVERSION = YES;
230 | CLANG_WARN_INFINITE_RECURSION = YES;
231 | CLANG_WARN_INT_CONVERSION = YES;
232 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
233 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
234 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
235 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
236 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
237 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
238 | CLANG_WARN_STRICT_PROTOTYPES = YES;
239 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
240 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
241 | CLANG_WARN_UNREACHABLE_CODE = YES;
242 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
243 | COPY_PHASE_STRIP = NO;
244 | DEBUG_INFORMATION_FORMAT = dwarf;
245 | ENABLE_STRICT_OBJC_MSGSEND = YES;
246 | ENABLE_TESTABILITY = YES;
247 | GCC_C_LANGUAGE_STANDARD = gnu11;
248 | GCC_DYNAMIC_NO_PIC = NO;
249 | GCC_NO_COMMON_BLOCKS = YES;
250 | GCC_OPTIMIZATION_LEVEL = 0;
251 | GCC_PREPROCESSOR_DEFINITIONS = (
252 | "DEBUG=1",
253 | "$(inherited)",
254 | );
255 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
256 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
257 | GCC_WARN_UNDECLARED_SELECTOR = YES;
258 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
259 | GCC_WARN_UNUSED_FUNCTION = YES;
260 | GCC_WARN_UNUSED_VARIABLE = YES;
261 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
262 | MTL_FAST_MATH = YES;
263 | ONLY_ACTIVE_ARCH = YES;
264 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
265 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
266 | };
267 | name = Debug;
268 | };
269 | D3A3F309280CBFEF00BB28F1 /* Release */ = {
270 | isa = XCBuildConfiguration;
271 | buildSettings = {
272 | ALWAYS_SEARCH_USER_PATHS = NO;
273 | CLANG_ANALYZER_NONNULL = YES;
274 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
275 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
276 | CLANG_ENABLE_MODULES = YES;
277 | CLANG_ENABLE_OBJC_ARC = YES;
278 | CLANG_ENABLE_OBJC_WEAK = YES;
279 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
280 | CLANG_WARN_BOOL_CONVERSION = YES;
281 | CLANG_WARN_COMMA = YES;
282 | CLANG_WARN_CONSTANT_CONVERSION = YES;
283 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
284 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
285 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
286 | CLANG_WARN_EMPTY_BODY = YES;
287 | CLANG_WARN_ENUM_CONVERSION = YES;
288 | CLANG_WARN_INFINITE_RECURSION = YES;
289 | CLANG_WARN_INT_CONVERSION = YES;
290 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
291 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
292 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
293 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
294 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
295 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
296 | CLANG_WARN_STRICT_PROTOTYPES = YES;
297 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
298 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
299 | CLANG_WARN_UNREACHABLE_CODE = YES;
300 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
301 | COPY_PHASE_STRIP = NO;
302 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
303 | ENABLE_NS_ASSERTIONS = NO;
304 | ENABLE_STRICT_OBJC_MSGSEND = YES;
305 | GCC_C_LANGUAGE_STANDARD = gnu11;
306 | GCC_NO_COMMON_BLOCKS = YES;
307 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
308 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
309 | GCC_WARN_UNDECLARED_SELECTOR = YES;
310 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
311 | GCC_WARN_UNUSED_FUNCTION = YES;
312 | GCC_WARN_UNUSED_VARIABLE = YES;
313 | MTL_ENABLE_DEBUG_INFO = NO;
314 | MTL_FAST_MATH = YES;
315 | SWIFT_COMPILATION_MODE = wholemodule;
316 | SWIFT_OPTIMIZATION_LEVEL = "-O";
317 | };
318 | name = Release;
319 | };
320 | D3A3F30B280CBFEF00BB28F1 /* Debug */ = {
321 | isa = XCBuildConfiguration;
322 | buildSettings = {
323 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
324 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
325 | CODE_SIGN_STYLE = Automatic;
326 | CURRENT_PROJECT_VERSION = 1;
327 | DEVELOPMENT_TEAM = 79TGW25V5A;
328 | ENABLE_PREVIEWS = YES;
329 | GENERATE_INFOPLIST_FILE = YES;
330 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
331 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
332 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
333 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
334 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
335 | IPHONEOS_DEPLOYMENT_TARGET = 14.1;
336 | LD_RUNPATH_SEARCH_PATHS = (
337 | "$(inherited)",
338 | "@executable_path/Frameworks",
339 | );
340 | MARKETING_VERSION = 1.0;
341 | PRODUCT_BUNDLE_IDENTIFIER = bugtest.ScrollViewLoaderTest;
342 | PRODUCT_NAME = ScrollViewLoaderTest;
343 | SDKROOT = iphoneos;
344 | SWIFT_EMIT_LOC_STRINGS = YES;
345 | SWIFT_VERSION = 5.0;
346 | TARGETED_DEVICE_FAMILY = "1,2";
347 | };
348 | name = Debug;
349 | };
350 | D3A3F30C280CBFEF00BB28F1 /* Release */ = {
351 | isa = XCBuildConfiguration;
352 | buildSettings = {
353 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
354 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
355 | CODE_SIGN_STYLE = Automatic;
356 | CURRENT_PROJECT_VERSION = 1;
357 | DEVELOPMENT_TEAM = 79TGW25V5A;
358 | ENABLE_PREVIEWS = YES;
359 | GENERATE_INFOPLIST_FILE = YES;
360 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
361 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
362 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
363 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
364 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
365 | IPHONEOS_DEPLOYMENT_TARGET = 14.1;
366 | LD_RUNPATH_SEARCH_PATHS = (
367 | "$(inherited)",
368 | "@executable_path/Frameworks",
369 | );
370 | MARKETING_VERSION = 1.0;
371 | PRODUCT_BUNDLE_IDENTIFIER = bugtest.ScrollViewLoaderTest;
372 | PRODUCT_NAME = ScrollViewLoaderTest;
373 | SDKROOT = iphoneos;
374 | SWIFT_EMIT_LOC_STRINGS = YES;
375 | SWIFT_VERSION = 5.0;
376 | TARGETED_DEVICE_FAMILY = "1,2";
377 | VALIDATE_PRODUCT = YES;
378 | };
379 | name = Release;
380 | };
381 | D3A3F311280CBFEF00BB28F1 /* Debug */ = {
382 | isa = XCBuildConfiguration;
383 | buildSettings = {
384 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
385 | CODE_SIGN_STYLE = Automatic;
386 | CURRENT_PROJECT_VERSION = 1;
387 | DEVELOPMENT_TEAM = 79TGW25V5A;
388 | GENERATE_INFOPLIST_FILE = YES;
389 | IPHONEOS_DEPLOYMENT_TARGET = 15.4;
390 | MARKETING_VERSION = 1.0;
391 | PRODUCT_BUNDLE_IDENTIFIER = "bugtest.Tests-iOS";
392 | PRODUCT_NAME = "$(TARGET_NAME)";
393 | SDKROOT = iphoneos;
394 | SWIFT_EMIT_LOC_STRINGS = NO;
395 | SWIFT_VERSION = 5.0;
396 | TARGETED_DEVICE_FAMILY = "1,2";
397 | TEST_TARGET_NAME = "ScrollViewLoaderTest (iOS)";
398 | };
399 | name = Debug;
400 | };
401 | D3A3F312280CBFEF00BB28F1 /* Release */ = {
402 | isa = XCBuildConfiguration;
403 | buildSettings = {
404 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
405 | CODE_SIGN_STYLE = Automatic;
406 | CURRENT_PROJECT_VERSION = 1;
407 | DEVELOPMENT_TEAM = 79TGW25V5A;
408 | GENERATE_INFOPLIST_FILE = YES;
409 | IPHONEOS_DEPLOYMENT_TARGET = 15.4;
410 | MARKETING_VERSION = 1.0;
411 | PRODUCT_BUNDLE_IDENTIFIER = "bugtest.Tests-iOS";
412 | PRODUCT_NAME = "$(TARGET_NAME)";
413 | SDKROOT = iphoneos;
414 | SWIFT_EMIT_LOC_STRINGS = NO;
415 | SWIFT_VERSION = 5.0;
416 | TARGETED_DEVICE_FAMILY = "1,2";
417 | TEST_TARGET_NAME = "ScrollViewLoaderTest (iOS)";
418 | VALIDATE_PRODUCT = YES;
419 | };
420 | name = Release;
421 | };
422 | /* End XCBuildConfiguration section */
423 |
424 | /* Begin XCConfigurationList section */
425 | D3A3F2D8280CBFEE00BB28F1 /* Build configuration list for PBXProject "ScrollViewLoaderTest" */ = {
426 | isa = XCConfigurationList;
427 | buildConfigurations = (
428 | D3A3F308280CBFEF00BB28F1 /* Debug */,
429 | D3A3F309280CBFEF00BB28F1 /* Release */,
430 | );
431 | defaultConfigurationIsVisible = 0;
432 | defaultConfigurationName = Release;
433 | };
434 | D3A3F30A280CBFEF00BB28F1 /* Build configuration list for PBXNativeTarget "ScrollViewLoaderTest (iOS)" */ = {
435 | isa = XCConfigurationList;
436 | buildConfigurations = (
437 | D3A3F30B280CBFEF00BB28F1 /* Debug */,
438 | D3A3F30C280CBFEF00BB28F1 /* Release */,
439 | );
440 | defaultConfigurationIsVisible = 0;
441 | defaultConfigurationName = Release;
442 | };
443 | D3A3F310280CBFEF00BB28F1 /* Build configuration list for PBXNativeTarget "Tests iOS" */ = {
444 | isa = XCConfigurationList;
445 | buildConfigurations = (
446 | D3A3F311280CBFEF00BB28F1 /* Debug */,
447 | D3A3F312280CBFEF00BB28F1 /* Release */,
448 | );
449 | defaultConfigurationIsVisible = 0;
450 | defaultConfigurationName = Release;
451 | };
452 | /* End XCConfigurationList section */
453 |
454 | /* Begin XCSwiftPackageProductDependency section */
455 | D395B43B2A4A6C1A00583F22 /* ScrollViewLoader */ = {
456 | isa = XCSwiftPackageProductDependency;
457 | productName = ScrollViewLoader;
458 | };
459 | /* End XCSwiftPackageProductDependency section */
460 | };
461 | rootObject = D3A3F2D5280CBFEE00BB28F1 /* Project object */;
462 | }
463 |
--------------------------------------------------------------------------------