Full Code of gh123man/SwiftUI-LazyPager for AI

master be4b1952c105 cached
36 files
99.8 KB
26.2k tokens
1 requests
Download .txt
Repository: gh123man/SwiftUI-LazyPager
Branch: master
Commit: be4b1952c105
Files: 36
Total size: 99.8 KB

Directory structure:
gitextract_8knwvjye/

├── .github/
│   └── ISSUE_TEMPLATE/
│       └── bug_report.md
├── .gitignore
├── Examples/
│   ├── LazyPagerExample.xcodeproj/
│   │   ├── project.pbxproj
│   │   └── project.xcworkspace/
│   │       ├── contents.xcworkspacedata
│   │       └── xcshareddata/
│   │           └── IDEWorkspaceChecks.plist
│   ├── LazyPagerExampleApp/
│   │   ├── AnimatedPagerControlsExample.swift
│   │   ├── Assets.xcassets/
│   │   │   ├── AccentColor.colorset/
│   │   │   │   └── Contents.json
│   │   │   ├── AppIcon.appiconset/
│   │   │   │   └── Contents.json
│   │   │   ├── Contents.json
│   │   │   ├── nora1.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── nora2.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── nora3.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── nora4.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── nora5.imageset/
│   │   │   │   └── Contents.json
│   │   │   └── nora6.imageset/
│   │   │       └── Contents.json
│   │   ├── EnvironmentExample.swift
│   │   ├── FullTestView.swift
│   │   ├── InsetTest.swift
│   │   ├── LazyPagerExampleApp.swift
│   │   ├── Preview Content/
│   │   │   └── Preview Assets.xcassets/
│   │   │       └── Contents.json
│   │   ├── SimpleExample.swift
│   │   └── VerticalMediaPager.swift
│   ├── LazyPagerExampleAppTests/
│   │   └── LazyPagerExampleAppTests.swift
│   └── LazyPagerExampleAppUITests/
│       ├── ImageScrollViewUITests.swift
│       └── ImageScrollViewUITestsLaunchTests.swift
├── LICENSE
├── Package.swift
├── README.md
├── Sources/
│   └── LazyPager/
│       ├── ClearFullScreenBackground.swift
│       ├── Collection+Extensions.swift
│       ├── LazyPager.swift
│       ├── Math.swift
│       ├── PagerView.swift
│       ├── ViewDataProvider.swift
│       └── ZoomableView.swift
└── Tests/
    └── LazyPagerTests/
        └── LazyPagerTests.swift

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
A working minimal code example is preferred!
If you cannot produce example code please list the steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Additional context**
Add any other context about the problem here.


================================================
FILE: .gitignore
================================================
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc


================================================
FILE: Examples/LazyPagerExample.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
	archiveVersion = 1;
	classes = {
	};
	objectVersion = 60;
	objects = {

/* Begin PBXBuildFile section */
		D309D3802E88B66E007A6FDC /* InsetTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D309D37F2E88B66E007A6FDC /* InsetTest.swift */; };
		D33BC0522B69E6EE004B4338 /* SimpleExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = D33BC0512B69E6EE004B4338 /* SimpleExample.swift */; };
		D33F96FD2C62F582004D934A /* VerticalMediaPager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D33F96FC2C62F582004D934A /* VerticalMediaPager.swift */; };
		D353FB592A52174B00C04ABE /* LazyPagerExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D353FB582A52174B00C04ABE /* LazyPagerExampleApp.swift */; };
		D353FB5B2A52174B00C04ABE /* FullTestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D353FB5A2A52174B00C04ABE /* FullTestView.swift */; };
		D353FB5D2A52174C00C04ABE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D353FB5C2A52174C00C04ABE /* Assets.xcassets */; };
		D353FB602A52174C00C04ABE /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D353FB5F2A52174C00C04ABE /* Preview Assets.xcassets */; };
		D353FB6A2A52174C00C04ABE /* LazyPagerExampleAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D353FB692A52174C00C04ABE /* LazyPagerExampleAppTests.swift */; };
		D353FB742A52174C00C04ABE /* ImageScrollViewUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D353FB732A52174C00C04ABE /* ImageScrollViewUITests.swift */; };
		D353FB762A52174C00C04ABE /* ImageScrollViewUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D353FB752A52174C00C04ABE /* ImageScrollViewUITestsLaunchTests.swift */; };
		D367DA132A59E930004497D4 /* LazyPager in Frameworks */ = {isa = PBXBuildFile; productRef = D367DA122A59E930004497D4 /* LazyPager */; };
		D3776B5F2CF5658500AFB89D /* AnimatedPagerControlsExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3776B5E2CF5658500AFB89D /* AnimatedPagerControlsExample.swift */; };
		D38D65E32C62E4B900AA140E /* LazyPager in Frameworks */ = {isa = PBXBuildFile; productRef = D38D65E22C62E4B900AA140E /* LazyPager */; };
		D3B3AEAC2DBD500800AC1E33 /* EnvironmentExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3B3AEAB2DBD500500AC1E33 /* EnvironmentExample.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
		D353FB662A52174C00C04ABE /* PBXContainerItemProxy */ = {
			isa = PBXContainerItemProxy;
			containerPortal = D353FB4D2A52174B00C04ABE /* Project object */;
			proxyType = 1;
			remoteGlobalIDString = D353FB542A52174B00C04ABE;
			remoteInfo = ImageScrollView;
		};
		D353FB702A52174C00C04ABE /* PBXContainerItemProxy */ = {
			isa = PBXContainerItemProxy;
			containerPortal = D353FB4D2A52174B00C04ABE /* Project object */;
			proxyType = 1;
			remoteGlobalIDString = D353FB542A52174B00C04ABE;
			remoteInfo = ImageScrollView;
		};
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
		D309D37F2E88B66E007A6FDC /* InsetTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsetTest.swift; sourceTree = "<group>"; };
		D33BC0512B69E6EE004B4338 /* SimpleExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleExample.swift; sourceTree = "<group>"; };
		D33F96FC2C62F582004D934A /* VerticalMediaPager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerticalMediaPager.swift; sourceTree = "<group>"; };
		D353FB552A52174B00C04ABE /* LazyPagerExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LazyPagerExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
		D353FB582A52174B00C04ABE /* LazyPagerExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyPagerExampleApp.swift; sourceTree = "<group>"; };
		D353FB5A2A52174B00C04ABE /* FullTestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullTestView.swift; sourceTree = "<group>"; };
		D353FB5C2A52174C00C04ABE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
		D353FB5F2A52174C00C04ABE /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
		D353FB652A52174C00C04ABE /* LazyPagerExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LazyPagerExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
		D353FB692A52174C00C04ABE /* LazyPagerExampleAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyPagerExampleAppTests.swift; sourceTree = "<group>"; };
		D353FB6F2A52174C00C04ABE /* LazyPagerExampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LazyPagerExampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
		D353FB732A52174C00C04ABE /* ImageScrollViewUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageScrollViewUITests.swift; sourceTree = "<group>"; };
		D353FB752A52174C00C04ABE /* ImageScrollViewUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageScrollViewUITestsLaunchTests.swift; sourceTree = "<group>"; };
		D3776B5E2CF5658500AFB89D /* AnimatedPagerControlsExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatedPagerControlsExample.swift; sourceTree = "<group>"; };
		D38D65E02C62E47C00AA140E /* LazyPager */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = LazyPager; path = ..; sourceTree = "<group>"; };
		D3B3AEAB2DBD500500AC1E33 /* EnvironmentExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentExample.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
		D353FB522A52174B00C04ABE /* Frameworks */ = {
			isa = PBXFrameworksBuildPhase;
			buildActionMask = 2147483647;
			files = (
				D38D65E32C62E4B900AA140E /* LazyPager in Frameworks */,
				D367DA132A59E930004497D4 /* LazyPager in Frameworks */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
		D353FB622A52174C00C04ABE /* Frameworks */ = {
			isa = PBXFrameworksBuildPhase;
			buildActionMask = 2147483647;
			files = (
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
		D353FB6C2A52174C00C04ABE /* Frameworks */ = {
			isa = PBXFrameworksBuildPhase;
			buildActionMask = 2147483647;
			files = (
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
		D353FB4C2A52174B00C04ABE = {
			isa = PBXGroup;
			children = (
				D353FB572A52174B00C04ABE /* LazyPagerExampleApp */,
				D353FB682A52174C00C04ABE /* LazyPagerExampleAppTests */,
				D353FB722A52174C00C04ABE /* LazyPagerExampleAppUITests */,
				D353FB562A52174B00C04ABE /* Products */,
				D367DA112A59E930004497D4 /* Frameworks */,
			);
			sourceTree = "<group>";
		};
		D353FB562A52174B00C04ABE /* Products */ = {
			isa = PBXGroup;
			children = (
				D353FB552A52174B00C04ABE /* LazyPagerExample.app */,
				D353FB652A52174C00C04ABE /* LazyPagerExampleTests.xctest */,
				D353FB6F2A52174C00C04ABE /* LazyPagerExampleUITests.xctest */,
			);
			name = Products;
			sourceTree = "<group>";
		};
		D353FB572A52174B00C04ABE /* LazyPagerExampleApp */ = {
			isa = PBXGroup;
			children = (
				D3B3AEAB2DBD500500AC1E33 /* EnvironmentExample.swift */,
				D353FB582A52174B00C04ABE /* LazyPagerExampleApp.swift */,
				D33F96FC2C62F582004D934A /* VerticalMediaPager.swift */,
				D33BC0512B69E6EE004B4338 /* SimpleExample.swift */,
				D309D37F2E88B66E007A6FDC /* InsetTest.swift */,
				D3776B5E2CF5658500AFB89D /* AnimatedPagerControlsExample.swift */,
				D353FB5A2A52174B00C04ABE /* FullTestView.swift */,
				D353FB5C2A52174C00C04ABE /* Assets.xcassets */,
				D353FB5E2A52174C00C04ABE /* Preview Content */,
			);
			path = LazyPagerExampleApp;
			sourceTree = "<group>";
		};
		D353FB5E2A52174C00C04ABE /* Preview Content */ = {
			isa = PBXGroup;
			children = (
				D353FB5F2A52174C00C04ABE /* Preview Assets.xcassets */,
			);
			path = "Preview Content";
			sourceTree = "<group>";
		};
		D353FB682A52174C00C04ABE /* LazyPagerExampleAppTests */ = {
			isa = PBXGroup;
			children = (
				D353FB692A52174C00C04ABE /* LazyPagerExampleAppTests.swift */,
			);
			path = LazyPagerExampleAppTests;
			sourceTree = "<group>";
		};
		D353FB722A52174C00C04ABE /* LazyPagerExampleAppUITests */ = {
			isa = PBXGroup;
			children = (
				D353FB732A52174C00C04ABE /* ImageScrollViewUITests.swift */,
				D353FB752A52174C00C04ABE /* ImageScrollViewUITestsLaunchTests.swift */,
			);
			path = LazyPagerExampleAppUITests;
			sourceTree = "<group>";
		};
		D367DA112A59E930004497D4 /* Frameworks */ = {
			isa = PBXGroup;
			children = (
				D38D65E02C62E47C00AA140E /* LazyPager */,
			);
			name = Frameworks;
			sourceTree = "<group>";
		};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
		D353FB542A52174B00C04ABE /* LazyPagerExample */ = {
			isa = PBXNativeTarget;
			buildConfigurationList = D353FB792A52174C00C04ABE /* Build configuration list for PBXNativeTarget "LazyPagerExample" */;
			buildPhases = (
				D353FB512A52174B00C04ABE /* Sources */,
				D353FB522A52174B00C04ABE /* Frameworks */,
				D353FB532A52174B00C04ABE /* Resources */,
			);
			buildRules = (
			);
			dependencies = (
			);
			name = LazyPagerExample;
			packageProductDependencies = (
				D367DA122A59E930004497D4 /* LazyPager */,
				D38D65E22C62E4B900AA140E /* LazyPager */,
			);
			productName = ImageScrollView;
			productReference = D353FB552A52174B00C04ABE /* LazyPagerExample.app */;
			productType = "com.apple.product-type.application";
		};
		D353FB642A52174C00C04ABE /* LazyPagerExampleTests */ = {
			isa = PBXNativeTarget;
			buildConfigurationList = D353FB7C2A52174C00C04ABE /* Build configuration list for PBXNativeTarget "LazyPagerExampleTests" */;
			buildPhases = (
				D353FB612A52174C00C04ABE /* Sources */,
				D353FB622A52174C00C04ABE /* Frameworks */,
				D353FB632A52174C00C04ABE /* Resources */,
			);
			buildRules = (
			);
			dependencies = (
				D353FB672A52174C00C04ABE /* PBXTargetDependency */,
			);
			name = LazyPagerExampleTests;
			productName = ImageScrollViewTests;
			productReference = D353FB652A52174C00C04ABE /* LazyPagerExampleTests.xctest */;
			productType = "com.apple.product-type.bundle.unit-test";
		};
		D353FB6E2A52174C00C04ABE /* LazyPagerExampleUITests */ = {
			isa = PBXNativeTarget;
			buildConfigurationList = D353FB7F2A52174C00C04ABE /* Build configuration list for PBXNativeTarget "LazyPagerExampleUITests" */;
			buildPhases = (
				D353FB6B2A52174C00C04ABE /* Sources */,
				D353FB6C2A52174C00C04ABE /* Frameworks */,
				D353FB6D2A52174C00C04ABE /* Resources */,
			);
			buildRules = (
			);
			dependencies = (
				D353FB712A52174C00C04ABE /* PBXTargetDependency */,
			);
			name = LazyPagerExampleUITests;
			productName = ImageScrollViewUITests;
			productReference = D353FB6F2A52174C00C04ABE /* LazyPagerExampleUITests.xctest */;
			productType = "com.apple.product-type.bundle.ui-testing";
		};
/* End PBXNativeTarget section */

/* Begin PBXProject section */
		D353FB4D2A52174B00C04ABE /* Project object */ = {
			isa = PBXProject;
			attributes = {
				BuildIndependentTargetsInParallel = 1;
				LastSwiftUpdateCheck = 1420;
				LastUpgradeCheck = 1420;
				TargetAttributes = {
					D353FB542A52174B00C04ABE = {
						CreatedOnToolsVersion = 14.2;
					};
					D353FB642A52174C00C04ABE = {
						CreatedOnToolsVersion = 14.2;
						TestTargetID = D353FB542A52174B00C04ABE;
					};
					D353FB6E2A52174C00C04ABE = {
						CreatedOnToolsVersion = 14.2;
						TestTargetID = D353FB542A52174B00C04ABE;
					};
				};
			};
			buildConfigurationList = D353FB502A52174B00C04ABE /* Build configuration list for PBXProject "LazyPagerExample" */;
			compatibilityVersion = "Xcode 14.0";
			developmentRegion = en;
			hasScannedForEncodings = 0;
			knownRegions = (
				en,
				Base,
			);
			mainGroup = D353FB4C2A52174B00C04ABE;
			packageReferences = (
				D38D65E12C62E4B900AA140E /* XCLocalSwiftPackageReference "../" */,
			);
			productRefGroup = D353FB562A52174B00C04ABE /* Products */;
			projectDirPath = "";
			projectRoot = "";
			targets = (
				D353FB542A52174B00C04ABE /* LazyPagerExample */,
				D353FB642A52174C00C04ABE /* LazyPagerExampleTests */,
				D353FB6E2A52174C00C04ABE /* LazyPagerExampleUITests */,
			);
		};
/* End PBXProject section */

/* Begin PBXResourcesBuildPhase section */
		D353FB532A52174B00C04ABE /* Resources */ = {
			isa = PBXResourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				D353FB602A52174C00C04ABE /* Preview Assets.xcassets in Resources */,
				D353FB5D2A52174C00C04ABE /* Assets.xcassets in Resources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
		D353FB632A52174C00C04ABE /* Resources */ = {
			isa = PBXResourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
		D353FB6D2A52174C00C04ABE /* Resources */ = {
			isa = PBXResourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXResourcesBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
		D353FB512A52174B00C04ABE /* Sources */ = {
			isa = PBXSourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				D353FB5B2A52174B00C04ABE /* FullTestView.swift in Sources */,
				D33F96FD2C62F582004D934A /* VerticalMediaPager.swift in Sources */,
				D3776B5F2CF5658500AFB89D /* AnimatedPagerControlsExample.swift in Sources */,
				D33BC0522B69E6EE004B4338 /* SimpleExample.swift in Sources */,
				D3B3AEAC2DBD500800AC1E33 /* EnvironmentExample.swift in Sources */,
				D353FB592A52174B00C04ABE /* LazyPagerExampleApp.swift in Sources */,
				D309D3802E88B66E007A6FDC /* InsetTest.swift in Sources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
		D353FB612A52174C00C04ABE /* Sources */ = {
			isa = PBXSourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				D353FB6A2A52174C00C04ABE /* LazyPagerExampleAppTests.swift in Sources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
		D353FB6B2A52174C00C04ABE /* Sources */ = {
			isa = PBXSourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				D353FB762A52174C00C04ABE /* ImageScrollViewUITestsLaunchTests.swift in Sources */,
				D353FB742A52174C00C04ABE /* ImageScrollViewUITests.swift in Sources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXSourcesBuildPhase section */

/* Begin PBXTargetDependency section */
		D353FB672A52174C00C04ABE /* PBXTargetDependency */ = {
			isa = PBXTargetDependency;
			target = D353FB542A52174B00C04ABE /* LazyPagerExample */;
			targetProxy = D353FB662A52174C00C04ABE /* PBXContainerItemProxy */;
		};
		D353FB712A52174C00C04ABE /* PBXTargetDependency */ = {
			isa = PBXTargetDependency;
			target = D353FB542A52174B00C04ABE /* LazyPagerExample */;
			targetProxy = D353FB702A52174C00C04ABE /* PBXContainerItemProxy */;
		};
/* End PBXTargetDependency section */

/* Begin XCBuildConfiguration section */
		D353FB772A52174C00C04ABE /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_ENABLE_OBJC_WEAK = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				COPY_PHASE_STRIP = NO;
				DEBUG_INFORMATION_FORMAT = dwarf;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				ENABLE_TESTABILITY = YES;
				GCC_C_LANGUAGE_STANDARD = gnu11;
				GCC_DYNAMIC_NO_PIC = NO;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_OPTIMIZATION_LEVEL = 0;
				GCC_PREPROCESSOR_DEFINITIONS = (
					"DEBUG=1",
					"$(inherited)",
				);
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				IPHONEOS_DEPLOYMENT_TARGET = 16.2;
				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
				MTL_FAST_MATH = YES;
				ONLY_ACTIVE_ARCH = YES;
				SDKROOT = iphoneos;
				SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
			};
			name = Debug;
		};
		D353FB782A52174C00C04ABE /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_ENABLE_OBJC_WEAK = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				COPY_PHASE_STRIP = NO;
				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
				ENABLE_NS_ASSERTIONS = NO;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				GCC_C_LANGUAGE_STANDARD = gnu11;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				IPHONEOS_DEPLOYMENT_TARGET = 16.2;
				MTL_ENABLE_DEBUG_INFO = NO;
				MTL_FAST_MATH = YES;
				SDKROOT = iphoneos;
				SWIFT_COMPILATION_MODE = wholemodule;
				SWIFT_OPTIMIZATION_LEVEL = "-O";
				VALIDATE_PRODUCT = YES;
			};
			name = Release;
		};
		D353FB7A2A52174C00C04ABE /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
				CODE_SIGN_STYLE = Automatic;
				CURRENT_PROJECT_VERSION = 1;
				DEVELOPMENT_ASSET_PATHS = "\"LazyPagerExampleApp/Preview Content\"";
				DEVELOPMENT_TEAM = BX46265734;
				ENABLE_PREVIEWS = YES;
				GENERATE_INFOPLIST_FILE = YES;
				INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
				INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
				INFOPLIST_KEY_UILaunchScreen_Generation = YES;
				INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
				INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
				IPHONEOS_DEPLOYMENT_TARGET = 17.6;
				LD_RUNPATH_SEARCH_PATHS = (
					"$(inherited)",
					"@executable_path/Frameworks",
				);
				MARKETING_VERSION = 1.0;
				PRODUCT_BUNDLE_IDENTIFIER = dateit.ImageScrollView;
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_EMIT_LOC_STRINGS = YES;
				SWIFT_VERSION = 5.0;
				TARGETED_DEVICE_FAMILY = "1,2";
			};
			name = Debug;
		};
		D353FB7B2A52174C00C04ABE /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
				CODE_SIGN_STYLE = Automatic;
				CURRENT_PROJECT_VERSION = 1;
				DEVELOPMENT_ASSET_PATHS = "\"LazyPagerExampleApp/Preview Content\"";
				DEVELOPMENT_TEAM = BX46265734;
				ENABLE_PREVIEWS = YES;
				GENERATE_INFOPLIST_FILE = YES;
				INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
				INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
				INFOPLIST_KEY_UILaunchScreen_Generation = YES;
				INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
				INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
				IPHONEOS_DEPLOYMENT_TARGET = 17.6;
				LD_RUNPATH_SEARCH_PATHS = (
					"$(inherited)",
					"@executable_path/Frameworks",
				);
				MARKETING_VERSION = 1.0;
				PRODUCT_BUNDLE_IDENTIFIER = dateit.ImageScrollView;
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_EMIT_LOC_STRINGS = YES;
				SWIFT_VERSION = 5.0;
				TARGETED_DEVICE_FAMILY = "1,2";
			};
			name = Release;
		};
		D353FB7D2A52174C00C04ABE /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
				BUNDLE_LOADER = "$(TEST_HOST)";
				CODE_SIGN_STYLE = Automatic;
				CURRENT_PROJECT_VERSION = 1;
				DEVELOPMENT_TEAM = BX46265734;
				GENERATE_INFOPLIST_FILE = YES;
				IPHONEOS_DEPLOYMENT_TARGET = 16.2;
				MARKETING_VERSION = 1.0;
				PRODUCT_BUNDLE_IDENTIFIER = dateit.ImageScrollViewTests;
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_EMIT_LOC_STRINGS = NO;
				SWIFT_VERSION = 5.0;
				TARGETED_DEVICE_FAMILY = "1,2";
				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/LazyPagerExample.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/LazyPagerExample";
			};
			name = Debug;
		};
		D353FB7E2A52174C00C04ABE /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
				BUNDLE_LOADER = "$(TEST_HOST)";
				CODE_SIGN_STYLE = Automatic;
				CURRENT_PROJECT_VERSION = 1;
				DEVELOPMENT_TEAM = BX46265734;
				GENERATE_INFOPLIST_FILE = YES;
				IPHONEOS_DEPLOYMENT_TARGET = 16.2;
				MARKETING_VERSION = 1.0;
				PRODUCT_BUNDLE_IDENTIFIER = dateit.ImageScrollViewTests;
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_EMIT_LOC_STRINGS = NO;
				SWIFT_VERSION = 5.0;
				TARGETED_DEVICE_FAMILY = "1,2";
				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/LazyPagerExample.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/LazyPagerExample";
			};
			name = Release;
		};
		D353FB802A52174C00C04ABE /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
				CODE_SIGN_STYLE = Automatic;
				CURRENT_PROJECT_VERSION = 1;
				DEVELOPMENT_TEAM = BX46265734;
				GENERATE_INFOPLIST_FILE = YES;
				MARKETING_VERSION = 1.0;
				PRODUCT_BUNDLE_IDENTIFIER = dateit.ImageScrollViewUITests;
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_EMIT_LOC_STRINGS = NO;
				SWIFT_VERSION = 5.0;
				TARGETED_DEVICE_FAMILY = "1,2";
				TEST_TARGET_NAME = ImageScrollView;
			};
			name = Debug;
		};
		D353FB812A52174C00C04ABE /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
				CODE_SIGN_STYLE = Automatic;
				CURRENT_PROJECT_VERSION = 1;
				DEVELOPMENT_TEAM = BX46265734;
				GENERATE_INFOPLIST_FILE = YES;
				MARKETING_VERSION = 1.0;
				PRODUCT_BUNDLE_IDENTIFIER = dateit.ImageScrollViewUITests;
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_EMIT_LOC_STRINGS = NO;
				SWIFT_VERSION = 5.0;
				TARGETED_DEVICE_FAMILY = "1,2";
				TEST_TARGET_NAME = ImageScrollView;
			};
			name = Release;
		};
/* End XCBuildConfiguration section */

/* Begin XCConfigurationList section */
		D353FB502A52174B00C04ABE /* Build configuration list for PBXProject "LazyPagerExample" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				D353FB772A52174C00C04ABE /* Debug */,
				D353FB782A52174C00C04ABE /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
		D353FB792A52174C00C04ABE /* Build configuration list for PBXNativeTarget "LazyPagerExample" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				D353FB7A2A52174C00C04ABE /* Debug */,
				D353FB7B2A52174C00C04ABE /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
		D353FB7C2A52174C00C04ABE /* Build configuration list for PBXNativeTarget "LazyPagerExampleTests" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				D353FB7D2A52174C00C04ABE /* Debug */,
				D353FB7E2A52174C00C04ABE /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
		D353FB7F2A52174C00C04ABE /* Build configuration list for PBXNativeTarget "LazyPagerExampleUITests" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				D353FB802A52174C00C04ABE /* Debug */,
				D353FB812A52174C00C04ABE /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
/* End XCConfigurationList section */

/* Begin XCLocalSwiftPackageReference section */
		D38D65E12C62E4B900AA140E /* XCLocalSwiftPackageReference "../" */ = {
			isa = XCLocalSwiftPackageReference;
			relativePath = ../;
		};
/* End XCLocalSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
		D367DA122A59E930004497D4 /* LazyPager */ = {
			isa = XCSwiftPackageProductDependency;
			productName = LazyPager;
		};
		D38D65E22C62E4B900AA140E /* LazyPager */ = {
			isa = XCSwiftPackageProductDependency;
			productName = LazyPager;
		};
/* End XCSwiftPackageProductDependency section */
	};
	rootObject = D353FB4D2A52174B00C04ABE /* Project object */;
}


================================================
FILE: Examples/LazyPagerExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
   version = "1.0">
   <FileRef
      location = "self:/Users/brian/dev/LazyPager/Examples/LazyPagerExample.xcodeproj">
   </FileRef>
</Workspace>


================================================
FILE: Examples/LazyPagerExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>IDEDidComputeMac32BitWarning</key>
	<true/>
</dict>
</plist>


================================================
FILE: Examples/LazyPagerExampleApp/AnimatedPagerControlsExample.swift
================================================
import SwiftUI
import LazyPager


struct AnimatedPagerControlsExample: View {
    
    @State var data = [
        "nora1",
        "nora2",
        "nora3",
        "nora4",
        "nora5",
        "nora6",
    ]
    
    @State var show = false
    @State var index = 0
    
    var body: some View {
        VStack {
            LazyPager(data: data, page: $index) { element in
                Image(element)
                    .resizable()
                    .aspectRatio(contentMode: .fit)
            }
            HStack(spacing: 20) {
                Button("First") {
                    withAnimation {
                        index = 0
                    }
                }
                Button("Prev") {
                    withAnimation {
                        if index > 0 {
                            index -= 1
                        }
                    }
                    
                }
                Button("Next") {
                    withAnimation {
                        if index < data.count {
                            index += 1
                        }
                    }
                    
                }
                Button("Last") {
                    withAnimation {
                        index = data.count - 1
                    }
                }
            }
        }
    }
}

struct AnimatedPagerControlsExample_Previews: PreviewProvider {
    static var previews: some View {
        AnimatedPagerControlsExample()
    }
}


================================================
FILE: Examples/LazyPagerExampleApp/Assets.xcassets/AccentColor.colorset/Contents.json
================================================
{
  "colors" : [
    {
      "idiom" : "universal"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: Examples/LazyPagerExampleApp/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
  "images" : [
    {
      "idiom" : "universal",
      "platform" : "ios",
      "size" : "1024x1024"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: Examples/LazyPagerExampleApp/Assets.xcassets/Contents.json
================================================
{
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: Examples/LazyPagerExampleApp/Assets.xcassets/nora1.imageset/Contents.json
================================================
{
  "images" : [
    {
      "filename" : "356181627_737281678149026_5519646735590788375_n.jpg",
      "idiom" : "universal",
      "scale" : "1x"
    },
    {
      "idiom" : "universal",
      "scale" : "2x"
    },
    {
      "idiom" : "universal",
      "scale" : "3x"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: Examples/LazyPagerExampleApp/Assets.xcassets/nora2.imageset/Contents.json
================================================
{
  "images" : [
    {
      "filename" : "356184881_810974757010572_166165563303848404_n.jpg",
      "idiom" : "universal",
      "scale" : "1x"
    },
    {
      "idiom" : "universal",
      "scale" : "2x"
    },
    {
      "idiom" : "universal",
      "scale" : "3x"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: Examples/LazyPagerExampleApp/Assets.xcassets/nora3.imageset/Contents.json
================================================
{
  "images" : [
    {
      "filename" : "356184996_1504506290292039_6439519590743317419_n.jpg",
      "idiom" : "universal",
      "scale" : "1x"
    },
    {
      "idiom" : "universal",
      "scale" : "2x"
    },
    {
      "idiom" : "universal",
      "scale" : "3x"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: Examples/LazyPagerExampleApp/Assets.xcassets/nora4.imageset/Contents.json
================================================
{
  "images" : [
    {
      "filename" : "356187567_797832131883666_8693445044613773171_n.jpg",
      "idiom" : "universal",
      "scale" : "1x"
    },
    {
      "idiom" : "universal",
      "scale" : "2x"
    },
    {
      "idiom" : "universal",
      "scale" : "3x"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: Examples/LazyPagerExampleApp/Assets.xcassets/nora5.imageset/Contents.json
================================================
{
  "images" : [
    {
      "filename" : "356198313_803291668248047_1588179413198578920_n.jpg",
      "idiom" : "universal",
      "scale" : "1x"
    },
    {
      "idiom" : "universal",
      "scale" : "2x"
    },
    {
      "idiom" : "universal",
      "scale" : "3x"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: Examples/LazyPagerExampleApp/Assets.xcassets/nora6.imageset/Contents.json
================================================
{
  "images" : [
    {
      "filename" : "358743821_933760767702238_5920729387861732707_n.jpg",
      "idiom" : "universal",
      "scale" : "1x"
    },
    {
      "idiom" : "universal",
      "scale" : "2x"
    },
    {
      "idiom" : "universal",
      "scale" : "3x"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: Examples/LazyPagerExampleApp/EnvironmentExample.swift
================================================
//
//  EnvironmentExample.swift
//  LazyPagerExample
//
//  Created by Brian Floersch on 4/26/25.
//

import SwiftUI
import LazyPager

struct SubView: View {
    
    @EnvironmentObject var textHolder: TextHolder
    @Environment(\.customValue) var customValue
    
    var parentText: String
    var body: some View {
        VStack {
            Text("\(textHolder.str) \(parentText)")
                .font(.title)
                .padding()
            Text("Environment value: \(customValue)")
                .font(.subheadline)
                .padding()
        }
    }
}

struct EnvironmentExample: View {
    
    @State var data = [
        "nora1",
        "nora2",
        "nora3",
        "nora4",
        "nora5",
        "nora6",
    ]
    
    @State var show = false

    var body: some View {
        ZStack {
            LazyPager(data: data) { element in
                SubView(parentText: element)
            }
        }
    }
}

class TextHolder: ObservableObject {
    let str: String
    
    init(str: String) {
        self.str = str
    }
}

private struct CustomEnvironmentKey: EnvironmentKey {
    static let defaultValue: String = "default value"
}

extension EnvironmentValues {
    var customValue: String {
        get { self[CustomEnvironmentKey.self] }
        set { self[CustomEnvironmentKey.self] = newValue }
    }
}

#Preview {
    EnvironmentExample()
        .environmentObject(TextHolder(str: "hello world"))
        .environment(\.customValue, "custom environment value")
}


================================================
FILE: Examples/LazyPagerExampleApp/FullTestView.swift
================================================
//
//  ContentView.swift
//  LazyPager
//
//  Created by Brian Floersch on 7/2/23.
//

import SwiftUI
import LazyPager


struct Foo {
    let id = UUID()
    var img: String
    let idx: Int
}

struct FullTestView: View {
    
    var direction: Direction
    @State var data = [
        Foo(img: "nora1", idx: 0),
        Foo(img: "nora2", idx: 1),
        Foo(img: "nora3", idx: 2),
        Foo(img: "nora4", idx: 3),
        Foo(img: "nora5", idx: 4),
        Foo(img: "nora6", idx: 5),
        Foo(img: "nora1", idx: 6),
        Foo(img: "nora2", idx: 7),
        Foo(img: "nora3", idx: 8),
        Foo(img: "nora4", idx: 9),
        Foo(img: "nora5", idx: 10),
        Foo(img: "nora6", idx: 11),
    ]
    
    @Binding var show: Bool
    @State var opacity: CGFloat = 1
    @State var index = 0
    @State var loadPager = false
    
    var body: some View {
        VStack {
            LazyPager(data: data, page: $index, direction: direction) { element in
                ZStack {
                    Image(element.img)
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                    VStack {
                        Text("\(index) \(element.idx) \(data.count - 1)")
                            .foregroundColor(.black)
                            .background(.white)
                    }
                }
            }
            .zoomable(min: 1, max: 5)
            .onDismiss(backgroundOpacity: $opacity) {
                show = false
            }
            .onTap {
                print("tap")
            }
            .onDoubleTap {
                print("double tap")
            }
            .shouldLoadMore(on: .lastElement(minus: 2)) {
                data.append(Foo(img: "nora4", idx: data.count))
            }
            .overscroll { position in
                if position == .beginning {
                    print("Swiped past beginning")
                } else {
                    print("Swiped past end")
                }
            }
            .onDrag {
                print("Drag")
            }
            .pageSpacing(10)
            .background(.black.opacity(opacity))
            .background(ClearFullScreenBackground())
            .ignoresSafeArea()
            VStack {
                HStack(spacing: 30) {
                    Button("-") {
                        index -= 1
                    }
                    VStack(spacing: 10) {
                        Button("append") {
                            data.append(Foo(img: "nora4", idx: data.count + 1))
                        }
                        Button("replace") {
                            data[0] = Foo(img: "nora4", idx: data.count + 1)
                        }
                        Button("update") {
                            data[0].img = "nora5"
                        }
                    }
                    VStack(spacing: 10) {
                        Button("del first") {
                            data.remove(at: 0)
                            index -= 1
                        }
                        Button("del last") {
                            data.remove(at: data.count - 1)
                        }
                        Button("jmp") {
                            index = 10
                        }
                    }
                    Button("+") {
                        index += 1
                    }
                }
                
            }
            .frame(maxWidth: .infinity)
            .background(.white)
        }
    }
}

struct FullTestView_Previews: PreviewProvider {
    static var previews: some View {
        FullTestView(direction: .horizontal, show: .constant(true))
    }
}


================================================
FILE: Examples/LazyPagerExampleApp/InsetTest.swift
================================================
import SwiftUI
import LazyPager


struct InsetTest: View {
    
    @State var data = [
        "nora1",
        "nora2",
        "nora3",
        "nora4",
        "nora5",
        "nora6",
    ]
    
    @State var show = false
    @State var ignoreSafeArea = true
    
    var body: some View {
        NavigationStack {
            ZStack {
                
                if ignoreSafeArea {
                    LazyPager(data: data) { element in
                        Image(element)
                            .resizable()
                            .aspectRatio(contentMode: .fit)
                    }
                    .ignoresSafeArea()
                    
                    VStack {
                    }
                    .frame(width: 300, height: 300)
                    .background(.red)
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                    .ignoresSafeArea()
                } else {
                    LazyPager(data: data) { element in
                        Image(element)
                            .resizable()
                            .aspectRatio(contentMode: .fit)
                    }
                    
                    VStack {
                    }
                    .frame(width: 300, height: 300)
                    .background(.red)
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                }
                
                VStack {
                    Spacer()
                    Toggle("ignore safe area", isOn: $ignoreSafeArea)
                        .padding()
                }
            }
        }
    }
}

#Preview {
    InsetTest()
}


================================================
FILE: Examples/LazyPagerExampleApp/LazyPagerExampleApp.swift
================================================
//
//  LazyPagerApp.swift
//  LazyPager
//
//  Created by Brian Floersch on 7/2/23.
//

import SwiftUI

@main
struct LazyPagerApp: App {
    @State var showFull = false
    
    var body: some Scene {
        WindowGroup {
            NavigationStack {
                VStack(spacing: 20) {
                    NavigationLink(destination: SimpleExample()) {
                        Text("Simple Example")
                    }
                    NavigationLink(destination: InsetTest()) {
                        Text("Inset test")
                    }
                    NavigationLink(destination: EnvironmentExample()
                        .environmentObject(TextHolder(str: "hello world"))
                        .environment(\.customValue, "custom environment value")
                    ) {
                        Text("Environment Example")
                    }
                    NavigationLink(destination: AnimatedPagerControlsExample()) {
                        Text("Animated Pager Controls Example")
                    }
                    Button("full Test View horizontal") {
                        showFull.toggle()
                    }
                    NavigationLink(destination: FullTestView(direction: .vertical, show: .constant(true))) {
                        Text("Full Test View vertical")
                    }
                    NavigationLink(destination: VerticalMediaPager()) {
                        Text("Vertical media pager sample")
                    }
                }
            }
            .fullScreenCover(isPresented: $showFull) {
                FullTestView(direction: .horizontal, show: $showFull)
            }
        }
    }
}


================================================
FILE: Examples/LazyPagerExampleApp/Preview Content/Preview Assets.xcassets/Contents.json
================================================
{
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: Examples/LazyPagerExampleApp/SimpleExample.swift
================================================
import SwiftUI
import LazyPager


struct SimpleExample: View {
    
    @State var data = [
        "nora1",
        "nora2",
        "nora3",
        "nora4",
        "nora5",
        "nora6",
    ]
    
    @State var show = false
    
    var body: some View {
        LazyPager(data: data) { element in
            Image(element)
                .resizable()
                .aspectRatio(contentMode: .fit)
        }
        .onDismiss {
        }
    }
}

struct SimpleExample_Previews: PreviewProvider {
    static var previews: some View {
        SimpleExample()
    }
}


================================================
FILE: Examples/LazyPagerExampleApp/VerticalMediaPager.swift
================================================
import SwiftUI
import LazyPager


struct VerticalMediaPager: View {
    
    @State var data = [
        "nora1",
        "nora2",
        "nora3",
        "nora4",
        "nora5",
        "nora6",
    ]
    @State var show = true
    
    var body: some View {
        ZStack {
            LazyPager(data: data, direction: .vertical) { element in
                Image(element)
                    .resizable()
                    .aspectRatio(contentMode: .fill)
            }
            .overscroll {
                if $0 == .beginning {
                    print("Swiped past beginning")
                } else if $0 == .end {
                    print("Swiped past end")
                }
            }
            .ignoresSafeArea()
            
            VStack(alignment: .leading) {
                HStack {
                    Spacer()
                    VStack(spacing: 30) {
                        Spacer()
                        imgButton("heart.fill")
                        imgButton("text.bubble.fill")
                        imgButton("bookmark.fill")
                        imgButton("arrow.turn.up.right")
                    }
                    .padding(.bottom, 20)
                }
                .padding()
                Spacer()
                HStack {
                    VStack {
                        Text("CatTok")
                            .frame(maxWidth: .infinity, alignment: .leading)
                            .font(.title2)
                        Text("Nora is an adorable cat")
                            .frame(maxWidth: .infinity, alignment: .leading)
                    }
                    Spacer()
                    Text("😸")
                        .font(.title)
                        .padding(5)
                        .background(.pink.opacity(0.8))
                        .clipShape(Circle())
                }
                .padding()
                .foregroundColor(.white)
                .background(.black.opacity(0.5))
            }
        }
    }
    
    @ViewBuilder
    func imgButton(_ name: String) -> some View {
        Image(systemName: name)
            .resizable()
            .aspectRatio(contentMode: .fit)
            .frame(width: 40, height: 40)
            .foregroundColor(.white.opacity(0.9))
    }
}

#Preview {
    VerticalMediaPager()
}


================================================
FILE: Examples/LazyPagerExampleAppTests/LazyPagerExampleAppTests.swift
================================================
//
//  LazyPagerTests.swift
//  LazyPagerTests
//
//  Created by Brian Floersch on 7/2/23.
//

import XCTest
@testable import LazyPager

final class LazyPagerTests: XCTestCase {

    override func setUpWithError() throws {
        // Put setup code here. This method is called before the invocation of each test method in the class.
    }

    override func tearDownWithError() throws {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
    }

    func testExample() throws {
        // This is an example of a functional test case.
        // Use XCTAssert and related functions to verify your tests produce the correct results.
        // Any test you write for XCTest can be annotated as throws and async.
        // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
        // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
    }

    func testPerformanceExample() throws {
        // This is an example of a performance test case.
        self.measure {
            // Put the code you want to measure the time of here.
        }
    }

}


================================================
FILE: Examples/LazyPagerExampleAppUITests/ImageScrollViewUITests.swift
================================================
//
//  LazyPagerUITests.swift
//  LazyPagerUITests
//
//  Created by Brian Floersch on 7/2/23.
//

import XCTest

final class LazyPagerUITests: XCTestCase {

    override func setUpWithError() throws {
        // Put setup code here. This method is called before the invocation of each test method in the class.

        // In UI tests it is usually best to stop immediately when a failure occurs.
        continueAfterFailure = false

        // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
    }

    override func tearDownWithError() throws {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
    }

    func testExample() throws {
        // UI tests must launch the application that they test.
        let app = XCUIApplication()
        app.launch()

        // Use XCTAssert and related functions to verify your tests produce the correct results.
    }

    func testLaunchPerformance() throws {
        if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
            // This measures how long it takes to launch your application.
            measure(metrics: [XCTApplicationLaunchMetric()]) {
                XCUIApplication().launch()
            }
        }
    }
}


================================================
FILE: Examples/LazyPagerExampleAppUITests/ImageScrollViewUITestsLaunchTests.swift
================================================
//
//  LazyPagerUITestsLaunchTests.swift
//  LazyPagerUITests
//
//  Created by Brian Floersch on 7/2/23.
//

import XCTest

final class LazyPagerUITestsLaunchTests: XCTestCase {

    override class var runsForEachTargetApplicationUIConfiguration: Bool {
        true
    }

    override func setUpWithError() throws {
        continueAfterFailure = false
    }

    func testLaunch() throws {
        let app = XCUIApplication()
        app.launch()

        // Insert steps here to perform after app launch but before taking a screenshot,
        // such as logging into a test account or navigating somewhere in the app

        let attachment = XCTAttachment(screenshot: app.screenshot())
        attachment.name = "Launch Screen"
        attachment.lifetime = .keepAlways
        add(attachment)
    }
}


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2022 Brian Floersch

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Package.swift
================================================
// swift-tools-version: 5.7
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "LazyPager",
    platforms: [
           .iOS(.v15)
       ],
    products: [
        // Products define the executables and libraries a package produces, and make them visible to other packages.
        .library(
            name: "LazyPager",
            targets: ["LazyPager"]),
    ],
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        // .package(url: /* package url */, from: "1.0.0"),
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages this package depends on.
        .target(
            name: "LazyPager",
            dependencies: []),
        .testTarget(
            name: "LazyPagerTests",
            dependencies: ["LazyPager"]),
    ]
)


================================================
FILE: README.md
================================================
# LazyPager for SwiftUI

A buttery smooth, lazy loaded, panning, zooming, and gesture dismissible view pager view for SwiftUI. 

The goal of this package is to expose a simple SwiftUI interface for a fluid and seamless content viewer. Unlike other pagers for SwiftUI - this is built on top of UIKit APIs exposing features not yet available in SwiftUI. 

### Horizontal
<p align="center">
  <img src="https://github.com/gh123man/LazyPager/assets/959778/a82da8c3-9d65-4782-8fd7-40cc598e16da" alt="animated" />
</p>

The above example is from [dateit](https://dateit.com/) demonstrating the capabilities of this library. Note: the overlay is custom and can be added by putting `LazyPager` inside a `ZStack`.

### Vertical
<p align="center">
  <img src="https://github.com/user-attachments/assets/21679506-c2ad-491c-8fe8-13fbd2b0aa2a" alt="animated" />
</p>

The above example [can be found in the example project.](https://github.com/gh123man/SwiftUI-LazyPager/blob/master/Examples/LazyPagerExampleApp/VerticalMediaPager.swift)


# Usage

## Add the Swift Package

1. Right click on your project -> `Add Package`
2. In the search bar paste: `https://github.com/gh123man/LazyPager`
3. Click `Add Package`

Or add the package to your `Package.swift` if your project is a Swift package.


## Examples

### Simple Example
A simple image pager that displays images by name from your app assets.

```swift 
@State var data = [ ... ]

var body: some View {
    LazyPager(data: data) { element in
        Image(element)
            .resizable()
            .aspectRatio(contentMode: .fit)
    }
}
```

That's it!

### Detailed Example

```swift 
@State var data = [ ... ]
@State var show = true
@State var opacity: CGFloat = 1 // Dismiss gesture background opacity 
@State var index = 0

var body: some View {
    Button("Open") {
        show.toggle()
    }
    .fullScreenCover(isPresented: $show) {

        // Provide any list of data and bind to an index
        LazyPager(data: data, page: $index) { element in

            // Supports any kind of view - not only images
            Image(element)
                .resizable()
                .aspectRatio(contentMode: .fit)
        }

        // Make the content zoomable
        .zoomable(min: 1, max: 5)

        // Enable the swipe to dismiss gesture and background opacity control
        .onDismiss(backgroundOpacity: $opacity) {
            show = false
        }

        // Handle single tap gestures
        .onTap {
            print("tap")
        }

        // Get notified when to load more content
        .shouldLoadMore {
            data.append("foobar")
        }
        
        // Get notified when swiping past the beginning or end of the list 
        .overscroll { position in
            if position == .beginning {
                print("Swiped past beginning")
            } else {
                print("Swiped past end")
            }
        }

        // Handle double tap gestures
        .onDoubleTap {
            print("double tap")
        }
        
        // Handle drag events initiated by the user
        .onDrag {
            print("Drag")
        }

        // Set the spacing between pages
        .pageSpacing(20)

        // Set the background color with the drag opacity control
        .background(.black.opacity(opacity))

        // A special included modifier to help make fullScreenCover transparent
        .background(ClearFullScreenBackground())
        
        // Works with safe areas or ignored safe areas
        .ignoresSafeArea()
    }
}
```

#### Vertical paging

```swift 
@State var data = [ ... ]

var body: some View {
    LazyPager(data: data, direction: .vertical) { element in
        Image(element)
            .resizable()
            .aspectRatio(contentMode: .fill)
    }
}
```

For a full working example, [open the sample project](https://github.com/gh123man/LazyPager/tree/master/Examples) in the examples folder, or [check out the code here](https://github.com/gh123man/SwiftUI-LazyPager/blob/master/Examples/LazyPagerExampleApp/FullTestView.swift)

# Features

- All content is lazy loaded. By default content is pre-loaded 3 elements ahead and behind the current index. 
- Display any kind of content - not just images! 
- Horizontal or Vertical paging.
- Lazy loaded views are disposed when they are outside of the pre-load frame to conserve resources. 
- Enable zooming and panning with `.zoomable(min: CGFloat, max: CGFloat)`.
- Double tap to zoom is also supported through `.zoomable` modifier.
- Notifies when to load more content with `.shouldLoadMore`.
- Notifies when you swipe past the beginning or end of data with `.overscroll`.
- Animate page transitions by using `withAnimation` when changing the page index. 
- Works with `.ignoresSafeArea()` (or not) to get a true full screen view.
- Drag to dismiss is supported with `.onDismiss` - Supply a binding opacity value to control the background opacity during the transition. 
- Tap events are handled internally, so use `.onTap` to handle single taps (useful for hiding and showing UI).
- Use `.onDoubleTap` to get notified on double taps.
- Use `.settings` to [modify advanced settings](https://github.com/gh123man/SwiftUI-LazyPager/blob/master/Sources/LazyPager/LazyPager.swift#L76).
- Use `.absoluteContentPosition` to subscribe to content position updates (the index + the offset while paging)
- Use `.onZoom` to get notified of the current zoom level
- Use `.onDrag` to handle drag events when the user interacts with the view. No triggered when page is changed programmatically.
- Use `.pageSpacing(CGFloat)` to set the spacing between pages.

# Detailed usage

## Working with `fullScreenCover`

`fullScreenCover` is a good native element for displaying a photo browser, however it has an opaque background by default that is difficult to remove. So `LazyPager` provides a `ClearFullScreenBackground` background view you can use to fix it. Simply add `.background(ClearFullScreenBackground())` to the root element of your `fullScreenCover`. This makes the pull to dismiss gesture seamless. 

## Double tap to zoom
You can customize the double tap behavior using the `zoomable(min: CGFloat, max: CGFloat, doubleTapGesture: DoubleTap)`. By default `doubleTapGesture` is set to `.scale(0.5)` which means "zoom 50% when double tapped". You can change this to a different ratio or set it to `.disabled` to disable the double tap gesture. 

## Dismiss gesture handling 
By default `.onDismiss` will be called after the pull to dismiss gesture is completed. It is often desirable to fade out the background in the process. `LazyPager` uses a fully transparent background by default so you can set your own custom background. NOTE: `.onDismiss` is only supported for `.horizontal` pagers.

To control the dismiss opacity of a custom background, use a `Binding<CGFloat>` like `.onDismiss(backgroundOpacity: $opacity) {` to fade out your custom background.


================================================
FILE: Sources/LazyPager/ClearFullScreenBackground.swift
================================================
//
//  ClearFullScreenBackground.swift
//  
//
//  Created by Brian Floersch on 7/8/23.
//

import Foundation
import SwiftUI
import UIKit

public struct ClearFullScreenBackground: UIViewRepresentable {
    
    public init() { }
    
    public func makeUIView(context: Context) -> UIView {
        let view = UIView()
        DispatchQueue.main.async {
            view.superview?.superview?.backgroundColor = .clear
        }
        return view
    }

    public func updateUIView(_ uiView: UIView, context: Context) {}
}


================================================
FILE: Sources/LazyPager/Collection+Extensions.swift
================================================
//
//  Collection+Extensons.swift
//  
//
//  Created by Brian Floersch on 7/8/23.
//

import Foundation

extension Collection {
    /// Returns the element at the specified index if it is within bounds, otherwise nil.
    subscript (safe index: Index) -> Element? {
        return indices.contains(index) ? self[index] : nil
    }
}



================================================
FILE: Sources/LazyPager/LazyPager.swift
================================================
//
//  LazyPager.swift
//  LazyPager
//
//  Created by Brian Floersch on 7/6/23.
//

import Foundation
import UIKit
import SwiftUI

public enum LoadMore {
    case lastElement(minus: Int = 0)
}

public enum DoubleTap {
    case disabled
    case scale(CGFloat)
}

public enum Direction {
    case horizontal
    case vertical
}

public enum ListPosition {
    case beginning
    case end
}

public enum ZoomConfig {
    case disabled
    case custom(min: CGFloat, max: CGFloat, doubleTap: DoubleTap)
}

public struct Config<Element> {
    /// binding variable to control a custom background opacity. LazyPager is transparent by default
    public var backgroundOpacity: Binding<CGFloat>?
    
    /// Called when the view is done dismissing - dismiss gesture is disabled if nil
    public var dismissCallback: (() -> ())?
    
    /// Called when tapping once
    public var tapCallback: (() -> ())?
    
    /// Called when tapping twice
    public var doubleTapCallback: (() -> ())?
    
    /// Called when dragging begins
    public var dragCallback: (() -> ())?
    
    /// The offset used to trigger load loadMoreCallback
    public var loadMoreOn: LoadMore = .lastElement(minus: 3)
    
    /// Called when more content should be loaded
    public var loadMoreCallback: (() -> ())?
    
    /// Direction of the pager
    public var direction : Direction = .horizontal
    
    /// Called whent the end of data is reached and the user tries to swipe again
    public var overscrollCallback: ((ListPosition) -> ())?
    
    /// The element index + the offset while paging
    public var absoluteContentPosition: Binding<CGFloat>?
    
    /// Called every view update to get the zoom config
    public var zoomConfigGetter: (Element) -> ZoomConfig = { _ in .disabled }
    
    /// Called while zooming to provide the current zoom level for an element
    public var onZoomHandler: ((Element, CGFloat) -> ())?

    /// The spacing between pages. Defaults to 0.
    public var pageSpacing: CGFloat = 0

    /// Advanced settings (only accessible via .settings)
    
    /// How may out of view pages to load in advance (forward and backwards)
    public var preloadAmount: Int = 3
    
    /// Minimum swipe velocity needed to trigger a dismiss
    public var dismissVelocity: CGFloat = 1.3
    
    /// the minimum % (between 0 and 1) you need to drag to trigger a dismiss
    public var dismissTriggerOffset: CGFloat = 0.1
    
    /// How long to animate the dismiss once done dragging
    public var dismissAnimationLength: CGFloat = 0.2
    
    /// Cancel SwiftUI animations. Default to true because the dismiss gesture is already animated.
    /// Stacking animations can cause undesirable behavior
    public var shouldCancelSwiftUIAnimationsOnDismiss = true
    
    /// At what drag % (between 0 and 1) the background should be fully transparent
    public var fullFadeOnDragAt: CGFloat = 0.2
    
    /// The minimum scroll distance the in which the pinch gesture is enabled
    public var pinchGestureEnableOffset: Double = 10
    
    /// % ammount (from 0-1) of overscroll needed to call overscrollCallback
    public var overscrollThreshold: Double = 0.15
}

public struct LazyPager<Element, DataCollecton: RandomAccessCollection, Content: View> where DataCollecton.Index == Int, DataCollecton.Element == Element {
    private var viewLoader: (Element) -> Content
    private var data: DataCollecton
    
    @State private var defaultPageInternal = 0
    private var providedPage: Binding<Int>?
    
    private var page: Binding<Int> {
        providedPage ?? Binding(
            get: { defaultPageInternal },
            set: { defaultPageInternal = $0 }
        )
    }
    
    var config = Config<Element>()
    
    public init(data: DataCollecton,
                page: Binding<Int>? = nil,
                direction: Direction = .horizontal,
                @ViewBuilder content: @escaping (Element) -> Content)  {
        self.data = data
        self.providedPage = page
        self.viewLoader = content
        self.config.direction = direction
    }
}

public extension LazyPager {
    func onDismiss(backgroundOpacity: Binding<CGFloat>? = nil, _ callback: @escaping () -> ()) -> LazyPager {
        guard config.direction == .horizontal else {
            return self
        }
        var this = self
        this.config.backgroundOpacity = backgroundOpacity
        this.config.dismissCallback = callback
        return this
    }
    
    func onTap(_ callback: @escaping () -> ()) -> LazyPager {
        var this = self
        this.config.tapCallback = callback
        return this
    }
    
    func onDoubleTap(_ callback: @escaping () -> ()) -> LazyPager {
        var this = self
        this.config.doubleTapCallback = callback
        return this
    }
    
    func onDrag(_ callback: @escaping () -> ()) -> LazyPager {
        var this = self
        this.config.dragCallback = callback
        return this
    }
    
    func pageSpacing(_ spacing: CGFloat) -> LazyPager {
        var this = self
        this.config.pageSpacing = spacing
        return this
    }
    
    func shouldLoadMore(on: LoadMore = .lastElement(minus: 3), _ callback: @escaping () -> ()) -> LazyPager {
        var this = self
        this.config.loadMoreOn = on
        this.config.loadMoreCallback = callback
        return this
    }
    
    func zoomable(min: CGFloat, max: CGFloat, doubleTapGesture: DoubleTap = .scale(0.5)) -> LazyPager {
        var this = self
        this.config.zoomConfigGetter = { _ in
            return .custom(min: min, max: max, doubleTap: doubleTapGesture)
        }
        return this
    }
    
    func zoomable(onElement: @escaping (Element) -> ZoomConfig) -> LazyPager {
        var this = self
        this.config.zoomConfigGetter = onElement
        return this
    }
    
    func settings(_ adjust: @escaping (inout Config<Element>) -> ()) -> LazyPager {
        var this = self
        adjust(&this.config)
        return this
    }
    
    func overscroll(_ callback: @escaping (ListPosition) -> ()) -> LazyPager {
        var this = self
        this.config.overscrollCallback = callback
        return this
    }
    
    func absoluteContentPosition(_ absoluteContentPosition: Binding<CGFloat>? = nil) -> LazyPager {
        guard config.direction == .horizontal else {
            return self
        }
        var this = self
        this.config.absoluteContentPosition = absoluteContentPosition
        return this
    }
    
    func onZoom(_ onZoomHandler: @escaping (Element, CGFloat) -> ()) -> LazyPager {
        var this = self
        this.config.onZoomHandler = onZoomHandler
        return this
    }
}

extension LazyPager: UIViewControllerRepresentable {
    public func makeUIViewController(context: Context) -> ViewDataProvider<Content, DataCollecton, Element> {
        let provider = ViewDataProvider(data: data,
                                        page: page,
                                        config: config,
                                        viewLoader: viewLoader)
        DispatchQueue.main.async {
            provider.goToPage(page.wrappedValue, animated: false)
        }
        return provider
    }
    
    public func updateUIViewController(_ uiViewController: ViewDataProvider<Content, DataCollecton, Element>, context: Context) {
        uiViewController.viewLoader = viewLoader
        uiViewController.data = data
        defer { uiViewController.reloadViews() }
        if page.wrappedValue != uiViewController.pagerView.currentIndex {
            // Index was explicitly updated
            uiViewController.goToPage(page.wrappedValue, animated: context.transaction.animation != nil)
        }
        
        if page.wrappedValue >= data.count {
            uiViewController.goToPage(data.count - 1, animated: false)
        } else if page.wrappedValue < 0 {
            uiViewController.goToPage(0, animated: false)
        }
    }
}


================================================
FILE: Sources/LazyPager/Math.swift
================================================
//
//  Math.swift
//  
//
//  Created by Brian Floersch on 7/8/23.
//

import Foundation

func lerp(from: CGFloat, to: CGFloat, by: CGFloat) -> CGFloat {
    return from * (1 - by) + to * by
}

func normalize(from min: CGFloat, at val: CGFloat, to max: CGFloat) -> CGFloat {
    let v = (val - min) / (max - min)
    return v < 0 ? 0 : v > 1 ? 1 : v
}



================================================
FILE: Sources/LazyPager/PagerView.swift
================================================
//
//  PagerView.swift
//  
//
//  Created by Brian Floersch on 7/8/23.
//

import Foundation
import UIKit
import SwiftUI

protocol ViewLoader: AnyObject {
    
    associatedtype Element
    associatedtype Content: View
    
    var dataCount: Int { get }
    
    func loadView(at: Int) -> ZoomableView<Element, Content>?
    func updateHostedView(for zoomableView: ZoomableView<Element, Content>)
}

class PagerView<Element, Loader: ViewLoader, Content: View>: UIScrollView, UIScrollViewDelegate where Loader.Element == Element, Loader.Content == Content {
    
    
    var pageSpacing: CGFloat {
        config.pageSpacing
    }
    var isFirstLoad = false
    var loadedViews = [ZoomableView<Element, Content>]()
    var config: Config<Element>
    weak var viewLoader: Loader?
    var lastBoundsSize: CGSize?
    
    var isRotating = false
    var page: Binding<Int>
    
    var currentIndex: Int = 0 {
        didSet {
            loadMoreIfNeeded()
        }
    }
    
    var absoluteOffset: CGFloat {
        var absoluteOffset: CGFloat
        if config.direction == .horizontal {
            absoluteOffset = self.contentOffset.x / self.pageWidth
        } else {
            absoluteOffset = self.contentOffset.y / self.pageHeight
        }
        return absoluteOffset
    }
    
    var relativeIndex: Int {
        if absoluteOffset.isInfinite || absoluteOffset.isNaN {
            return 0
        }
        var idx = Int(round(absoluteOffset))
        idx = idx < 0 ? 0 : idx
        idx = idx >= loadedViews.count ? loadedViews.count-1 : idx
        return idx
    }
    
    var currentView: ZoomableView<Element, Content> {
        loadedViews[relativeIndex]
    }
    
    var pageWidth: CGFloat {
        if config.direction == .horizontal {
            return frame.width + pageSpacing
        }
        return frame.width
    }
    
    var pageHeight: CGFloat {
        if config.direction == .vertical {
            return frame.height + pageSpacing
        }
        return frame.height
    }
    
    init(page: Binding<Int>, config: Config<Element>) {
        self.currentIndex = page.wrappedValue
        self.page = page
        self.config = config
        super.init(frame: .zero)
        
        showsVerticalScrollIndicator = false
        showsHorizontalScrollIndicator = false
        backgroundColor = .clear
        decelerationRate = .fast
        delegate = self
        contentInsetAdjustmentBehavior = .always
        // DEBUG
//        backgroundColor = .blue
    }
    
    
    required init?(coder: NSCoder) {
        fatalError("Not implemented")
    }
    
    public override func layoutSubviews() {
        super.layoutSubviews()
        if !isFirstLoad {
            ensureCurrentPage(animated: false)
            isFirstLoad = true
        } else if isRotating {
            ensureCurrentPage(animated: false)
        }
    
        // Ensures insets are updated when the screen rotates
        if bounds.size != lastBoundsSize {
            updateInsets(animated: lastBoundsSize != nil)
            lastBoundsSize = bounds.size
        }
    }
    
    func computeViewState(immediate: Bool = false) {
        delegate = nil
        DispatchQueue.main.async {
            self.delegate = self
        }
        
        if subviews.isEmpty {
            for i in currentIndex...(currentIndex + config.preloadAmount) {
                if immediate {
                    appendView(at: i)
                } else {
                    scheduleAppend(at: i)
                }
            }
            for i in ((currentIndex - config.preloadAmount)..<currentIndex).reversed() {
                if immediate {
                    schedulePrepend(at: i)
                } else {
                    prependView(at: i)
                }
            }
        }
        
        if let lastView = loadedViews.last {
            let diff = lastView.index - currentIndex
            if diff < (config.preloadAmount) {
                for i in lastView.index..<(lastView.index + (config.preloadAmount - diff)) {
                    if immediate {
                        appendView(at: i + 1)
                    } else {
                        scheduleAppend(at: i + 1)
                    }
                }
            }
        }
        
        if let firstView = loadedViews.first {
            let diff = currentIndex - firstView.index
            if diff < (config.preloadAmount) {
                for i in (firstView.index - (config.preloadAmount - diff)..<firstView.index).reversed() {
                    if immediate {
                        schedulePrepend(at: i)
                    } else {
                        prependView(at: i)
                    }
                }
            }
        }
        self.removeOutOfFrameViews()
        updateInsets(animated: false)
        
        // Debug
//         print(self.loadedViews.map { $0.index })
    }
    
    func updateInsets(animated: Bool) {
        let update = {
            self.contentInset = UIEdgeInsets(top: -self.safeAreaInsets.top,
                                             left: -self.safeAreaInsets.left,
                                             bottom: -self.safeAreaInsets.bottom,
                                             right: -self.safeAreaInsets.right)
        }
        if !animated {
            update()
        } else {
            UIView.animate(withDuration: 0.3) {
                update()
            }
        }
    }
    
    
    func addSubview(_ zoomView: ZoomableView<Element, Content>) {
        super.addSubview(zoomView)
        NSLayoutConstraint.activate([
            zoomView.widthAnchor.constraint(equalTo: frameLayoutGuide.widthAnchor),
            zoomView.heightAnchor.constraint(equalTo: frameLayoutGuide.heightAnchor),
        ])
    }
    
    func addFirstView(_ zoomView: ZoomableView<Element, Content>) {
        if config.direction == .horizontal {
            zoomView.leadingConstraint = zoomView.leadingAnchor.constraint(equalTo: leadingAnchor)
            zoomView.trailingConstraint = zoomView.trailingAnchor.constraint(equalTo: trailingAnchor)
            zoomView.leadingConstraint?.isActive = true
            zoomView.trailingConstraint?.isActive = true
        } else {
            zoomView.topConstraint = zoomView.topAnchor.constraint(equalTo: topAnchor)
            zoomView.bottomConstraint = zoomView.bottomAnchor.constraint(equalTo: bottomAnchor)
            zoomView.topConstraint?.isActive = true
            zoomView.bottomConstraint?.isActive = true
        }
    }
    
    func scheduleAppend(at index: Int) {
        DispatchQueue.main.async { [weak self] in
            guard let self = self else { return }
            // Ensure we are not trying to add a view that has already been loaded
            if self.loadedViews.contains(where: { $0.index == index }) {
                return
            }
            self.appendView(at: index)
        }
    }
    
    func appendView(at index: Int) {
        guard let zoomView = viewLoader?.loadView(at: index) else { return }
        
        addSubview(zoomView)
        
        if let lastView = loadedViews.last {
            if config.direction == .horizontal {
                lastView.trailingConstraint?.isActive = false
                lastView.trailingConstraint = nil
                
                zoomView.leadingConstraint = zoomView.leadingAnchor.constraint(equalTo: lastView.trailingAnchor, constant: pageSpacing)
                zoomView.trailingConstraint = zoomView.trailingAnchor.constraint(equalTo: trailingAnchor)
                zoomView.leadingConstraint?.isActive = true
                zoomView.trailingConstraint?.isActive = true
            } else {
                lastView.bottomConstraint?.isActive = false
                lastView.bottomConstraint = nil
                
                zoomView.topConstraint = zoomView.topAnchor.constraint(equalTo: lastView.bottomAnchor, constant: pageSpacing)
                zoomView.bottomConstraint = zoomView.bottomAnchor.constraint(equalTo: bottomAnchor)
                zoomView.topConstraint?.isActive = true
                zoomView.bottomConstraint?.isActive = true
            }
            
        } else {
            addFirstView(zoomView)
        }
        loadedViews.append(zoomView)
    }
    
    func schedulePrepend(at index: Int) {
        DispatchQueue.main.async { [weak self] in
            guard let self = self else { return }
            // Ensure we are not trying to add a view that has already been loaded
            if self.loadedViews.contains(where: { $0.index == index }) {
                return
            }
            self.prependView(at: index)
        }
    }
    
    func prependView(at index: Int) {
        guard let zoomView = viewLoader?.loadView(at: index) else { return }
        
        addSubview(zoomView)
        
        if let firstView = loadedViews.first {
            if config.direction == .horizontal {
                firstView.leadingConstraint?.isActive = false
                firstView.leadingConstraint = nil
                
                zoomView.leadingConstraint = zoomView.leadingAnchor.constraint(equalTo: leadingAnchor)
                zoomView.trailingConstraint = zoomView.trailingAnchor.constraint(equalTo: firstView.leadingAnchor, constant: -pageSpacing)
                zoomView.leadingConstraint?.isActive = true
                zoomView.trailingConstraint?.isActive = true
            } else {
                firstView.topConstraint?.isActive = false
                firstView.topConstraint = nil
                
                zoomView.topConstraint = zoomView.topAnchor.constraint(equalTo: topAnchor)
                zoomView.bottomConstraint = zoomView.bottomAnchor.constraint(equalTo: firstView.topAnchor, constant: -pageSpacing)
                zoomView.topConstraint?.isActive = true
                zoomView.bottomConstraint?.isActive = true
            }
            
        } else {
            addFirstView(zoomView)
        }
        
        loadedViews.insert(zoomView, at: 0)
        if config.direction == .horizontal {
            contentOffset.x += pageWidth
        } else {
            contentOffset.y += pageHeight
        }
    }
    
    func reloadViews() {
        for view in loadedViews {
            viewLoader?.updateHostedView(for: view)
        }
    }
    
    func remove(view: ZoomableView<Element, Content>) {
        guard let viewIndex = loadedViews.firstIndex(where: { $0.index == view.index }) else { return }
        
        let viewToDisconnect = loadedViews[viewIndex]
        let prevView: ZoomableView<Element, Content>? = loadedViews[safe: viewIndex - 1]
        let nextView: ZoomableView<Element, Content>? = loadedViews[safe: viewIndex + 1]
        
        let removedIndex = viewToDisconnect.index
        
        viewToDisconnect.removeFromSuperview()
        loadedViews.remove(at: viewIndex)
        
        if config.direction == .horizontal {
            if let prevView = prevView, let nextView = nextView {
                // Both exist, removing from the middle
                prevView.trailingConstraint?.isActive = false
                prevView.trailingConstraint = prevView.trailingAnchor.constraint(equalTo: nextView.leadingAnchor, constant: -pageSpacing)
                prevView.trailingConstraint?.isActive = true
            } else if let prevView = prevView {
                // This was the last view
                prevView.trailingConstraint?.isActive = false
                prevView.trailingConstraint = prevView.trailingAnchor.constraint(equalTo: trailingAnchor)
                prevView.trailingConstraint?.isActive = true
            } else if let nextView = nextView {
                // This was the first view
                nextView.leadingConstraint?.isActive = false
                nextView.leadingConstraint = nextView.leadingAnchor.constraint(equalTo: leadingAnchor)
                nextView.leadingConstraint?.isActive = true
            }
            
            if removedIndex < (loadedViews.first?.index ?? 0) {
                contentOffset.x -= pageWidth
            }
        } else {
            if let prevView = prevView, let nextView = nextView {
                // Both exist, removing from the middle
                prevView.bottomConstraint?.isActive = false
                prevView.bottomConstraint = prevView.bottomAnchor.constraint(equalTo: nextView.topAnchor, constant: -pageSpacing)
                prevView.bottomConstraint?.isActive = true
            } else if let prevView = prevView {
                // This was the last view
                prevView.bottomConstraint?.isActive = false
                prevView.bottomConstraint = prevView.bottomAnchor.constraint(equalTo: bottomAnchor)
                prevView.bottomConstraint?.isActive = true
            } else if let nextView = nextView {
                // This was the first view
                nextView.topConstraint?.isActive = false
                nextView.topConstraint = nextView.topAnchor.constraint(equalTo: topAnchor)
                nextView.topConstraint?.isActive = true
            }
            
            if removedIndex < (loadedViews.first?.index ?? 0) {
                contentOffset.y -= pageHeight
            }
        }
    }
    
    
    func removeOutOfFrameViews() {
        guard let viewLoader = viewLoader else { return }
        
        for view in loadedViews {
            if abs(currentIndex - view.index) > config.preloadAmount || view.index >= viewLoader.dataCount {
                remove(view: view)
            }
        }
    }
    
    func resizeOutOfBoundsViews() {
        for v in loadedViews {
            if v.index != currentIndex {
                v.zoomScale = 1
            }
        }
    }
    
    func goToPage(_ page: Int, animated: Bool) {
        currentIndex = page
        DispatchQueue.main.async {
            self.computeViewState(immediate: true)
            self.ensureCurrentPage(animated: animated)
        }
    }
    
    func ensureCurrentPage(animated: Bool) {
        guard let index = loadedViews.firstIndex(where: { $0.index == currentIndex }) else { return }
        if config.direction == .horizontal {
            setContentOffset(CGPoint(x: CGFloat(index) * pageWidth, y: contentOffset.y), animated: animated)
        } else {
            setContentOffset(CGPoint(x: contentOffset.x, y: CGFloat(index) * pageHeight), animated: animated)
        }
        self.currentView.dismissEnabled = true
    }
    
    func loadMoreIfNeeded() {
        guard let loadMoreCallback = config.loadMoreCallback else { return }
        guard case let .lastElement(offset) = config.loadMoreOn else { return }
        guard let viewLoader = viewLoader else { return }
        
        if currentIndex + offset >= viewLoader.dataCount - 1 {
            DispatchQueue.main.async {
                loadMoreCallback()
            }
        }
    }
    
    func scrollingFinished() {
        let newIndex = currentView.index
        
        if currentIndex != newIndex {
            currentIndex = newIndex
            page.wrappedValue = newIndex
        }
        
        computeViewState()
        
        hasNotfiedOverscroll = false
        resizeOutOfBoundsViews()
        if loadedViews.isEmpty { return }
        currentView.dismissEnabled = true
    }
    
    // MARK: UISCrollVieDelegate methods
    
    var lastPos: CGFloat = 0
    var hasNotfiedOverscroll = false
    
    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
        config.dragCallback?()
    }

    public func scrollViewDidScroll(_ scrollView: UIScrollView) {
        
        if !scrollView.isTracking, !isRotating, (currentView.index != page.wrappedValue || page.wrappedValue != currentIndex ) {
            currentIndex = currentView.index
            page.wrappedValue = currentIndex
        }
        
        if let index = loadedViews[safe: relativeIndex]?.index {
            config.absoluteContentPosition?.wrappedValue = CGFloat(index) + absoluteOffset - CGFloat(relativeIndex)
        }
        
        if !hasNotfiedOverscroll {
            if relativeIndex >= loadedViews.count-1, absoluteOffset - CGFloat(relativeIndex) > config.overscrollThreshold {
                config.overscrollCallback?(.end)
                hasNotfiedOverscroll = true
            }
            
            if relativeIndex <= 0, absoluteOffset - CGFloat(relativeIndex) < -config.overscrollThreshold {
                config.overscrollCallback?(.beginning)
                hasNotfiedOverscroll = true
            }
        }
        
        if loadedViews.isEmpty { return }
        self.currentView.dismissEnabled = false
    }
    
    public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
        
        let targetPage: CGFloat
        let velocityThreshold: CGFloat = 0.5 // a value to tune
        
        if config.direction == .horizontal {
            let currentRelativePage = scrollView.contentOffset.x / pageWidth
            if velocity.x > velocityThreshold {
                // Swiped forward
                targetPage = floor(currentRelativePage + 1)
            } else if velocity.x < -velocityThreshold {
                // Swiped backward
                targetPage = ceil(currentRelativePage - 1)
            } else {
                // No strong swipe, snap to nearest
                targetPage = round(currentRelativePage)
            }
            targetContentOffset.pointee.x = targetPage * pageWidth
        } else {
            let currentRelativePage = scrollView.contentOffset.y / pageHeight
            if velocity.y > velocityThreshold {
                targetPage = floor(currentRelativePage + 1)
            } else if velocity.y < -velocityThreshold {
                targetPage = ceil(currentRelativePage - 1)
            } else {
                targetPage = round(currentRelativePage)
            }
            targetContentOffset.pointee.y = targetPage * pageHeight
        }
    }
    
    public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        if !decelerate {
            scrollingFinished()
        }
    }
    
    public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        scrollingFinished()
    }
}


================================================
FILE: Sources/LazyPager/ViewDataProvider.swift
================================================
//
//  ViewDataProvider.swift
//  
//
//  Created by Brian Floersch on 7/8/23.
//

import Foundation
import SwiftUI
import UIKit

public class ViewDataProvider<Content: View, DataCollecton: RandomAccessCollection, Element>: UIViewController, ViewLoader where DataCollecton.Index == Int, DataCollecton.Element == Element {
    
    var viewLoader: (Element) -> Content
    var data: DataCollecton
    var config: Config<Element>
    var pagerView: PagerView<Element, ViewDataProvider, Content>
    
    var dataCount: Int {
        return data.count
    }
    
    init(data: DataCollecton,
         page: Binding<Int>,
         config: Config<Element>,
         viewLoader: @escaping (Element) -> Content) {
        
        self.data = data
        self.viewLoader = viewLoader
        self.config = config
        self.pagerView = PagerView(page: page, config: config)

        super.init(nibName: nil, bundle: nil)
        self.pagerView.viewLoader = self
        
        pagerView.computeViewState()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func goToPage(_ page: Int, animated: Bool) {
        pagerView.goToPage(page, animated: animated)
    }
    
    func reloadViews() {
        pagerView.reloadViews()
        pagerView.computeViewState()
    }

    // MARK: ViewLoader
    
    func loadView(at index: Int) -> ZoomableView<Element, Content>? {
        guard let dta = data[safe: index] else { return nil }
        
        let hostingController = UIHostingController(rootView: viewLoader(dta))
        return ZoomableView(hostingController: hostingController, index: index, data: dta, config: config)
    }
    
    func updateHostedView(for zoomableView: ZoomableView<Element, Content>) {
        guard let dta = data[safe: zoomableView.index] else { return }
        
        zoomableView.hostingController.rootView = viewLoader(dta)
    }
    
    // MARK: UIViewController
    
    public override func loadView() {
        self.view = pagerView
    }
    
    public override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        
        pagerView.isRotating = true
        self.pagerView.scrollingFinished()
        coordinator.animate(alongsideTransition: { context in }, completion: { context in
            self.pagerView.isRotating = false
            DispatchQueue.main.async {
                self.pagerView.goToPage(self.pagerView.currentIndex, animated: false)
            }
        })
    }
}



================================================
FILE: Sources/LazyPager/ZoomableView.swift
================================================
//
//  ZoomableView.swift
//  LazyPager
//
//  Created by Brian Floersch on 7/4/23.
//

import Foundation
import UIKit
import SwiftUI

class ZoomableView<Element, Content: View>: UIScrollView, UIScrollViewDelegate, UIGestureRecognizerDelegate {
    
    var trailingConstraint: NSLayoutConstraint?
    var leadingConstraint: NSLayoutConstraint?
    var topConstraint: NSLayoutConstraint?
    var bottomConstraint: NSLayoutConstraint?
    var contentTopToContent: NSLayoutConstraint!
    var contentTopToFrame: NSLayoutConstraint!
    var contentBottomToFrame: NSLayoutConstraint!
    var contentBottomToView: NSLayoutConstraint!
    
    var config: Config<Element>
    var bottomView: UIView
    
    var allowScroll: Bool = true {
        didSet {
            if allowScroll, config.direction == .horizontal {
                contentTopToFrame.isActive = false
                contentBottomToFrame.isActive = false
                bottomView.isHidden = false
                
                contentTopToContent.isActive = true
                contentBottomToView.isActive = true
            } else {
                contentTopToContent.isActive = false
                contentBottomToView.isActive = false
                
                contentTopToFrame.isActive = true
                contentBottomToFrame.isActive = true
                bottomView.isHidden = true
            }
        }
    }
    
    var wasTracking = false
    var isZoomHappening = false
    var dismissEnabled = false // Contorlled by PagerView to prevent flicker
    var lastInset: CGFloat = 0
    var currentZoomInsetAnimation: UIViewPropertyAnimator?
    
    var hostingController: UIHostingController<Content>
    var index: Int
    var data: Element
    var doubleTap: DoubleTap?
    var lastBoundsSize: CGSize?
    
    var view: UIView {
        return hostingController.view
    }
    
    init(hostingController: UIHostingController<Content>, index: Int, data: Element, config: Config<Element>) {
        self.index = index
        self.hostingController = hostingController
        self.data = data
        self.config = config
        
        let v = UIView()
        self.bottomView = v
        
        super.init(frame: .zero)
        
        translatesAutoresizingMaskIntoConstraints = false
        delegate = self
        panGestureRecognizer.delegate = self
        
        updateZoomConfig()
        
        bouncesZoom = true
        backgroundColor = .clear
        alwaysBounceVertical = false
        contentInsetAdjustmentBehavior = .never
        if config.dismissCallback != nil {
            alwaysBounceVertical = true
        }
        showsVerticalScrollIndicator = false
        showsHorizontalScrollIndicator = false
        
        view.translatesAutoresizingMaskIntoConstraints = false
        view.backgroundColor = .clear
        decelerationRate = .fast
        // DEBUG
//        backgroundColor = .red
        addSubview(view)
        
        NSLayoutConstraint.activate([
            view.leadingAnchor.constraint(equalTo: leadingAnchor),
            view.trailingAnchor.constraint(equalTo: trailingAnchor),
            view.widthAnchor.constraint(equalTo: frameLayoutGuide.widthAnchor),
            view.heightAnchor.constraint(equalTo: frameLayoutGuide.heightAnchor),
        ])
        
        contentTopToFrame = view.topAnchor.constraint(equalTo: contentLayoutGuide.topAnchor)
        contentTopToContent = view.topAnchor.constraint(equalTo: topAnchor)
        contentBottomToFrame = view.bottomAnchor.constraint(equalTo: contentLayoutGuide.bottomAnchor)
        contentBottomToView = view.bottomAnchor.constraint(equalTo: bottomView.topAnchor)
        
        v.translatesAutoresizingMaskIntoConstraints = false
        addSubview(v)

//        This is for future support of a drawer view
        let constant: CGFloat = config.dismissCallback == nil ? 0 : 1

        NSLayoutConstraint.activate([
          v.bottomAnchor.constraint(equalTo: bottomAnchor),
          v.leadingAnchor.constraint(equalTo: frameLayoutGuide.leadingAnchor),
          v.trailingAnchor.constraint(equalTo: frameLayoutGuide.trailingAnchor),
          v.heightAnchor.constraint(equalToConstant: constant)
        ])
        
        var singleTapGesture: UITapGestureRecognizer?
        if config.tapCallback != nil {
            let gesture = UITapGestureRecognizer(target: self, action: #selector(singleTap(_:)))
            gesture.numberOfTapsRequired = 1
            gesture.numberOfTouchesRequired = 1
            addGestureRecognizer(gesture)
            singleTapGesture = gesture
        }
                
        func setupDoubleTapGesture() {
            let doubleTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(onDoubleTap(_:)))
            doubleTapRecognizer.numberOfTapsRequired = 2
            doubleTapRecognizer.numberOfTouchesRequired = 1
            addGestureRecognizer(doubleTapRecognizer)            
            singleTapGesture?.require(toFail: doubleTapRecognizer)
        }
        
        if case .scale = doubleTap {
            setupDoubleTapGesture()
        } else if config.doubleTapCallback != nil {
            setupDoubleTapGesture()
        }
        
        DispatchQueue.main.async {
            self.updateState()
        }
    }
    
    required init?(coder: NSCoder) {
        fatalError("Not implemented")
    }
    
    func updateZoomConfig() {
        switch config.zoomConfigGetter(data) {
        case .disabled:
            maximumZoomScale = 1
            minimumZoomScale = 1
            doubleTap = nil
        case let .custom(min, max, doubleTap):
            minimumZoomScale = min
            maximumZoomScale = max
            self.doubleTap = doubleTap
        }
    }
    
    @objc func singleTap(_ recognizer: UITapGestureRecognizer) {
        config.tapCallback?()
    }
    
    @objc func onDoubleTap(_ recognizer: UITapGestureRecognizer) {
        config.doubleTapCallback?()
        
        if case let .scale(scale) = doubleTap {
            let pointInView = recognizer.location(in: view)
            zoom(at: pointInView, scale: scale)
            updateInsets()
        }
    }
    
    func updateState() {
        updateZoomConfig()
        allowScroll = zoomScale == 1

        if contentOffset.y > config.pinchGestureEnableOffset, allowScroll {
            pinchGestureRecognizer?.isEnabled = false
        } else {
            pinchGestureRecognizer?.isEnabled = true
        }
        
        if allowScroll {
            if dismissEnabled, config.dismissCallback != nil {
                let offset = contentOffset.y
                if offset < 0 {
                    let absoluteDragOffset = normalize(from: 0, at: abs(offset), to: frame.size.height)
                    let fadeOffset = normalize(from: 0, at: absoluteDragOffset, to: config.fullFadeOnDragAt)
                    config.backgroundOpacity?.wrappedValue = 1 - fadeOffset
                } else {
                    DispatchQueue.main.async {
                        self.config.backgroundOpacity?.wrappedValue = 1
                    }
                }
            }
            wasTracking = isTracking
        }
    }
    
    func zoom(at point: CGPoint, scale: CGFloat) {
        let mid = lerp(from: minimumZoomScale, to: maximumZoomScale, by: scale)
        let newZoomScale = zoomScale == minimumZoomScale ? mid : minimumZoomScale
        let size = bounds.size
        let w = size.width / newZoomScale
        let h = size.height / newZoomScale
        let x = point.x - (w * 0.5)
        let y = point.y - (h * 0.5)
        zoom(to: CGRect(x: x, y: y, width: w, height: h), animated: true)
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        // Ensures insets are updated when the screen rotates
        if bounds.size != lastBoundsSize {
            lastBoundsSize = bounds.size
            updateInsets()
        }
    }
    
    // MARK: UIScrollViewDelegate methods
    
    func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
        isZoomHappening = true
        updateState()
    }
    
    func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
        isZoomHappening = false
        updateState()
        updateInsets()
    }
    
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        updateState()
    }
    
    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        return view
    }
    
    func updateInsets() {
        let w: CGFloat = view.intrinsicContentSize.width * UIScreen.main.scale
        let h: CGFloat = view.intrinsicContentSize.height * UIScreen.main.scale
        
        let ratioW = view.frame.width / w
        let ratioH = view.frame.height / h
        
        let ratio = ratioW < ratioH ? ratioW : ratioH
        
        let newWidth = w*ratio
        let newHeight = h*ratio
        
        let left = 0.5 * (newWidth * zoomScale > view.frame.width
                          ? (newWidth - view.frame.width)
                          : (frame.width - view.frame.width))
        let top = 0.5 * (newHeight * zoomScale > view.frame.height
                         ? (newHeight - view.frame.height)
                         : (frame.height - view.frame.height))
        
        if zoomScale <= maximumZoomScale {
            let targetInsets = UIEdgeInsets(
                top: top,
                left: left,
                bottom: top,
                right: left
            )
            
            UIView.animate(withDuration: 0.3) {
                self.contentInset = targetInsets
            }
        }
    }
    
    func scrollViewDidZoom(_ scrollView: UIScrollView) {

        let scrollViewSize = scrollView.bounds.size
        let zoomViewSize = view.frame.size

        let horizontalInset = max(0, (scrollViewSize.width - zoomViewSize.width) / 2)
        let verticalInset = max(0, (scrollViewSize.height - zoomViewSize.height) / 2)

        scrollView.contentInset = UIEdgeInsets(top: verticalInset, left: horizontalInset, bottom: verticalInset, right: horizontalInset)
        config.onZoomHandler?(data, scrollView.zoomScale)
    }
    
    
    func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
        
        let percentage = contentOffset.y / (contentSize.height - bounds.size.height)
        
        if wasTracking,
           percentage < -config.dismissTriggerOffset,
           !isZoomHappening,
           velocity.y < -config.dismissVelocity,
           config.dismissCallback != nil {
            
            dismissEnabled = false // prevent touch interaction from messing with animation of opacity.
            let ogFram = frame.origin
            
            withAnimation(.linear(duration: self.config.dismissAnimationLength)) {
                self.config.backgroundOpacity?.wrappedValue = 0
            }
            
            frame.origin.y = -contentOffset.y
            
            UIView.animate(withDuration: self.config.dismissAnimationLength, animations: {
                self.frame.origin = CGPoint(x: ogFram.x, y: self.frame.size.height)
            }) { _ in
                if self.config.shouldCancelSwiftUIAnimationsOnDismiss {
                    var transaction = Transaction()
                    transaction.disablesAnimations = true
                    withTransaction(transaction) {
                        self.config.dismissCallback?()
                    }
                } else {
                    self.config.dismissCallback?()
                }
            }
        }
    }
    
    // MARK: UIGestureRecognizerDelegate
    
    override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        // We only want to intercept our own pan gesture.
        guard gestureRecognizer == self.panGestureRecognizer else {
            return true
        }

        let panGesture = self.panGestureRecognizer
        let velocity = panGesture.velocity(in: self)

        // This logic is for the horizontal pager.
        if config.direction == .horizontal {
            // If the swipe is mostly vertical, it's for dismissal. Let it happen.
            if abs(velocity.y) > abs(velocity.x) {
                return true
            }

            // It's a horizontal swipe. Should we let our own pan gesture begin?

            // If not zoomed, NO. The pager should handle all horizontal movement.
            if zoomScale <= minimumZoomScale {
                return false // Prevent our pan, let PagerView handle it.
            }

            // If we ARE zoomed, check if we're at the horizontal edges.
            let maxOffsetX = contentSize.width - bounds.width + contentInset.right
            let minOffsetX = -contentInset.left
            
            let isAtRightEdge = contentOffset.x >= maxOffsetX - 1.0
            let isAtLeftEdge = contentOffset.x <= minOffsetX + 1.0

            // At the right edge and trying to swipe left (to the next page).
            if isAtRightEdge && velocity.x < 0 {
                return false // Prevent our pan, let PagerView handle it.
            }

            // At the left edge and trying to swipe right (to the previous page).
            if isAtLeftEdge && velocity.x > 0 {
                return false // Prevent our pan, let PagerView handle it.
            }
        } else { // Vertical Pager
            // If the swipe is mostly horizontal, let it happen.
            if abs(velocity.x) > abs(velocity.y) {
                return true
            }

            // It's a vertical swipe. Should we let our own pan gesture begin?

            // If not zoomed, NO. The pager should handle all vertical movement.
            if zoomScale <= minimumZoomScale {
                return false // Prevent our pan, let PagerView handle it.
            }

            // If we ARE zoomed, check if we're at the vertical edges.
            let maxOffsetY = contentSize.height - bounds.height + contentInset.bottom
            let minOffsetY = -contentInset.top

            let isAtBottomEdge = contentOffset.y >= maxOffsetY - 1.0
            let isAtTopEdge = contentOffset.y <= minOffsetY + 1.0

            // At the bottom edge and trying to swipe up (to the next page).
            if isAtBottomEdge && velocity.y < 0 {
                return false // Prevent our pan, let PagerView handle it.
            }

            // At the top edge and trying to swipe down (to the previous page).
            if isAtTopEdge && velocity.y > 0 {
                return false // Prevent our pan, let PagerView handle it.
            }
        }
        
        // If we're not at an edge while zoomed, our pan gesture should begin.
        return true
    }

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        // We no longer need simultaneous recognition. This simplifies the logic and
        // prevents the differential panning issues.
        return false
    }
}


================================================
FILE: Tests/LazyPagerTests/LazyPagerTests.swift
================================================
import XCTest
@testable import LazyPager

final class LazyPagerTests: XCTestCase {
    func testExample() throws {
        // This is an example of a functional test case.
        // Use XCTAssert and related functions to verify your tests produce the correct
        // results.
//        XCTAssertEqual(LazyPager().text, "Hello, World!")
    }
}
Download .txt
gitextract_8knwvjye/

├── .github/
│   └── ISSUE_TEMPLATE/
│       └── bug_report.md
├── .gitignore
├── Examples/
│   ├── LazyPagerExample.xcodeproj/
│   │   ├── project.pbxproj
│   │   └── project.xcworkspace/
│   │       ├── contents.xcworkspacedata
│   │       └── xcshareddata/
│   │           └── IDEWorkspaceChecks.plist
│   ├── LazyPagerExampleApp/
│   │   ├── AnimatedPagerControlsExample.swift
│   │   ├── Assets.xcassets/
│   │   │   ├── AccentColor.colorset/
│   │   │   │   └── Contents.json
│   │   │   ├── AppIcon.appiconset/
│   │   │   │   └── Contents.json
│   │   │   ├── Contents.json
│   │   │   ├── nora1.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── nora2.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── nora3.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── nora4.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── nora5.imageset/
│   │   │   │   └── Contents.json
│   │   │   └── nora6.imageset/
│   │   │       └── Contents.json
│   │   ├── EnvironmentExample.swift
│   │   ├── FullTestView.swift
│   │   ├── InsetTest.swift
│   │   ├── LazyPagerExampleApp.swift
│   │   ├── Preview Content/
│   │   │   └── Preview Assets.xcassets/
│   │   │       └── Contents.json
│   │   ├── SimpleExample.swift
│   │   └── VerticalMediaPager.swift
│   ├── LazyPagerExampleAppTests/
│   │   └── LazyPagerExampleAppTests.swift
│   └── LazyPagerExampleAppUITests/
│       ├── ImageScrollViewUITests.swift
│       └── ImageScrollViewUITestsLaunchTests.swift
├── LICENSE
├── Package.swift
├── README.md
├── Sources/
│   └── LazyPager/
│       ├── ClearFullScreenBackground.swift
│       ├── Collection+Extensions.swift
│       ├── LazyPager.swift
│       ├── Math.swift
│       ├── PagerView.swift
│       ├── ViewDataProvider.swift
│       └── ZoomableView.swift
└── Tests/
    └── LazyPagerTests/
        └── LazyPagerTests.swift
Condensed preview — 36 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (111K chars).
[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 544,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the b"
  },
  {
    "path": ".gitignore",
    "chars": 165,
    "preview": ".DS_Store\n/.build\n/Packages\n/*.xcodeproj\nxcuserdata/\nDerivedData/\n.swiftpm/config/registries.json\n.swiftpm/xcode/package"
  },
  {
    "path": "Examples/LazyPagerExample.xcodeproj/project.pbxproj",
    "chars": 27203,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 60;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "Examples/LazyPagerExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "chars": 197,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:/Users/brian/de"
  },
  {
    "path": "Examples/LazyPagerExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "chars": 238,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Examples/LazyPagerExampleApp/AnimatedPagerControlsExample.swift",
    "chars": 1504,
    "preview": "import SwiftUI\nimport LazyPager\n\n\nstruct AnimatedPagerControlsExample: View {\n    \n    @State var data = [\n        \"nora"
  },
  {
    "path": "Examples/LazyPagerExampleApp/Assets.xcassets/AccentColor.colorset/Contents.json",
    "chars": 123,
    "preview": "{\n  \"colors\" : [\n    {\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }"
  },
  {
    "path": "Examples/LazyPagerExampleApp/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 177,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"platform\" : \"ios\",\n      \"size\" : \"1024x1024\"\n    }\n  ],\n  \"i"
  },
  {
    "path": "Examples/LazyPagerExampleApp/Assets.xcassets/Contents.json",
    "chars": 63,
    "preview": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Examples/LazyPagerExampleApp/Assets.xcassets/nora1.imageset/Contents.json",
    "chars": 345,
    "preview": "{\n  \"images\" : [\n    {\n      \"filename\" : \"356181627_737281678149026_5519646735590788375_n.jpg\",\n      \"idiom\" : \"univer"
  },
  {
    "path": "Examples/LazyPagerExampleApp/Assets.xcassets/nora2.imageset/Contents.json",
    "chars": 344,
    "preview": "{\n  \"images\" : [\n    {\n      \"filename\" : \"356184881_810974757010572_166165563303848404_n.jpg\",\n      \"idiom\" : \"univers"
  },
  {
    "path": "Examples/LazyPagerExampleApp/Assets.xcassets/nora3.imageset/Contents.json",
    "chars": 346,
    "preview": "{\n  \"images\" : [\n    {\n      \"filename\" : \"356184996_1504506290292039_6439519590743317419_n.jpg\",\n      \"idiom\" : \"unive"
  },
  {
    "path": "Examples/LazyPagerExampleApp/Assets.xcassets/nora4.imageset/Contents.json",
    "chars": 345,
    "preview": "{\n  \"images\" : [\n    {\n      \"filename\" : \"356187567_797832131883666_8693445044613773171_n.jpg\",\n      \"idiom\" : \"univer"
  },
  {
    "path": "Examples/LazyPagerExampleApp/Assets.xcassets/nora5.imageset/Contents.json",
    "chars": 345,
    "preview": "{\n  \"images\" : [\n    {\n      \"filename\" : \"356198313_803291668248047_1588179413198578920_n.jpg\",\n      \"idiom\" : \"univer"
  },
  {
    "path": "Examples/LazyPagerExampleApp/Assets.xcassets/nora6.imageset/Contents.json",
    "chars": 345,
    "preview": "{\n  \"images\" : [\n    {\n      \"filename\" : \"358743821_933760767702238_5920729387861732707_n.jpg\",\n      \"idiom\" : \"univer"
  },
  {
    "path": "Examples/LazyPagerExampleApp/EnvironmentExample.swift",
    "chars": 1520,
    "preview": "//\n//  EnvironmentExample.swift\n//  LazyPagerExample\n//\n//  Created by Brian Floersch on 4/26/25.\n//\n\nimport SwiftUI\nimp"
  },
  {
    "path": "Examples/LazyPagerExampleApp/FullTestView.swift",
    "chars": 3735,
    "preview": "//\n//  ContentView.swift\n//  LazyPager\n//\n//  Created by Brian Floersch on 7/2/23.\n//\n\nimport SwiftUI\nimport LazyPager\n\n"
  },
  {
    "path": "Examples/LazyPagerExampleApp/InsetTest.swift",
    "chars": 1672,
    "preview": "import SwiftUI\nimport LazyPager\n\n\nstruct InsetTest: View {\n    \n    @State var data = [\n        \"nora1\",\n        \"nora2\""
  },
  {
    "path": "Examples/LazyPagerExampleApp/LazyPagerExampleApp.swift",
    "chars": 1697,
    "preview": "//\n//  LazyPagerApp.swift\n//  LazyPager\n//\n//  Created by Brian Floersch on 7/2/23.\n//\n\nimport SwiftUI\n\n@main\nstruct Laz"
  },
  {
    "path": "Examples/LazyPagerExampleApp/Preview Content/Preview Assets.xcassets/Contents.json",
    "chars": 63,
    "preview": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Examples/LazyPagerExampleApp/SimpleExample.swift",
    "chars": 579,
    "preview": "import SwiftUI\nimport LazyPager\n\n\nstruct SimpleExample: View {\n    \n    @State var data = [\n        \"nora1\",\n        \"no"
  },
  {
    "path": "Examples/LazyPagerExampleApp/VerticalMediaPager.swift",
    "chars": 2349,
    "preview": "import SwiftUI\nimport LazyPager\n\n\nstruct VerticalMediaPager: View {\n    \n    @State var data = [\n        \"nora1\",\n      "
  },
  {
    "path": "Examples/LazyPagerExampleAppTests/LazyPagerExampleAppTests.swift",
    "chars": 1229,
    "preview": "//\n//  LazyPagerTests.swift\n//  LazyPagerTests\n//\n//  Created by Brian Floersch on 7/2/23.\n//\n\nimport XCTest\n@testable i"
  },
  {
    "path": "Examples/LazyPagerExampleAppUITests/ImageScrollViewUITests.swift",
    "chars": 1379,
    "preview": "//\n//  LazyPagerUITests.swift\n//  LazyPagerUITests\n//\n//  Created by Brian Floersch on 7/2/23.\n//\n\nimport XCTest\n\nfinal "
  },
  {
    "path": "Examples/LazyPagerExampleAppUITests/ImageScrollViewUITestsLaunchTests.swift",
    "chars": 809,
    "preview": "//\n//  LazyPagerUITestsLaunchTests.swift\n//  LazyPagerUITests\n//\n//  Created by Brian Floersch on 7/2/23.\n//\n\nimport XCT"
  },
  {
    "path": "LICENSE",
    "chars": 1071,
    "preview": "MIT License\n\nCopyright (c) 2022 Brian Floersch\n\nPermission is hereby granted, free of charge, to any person obtaining a "
  },
  {
    "path": "Package.swift",
    "chars": 1066,
    "preview": "// swift-tools-version: 5.7\n// The swift-tools-version declares the minimum version of Swift required to build this pack"
  },
  {
    "path": "README.md",
    "chars": 6943,
    "preview": "# LazyPager for SwiftUI\n\nA buttery smooth, lazy loaded, panning, zooming, and gesture dismissible view pager view for Sw"
  },
  {
    "path": "Sources/LazyPager/ClearFullScreenBackground.swift",
    "chars": 525,
    "preview": "//\n//  ClearFullScreenBackground.swift\n//  \n//\n//  Created by Brian Floersch on 7/8/23.\n//\n\nimport Foundation\nimport Swi"
  },
  {
    "path": "Sources/LazyPager/Collection+Extensions.swift",
    "chars": 335,
    "preview": "//\n//  Collection+Extensons.swift\n//  \n//\n//  Created by Brian Floersch on 7/8/23.\n//\n\nimport Foundation\n\nextension Coll"
  },
  {
    "path": "Sources/LazyPager/LazyPager.swift",
    "chars": 7971,
    "preview": "//\n//  LazyPager.swift\n//  LazyPager\n//\n//  Created by Brian Floersch on 7/6/23.\n//\n\nimport Foundation\nimport UIKit\nimpo"
  },
  {
    "path": "Sources/LazyPager/Math.swift",
    "chars": 353,
    "preview": "//\n//  Math.swift\n//  \n//\n//  Created by Brian Floersch on 7/8/23.\n//\n\nimport Foundation\n\nfunc lerp(from: CGFloat, to: C"
  },
  {
    "path": "Sources/LazyPager/PagerView.swift",
    "chars": 18465,
    "preview": "//\n//  PagerView.swift\n//  \n//\n//  Created by Brian Floersch on 7/8/23.\n//\n\nimport Foundation\nimport UIKit\nimport SwiftU"
  },
  {
    "path": "Sources/LazyPager/ViewDataProvider.swift",
    "chars": 2619,
    "preview": "//\n//  ViewDataProvider.swift\n//  \n//\n//  Created by Brian Floersch on 7/8/23.\n//\n\nimport Foundation\nimport SwiftUI\nimpo"
  },
  {
    "path": "Sources/LazyPager/ZoomableView.swift",
    "chars": 15217,
    "preview": "//\n//  ZoomableView.swift\n//  LazyPager\n//\n//  Created by Brian Floersch on 7/4/23.\n//\n\nimport Foundation\nimport UIKit\ni"
  },
  {
    "path": "Tests/LazyPagerTests/LazyPagerTests.swift",
    "chars": 348,
    "preview": "import XCTest\n@testable import LazyPager\n\nfinal class LazyPagerTests: XCTestCase {\n    func testExample() throws {\n     "
  }
]

About this extraction

This page contains the full source code of the gh123man/SwiftUI-LazyPager GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 36 files (99.8 KB), approximately 26.2k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!