diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..05f1487
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,31 @@
+name: CI
+
+on:
+ # Trigger the workflow on push or pull request, only for the master branch
+ push:
+ branches:
+ - master
+ pull_request:
+ branches:
+ - master
+
+jobs:
+ SwiftLint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v1
+ - name: SwiftLint Action
+ uses: norio-nomura/action-swiftlint@3.2.1
+
+ Test:
+ runs-on: macOS-latest
+ steps:
+ - uses: actions/checkout@v1
+ - name: List available Xcode versions
+ run: ls /Applications | grep Xcode
+ - name: Select Xcode
+ run: sudo xcode-select -switch /Applications/Xcode_14.1.app && /usr/bin/xcodebuild -version
+ - name: Run unit tests
+ run: xcodebuild test -scheme CollectionViewPagingLayout -project CollectionViewPagingLayout.xcodeproj -destination 'platform=iOS Simulator,name=iPhone 14,OS=16.1' | xcpretty && exit ${PIPESTATUS[0]}
+
+
diff --git a/.swiftlint.yml b/.swiftlint.yml
index a8c67ad..9221a3b 100644
--- a/.swiftlint.yml
+++ b/.swiftlint.yml
@@ -38,6 +38,9 @@ excluded:
- Package.swift
- Pods
- PagingLayoutSamples/SampleProject.bundle/SampleProject
+ - Samples/Pods
+ - Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject
+ - CollectionViewPagingLayoutTests
# adjusting rules
@@ -58,12 +61,6 @@ file_header:
\/\/ Copyright © \d{4} Amir Khorsandi. All rights reserved\.
\/\/
-type_name:
- min_length: 3
- max_length:
- warning: 50
- error: 65
- excluded: id # excluded via string
reporter: "xcode"
cyclomatic_complexity:
diff --git a/CollectionViewPagingLayout.podspec b/CollectionViewPagingLayout.podspec
index 3097b06..f081bb6 100644
--- a/CollectionViewPagingLayout.podspec
+++ b/CollectionViewPagingLayout.podspec
@@ -1,21 +1,23 @@
-
Pod::Spec.new do |s|
- s.name = "CollectionViewPagingLayout"
- s.version = "0.3.0"
- s.summary = "Simple layout for making paging effects with UICollectionView."
-
- s.description = <<-DESC
- A custom UICollectionViewLayout for making paging effects with custom transforms
+ s.name = "CollectionViewPagingLayout"
+ s.version = "1.1.0"
+ s.summary = "A simple but highly customizable layout for UICollectionView and SwiftUI."
+
+ s.description = <<-DESC
+ A simple but highly customizable UICollectionViewLayout for UICollectionView.
+ Simple SwiftUI views that let you make page-view effects.
DESC
- s.homepage = "https://github.com/amirdew/CollectionViewPagingLayout"
- s.license = { :type => "MIT", :file => "LICENSE" }
- s.author = { "Amir Khorsandi" => "khorsandi@me.com" }
- s.source = { :git => "https://github.com/amirdew/CollectionViewPagingLayout.git", :tag => "#{s.version}" }
+ s.homepage = "https://github.com/amirdew/CollectionViewPagingLayout"
+ s.license = { :type => "MIT", :file => "LICENSE" }
+ s.author = { "Amir Khorsandi" => "khorsandi@me.com" }
+ s.source = { :git => "https://github.com/amirdew/CollectionViewPagingLayout.git", :tag => "#{s.version}" }
+ s.source_files = ["Lib/**/*.swift"]
- s.swift_versions = ["5.1"]
- s.ios.deployment_target = "8.0"
+ s.swift_versions = ["5.5"]
- s.source_files = "Lib/**/*.swift"
+ s.ios.deployment_target = "13.0"
+ s.frameworks = "UIKit"
+ s.weak_frameworks = "SwiftUI", "Combine"
end
diff --git a/CollectionViewPagingLayout.xcodeproj/project.pbxproj b/CollectionViewPagingLayout.xcodeproj/project.pbxproj
index cce7571..10010f4 100644
--- a/CollectionViewPagingLayout.xcodeproj/project.pbxproj
+++ b/CollectionViewPagingLayout.xcodeproj/project.pbxproj
@@ -7,6 +7,18 @@
objects = {
/* Begin PBXBuildFile section */
+ 291EC0E32610B32500C65A34 /* PagingCollectionViewControllerBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291EC0DB2610B32500C65A34 /* PagingCollectionViewControllerBuilder.swift */; };
+ 291EC0E42610B32500C65A34 /* StackPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291EC0DC2610B32500C65A34 /* StackPageView.swift */; };
+ 291EC0E52610B32500C65A34 /* ScalePageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291EC0DD2610B32500C65A34 /* ScalePageView.swift */; };
+ 291EC0E62610B32500C65A34 /* TransformPageViewProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291EC0DE2610B32500C65A34 /* TransformPageViewProtocol.swift */; };
+ 291EC0E72610B32500C65A34 /* PagingCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291EC0DF2610B32500C65A34 /* PagingCollectionViewCell.swift */; };
+ 291EC0E82610B32500C65A34 /* TransformPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291EC0E02610B32500C65A34 /* TransformPageView.swift */; };
+ 291EC0E92610B32500C65A34 /* PagingCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291EC0E12610B32500C65A34 /* PagingCollectionViewController.swift */; };
+ 291EC0EA2610B32500C65A34 /* SnapshotPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291EC0E22610B32500C65A34 /* SnapshotPageView.swift */; };
+ 291FDEC6262327FD00AD1C14 /* CollectionViewPagingLayoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291FDEC5262327FD00AD1C14 /* CollectionViewPagingLayoutTests.swift */; };
+ 291FDEC8262327FD00AD1C14 /* CollectionViewPagingLayout.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 298DBBF62441C94900341D8E /* CollectionViewPagingLayout.framework */; };
+ 2967EBA226230A320035540A /* PagePadding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2967EBA126230A320035540A /* PagePadding.swift */; };
+ 2967EBA526230A570035540A /* PagingCollectionViewModifierData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2967EBA426230A570035540A /* PagingCollectionViewModifierData.swift */; };
298DBBFB2441C94900341D8E /* CollectionViewPagingLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 298DBBF92441C94900341D8E /* CollectionViewPagingLayout.h */; settings = {ATTRIBUTES = (Public, ); }; };
298DBC242441C98E00341D8E /* BlurEffectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298DBC022441C98E00341D8E /* BlurEffectView.swift */; };
298DBC252441C98E00341D8E /* SnapshotTransformViewOptions.PiecesValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298DBC042441C98E00341D8E /* SnapshotTransformViewOptions.PiecesValue.swift */; };
@@ -27,9 +39,37 @@
298DBC342441C98E00341D8E /* StackTransformView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298DBC1A2441C98E00341D8E /* StackTransformView.swift */; };
298DBC352441C98E00341D8E /* StackTransformViewOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298DBC1B2441C98E00341D8E /* StackTransformViewOptions.swift */; };
298DBC382441C98E00341D8E /* TransformableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298DBC232441C98E00341D8E /* TransformableView.swift */; };
+ 29CD47C626235E6F00376CC8 /* SnapshotTransformViewOptions+Layout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29CD47C526235E6F00376CC8 /* SnapshotTransformViewOptions+Layout.swift */; };
+ 29CD47CA26235E7800376CC8 /* StackTransformViewOptions+Layout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29CD47C926235E7800376CC8 /* StackTransformViewOptions+Layout.swift */; };
+ 29CD47CE26235E8A00376CC8 /* ScaleTransformViewOptions+Layout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29CD47CD26235E8A00376CC8 /* ScaleTransformViewOptions+Layout.swift */; };
+ 29DF4686261DF863007E0FA4 /* ViewAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29DF4684261DF863007E0FA4 /* ViewAnimator.swift */; };
+ 29DF4687261DF863007E0FA4 /* CollectionViewPagingLayout.ZPositionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29DF4685261DF863007E0FA4 /* CollectionViewPagingLayout.ZPositionHandler.swift */; };
/* End PBXBuildFile section */
+/* Begin PBXContainerItemProxy section */
+ 291FDEC9262327FD00AD1C14 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 298DBBED2441C94900341D8E /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 298DBBF52441C94900341D8E;
+ remoteInfo = CollectionViewPagingLayout;
+ };
+/* End PBXContainerItemProxy section */
+
/* Begin PBXFileReference section */
+ 291EC0DB2610B32500C65A34 /* PagingCollectionViewControllerBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PagingCollectionViewControllerBuilder.swift; sourceTree = ""; };
+ 291EC0DC2610B32500C65A34 /* StackPageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackPageView.swift; sourceTree = ""; };
+ 291EC0DD2610B32500C65A34 /* ScalePageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScalePageView.swift; sourceTree = ""; };
+ 291EC0DE2610B32500C65A34 /* TransformPageViewProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformPageViewProtocol.swift; sourceTree = ""; };
+ 291EC0DF2610B32500C65A34 /* PagingCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PagingCollectionViewCell.swift; sourceTree = ""; };
+ 291EC0E02610B32500C65A34 /* TransformPageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformPageView.swift; sourceTree = ""; };
+ 291EC0E12610B32500C65A34 /* PagingCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PagingCollectionViewController.swift; sourceTree = ""; };
+ 291EC0E22610B32500C65A34 /* SnapshotPageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnapshotPageView.swift; sourceTree = ""; };
+ 291FDEC3262327FD00AD1C14 /* CollectionViewPagingLayoutTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CollectionViewPagingLayoutTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 291FDEC5262327FD00AD1C14 /* CollectionViewPagingLayoutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewPagingLayoutTests.swift; sourceTree = ""; };
+ 291FDEC7262327FD00AD1C14 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 2967EBA126230A320035540A /* PagePadding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PagePadding.swift; sourceTree = ""; };
+ 2967EBA426230A570035540A /* PagingCollectionViewModifierData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PagingCollectionViewModifierData.swift; sourceTree = ""; };
298DBBF62441C94900341D8E /* CollectionViewPagingLayout.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CollectionViewPagingLayout.framework; sourceTree = BUILT_PRODUCTS_DIR; };
298DBBF92441C94900341D8E /* CollectionViewPagingLayout.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CollectionViewPagingLayout.h; sourceTree = ""; };
298DBBFA2441C94900341D8E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
@@ -52,9 +92,22 @@
298DBC1A2441C98E00341D8E /* StackTransformView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackTransformView.swift; sourceTree = ""; };
298DBC1B2441C98E00341D8E /* StackTransformViewOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackTransformViewOptions.swift; sourceTree = ""; };
298DBC232441C98E00341D8E /* TransformableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformableView.swift; sourceTree = ""; };
+ 29CD47C526235E6F00376CC8 /* SnapshotTransformViewOptions+Layout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SnapshotTransformViewOptions+Layout.swift"; sourceTree = ""; };
+ 29CD47C926235E7800376CC8 /* StackTransformViewOptions+Layout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "StackTransformViewOptions+Layout.swift"; sourceTree = ""; };
+ 29CD47CD26235E8A00376CC8 /* ScaleTransformViewOptions+Layout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ScaleTransformViewOptions+Layout.swift"; sourceTree = ""; };
+ 29DF4684261DF863007E0FA4 /* ViewAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewAnimator.swift; sourceTree = ""; };
+ 29DF4685261DF863007E0FA4 /* CollectionViewPagingLayout.ZPositionHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewPagingLayout.ZPositionHandler.swift; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
+ 291FDEC0262327FD00AD1C14 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 291FDEC8262327FD00AD1C14 /* CollectionViewPagingLayout.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
298DBBF32441C94900341D8E /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -65,10 +118,37 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 291EC0DA2610B32500C65A34 /* SwiftUI */ = {
+ isa = PBXGroup;
+ children = (
+ 291EC0E22610B32500C65A34 /* SnapshotPageView.swift */,
+ 2967EBA126230A320035540A /* PagePadding.swift */,
+ 291EC0DB2610B32500C65A34 /* PagingCollectionViewControllerBuilder.swift */,
+ 291EC0DC2610B32500C65A34 /* StackPageView.swift */,
+ 291EC0DD2610B32500C65A34 /* ScalePageView.swift */,
+ 2967EBA426230A570035540A /* PagingCollectionViewModifierData.swift */,
+ 291EC0DE2610B32500C65A34 /* TransformPageViewProtocol.swift */,
+ 291EC0DF2610B32500C65A34 /* PagingCollectionViewCell.swift */,
+ 291EC0E02610B32500C65A34 /* TransformPageView.swift */,
+ 291EC0E12610B32500C65A34 /* PagingCollectionViewController.swift */,
+ );
+ path = SwiftUI;
+ sourceTree = "";
+ };
+ 291FDEC4262327FD00AD1C14 /* CollectionViewPagingLayoutTests */ = {
+ isa = PBXGroup;
+ children = (
+ 291FDEC5262327FD00AD1C14 /* CollectionViewPagingLayoutTests.swift */,
+ 291FDEC7262327FD00AD1C14 /* Info.plist */,
+ );
+ path = CollectionViewPagingLayoutTests;
+ sourceTree = "";
+ };
298DBBEC2441C94900341D8E = {
isa = PBXGroup;
children = (
298DBBF82441C94900341D8E /* CollectionViewPagingLayout */,
+ 291FDEC4262327FD00AD1C14 /* CollectionViewPagingLayoutTests */,
298DBBF72441C94900341D8E /* Products */,
);
sourceTree = "";
@@ -77,6 +157,7 @@
isa = PBXGroup;
children = (
298DBBF62441C94900341D8E /* CollectionViewPagingLayout.framework */,
+ 291FDEC3262327FD00AD1C14 /* CollectionViewPagingLayoutTests.xctest */,
);
name = Products;
sourceTree = "";
@@ -95,14 +176,16 @@
isa = PBXGroup;
children = (
298DBC022441C98E00341D8E /* BlurEffectView.swift */,
- 298DBC032441C98E00341D8E /* Snapshot */,
+ 298DBC142441C98E00341D8E /* CollectionViewPagingLayout.swift */,
+ 298DBC232441C98E00341D8E /* TransformableView.swift */,
298DBC092441C98E00341D8E /* TransformCurve.swift */,
+ 29DF4685261DF863007E0FA4 /* CollectionViewPagingLayout.ZPositionHandler.swift */,
+ 29DF4684261DF863007E0FA4 /* ViewAnimator.swift */,
298DBC0A2441C98E00341D8E /* Scale */,
- 298DBC0F2441C98E00341D8E /* Utilities */,
- 298DBC142441C98E00341D8E /* CollectionViewPagingLayout.swift */,
- 298DBC152441C98E00341D8E /* Sources */,
+ 298DBC032441C98E00341D8E /* Snapshot */,
298DBC192441C98E00341D8E /* Stack */,
- 298DBC232441C98E00341D8E /* TransformableView.swift */,
+ 291EC0DA2610B32500C65A34 /* SwiftUI */,
+ 298DBC0F2441C98E00341D8E /* Utilities */,
);
path = Lib;
sourceTree = SOURCE_ROOT;
@@ -115,6 +198,7 @@
298DBC062441C98E00341D8E /* SnapshotTransformViewOptions.PiecePosition.swift */,
298DBC072441C98E00341D8E /* SnapshotContainerView.swift */,
298DBC082441C98E00341D8E /* SnapshotTransformViewOptions.swift */,
+ 29CD47C526235E6F00376CC8 /* SnapshotTransformViewOptions+Layout.swift */,
);
path = Snapshot;
sourceTree = "";
@@ -126,6 +210,7 @@
298DBC0C2441C98E00341D8E /* ScaleTransformView.swift */,
298DBC0D2441C98E00341D8E /* ScaleTransformViewOptions.Rotation3dOptions.swift */,
298DBC0E2441C98E00341D8E /* ScaleTransformViewOptions.Translation3dOptions.swift */,
+ 29CD47CD26235E8A00376CC8 /* ScaleTransformViewOptions+Layout.swift */,
);
path = Scale;
sourceTree = "";
@@ -141,42 +226,12 @@
path = Utilities;
sourceTree = "";
};
- 298DBC152441C98E00341D8E /* Sources */ = {
- isa = PBXGroup;
- children = (
- 298DBC162441C98E00341D8E /* Tests */,
- 298DBC182441C98E00341D8E /* Stack */,
- );
- path = Sources;
- sourceTree = "";
- };
- 298DBC162441C98E00341D8E /* Tests */ = {
- isa = PBXGroup;
- children = (
- 298DBC172441C98E00341D8E /* LibTests */,
- );
- path = Tests;
- sourceTree = "";
- };
- 298DBC172441C98E00341D8E /* LibTests */ = {
- isa = PBXGroup;
- children = (
- );
- path = LibTests;
- sourceTree = "";
- };
- 298DBC182441C98E00341D8E /* Stack */ = {
- isa = PBXGroup;
- children = (
- );
- path = Stack;
- sourceTree = "";
- };
298DBC192441C98E00341D8E /* Stack */ = {
isa = PBXGroup;
children = (
298DBC1A2441C98E00341D8E /* StackTransformView.swift */,
298DBC1B2441C98E00341D8E /* StackTransformViewOptions.swift */,
+ 29CD47C926235E7800376CC8 /* StackTransformViewOptions+Layout.swift */,
);
path = Stack;
sourceTree = "";
@@ -195,15 +250,33 @@
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
+ 291FDEC2262327FD00AD1C14 /* CollectionViewPagingLayoutTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 291FDECD262327FD00AD1C14 /* Build configuration list for PBXNativeTarget "CollectionViewPagingLayoutTests" */;
+ buildPhases = (
+ 291FDEBF262327FD00AD1C14 /* Sources */,
+ 291FDEC0262327FD00AD1C14 /* Frameworks */,
+ 291FDEC1262327FD00AD1C14 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 291FDECA262327FD00AD1C14 /* PBXTargetDependency */,
+ );
+ name = CollectionViewPagingLayoutTests;
+ productName = CollectionViewPagingLayoutTests;
+ productReference = 291FDEC3262327FD00AD1C14 /* CollectionViewPagingLayoutTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
298DBBF52441C94900341D8E /* CollectionViewPagingLayout */ = {
isa = PBXNativeTarget;
buildConfigurationList = 298DBBFE2441C94900341D8E /* Build configuration list for PBXNativeTarget "CollectionViewPagingLayout" */;
buildPhases = (
+ 2967003224420B4100A1F508 /* Swiftlint */,
298DBBF12441C94900341D8E /* Headers */,
298DBBF22441C94900341D8E /* Sources */,
298DBBF32441C94900341D8E /* Frameworks */,
298DBBF42441C94900341D8E /* Resources */,
- 2967003224420B4100A1F508 /* Swiftlint */,
);
buildRules = (
);
@@ -220,9 +293,13 @@
298DBBED2441C94900341D8E /* Project object */ = {
isa = PBXProject;
attributes = {
- LastUpgradeCheck = 1130;
+ LastSwiftUpdateCheck = 1240;
+ LastUpgradeCheck = 1330;
ORGANIZATIONNAME = Amir;
TargetAttributes = {
+ 291FDEC2262327FD00AD1C14 = {
+ CreatedOnToolsVersion = 12.4;
+ };
298DBBF52441C94900341D8E = {
CreatedOnToolsVersion = 11.3.1;
};
@@ -242,11 +319,19 @@
projectRoot = "";
targets = (
298DBBF52441C94900341D8E /* CollectionViewPagingLayout */,
+ 291FDEC2262327FD00AD1C14 /* CollectionViewPagingLayoutTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
+ 291FDEC1262327FD00AD1C14 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
298DBBF42441C94900341D8E /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -278,35 +363,104 @@
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
+ 291FDEBF262327FD00AD1C14 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 291FDEC6262327FD00AD1C14 /* CollectionViewPagingLayoutTests.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
298DBBF22441C94900341D8E /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 29CD47C626235E6F00376CC8 /* SnapshotTransformViewOptions+Layout.swift in Sources */,
+ 291EC0E52610B32500C65A34 /* ScalePageView.swift in Sources */,
+ 291EC0E62610B32500C65A34 /* TransformPageViewProtocol.swift in Sources */,
+ 29CD47CA26235E7800376CC8 /* StackTransformViewOptions+Layout.swift in Sources */,
+ 2967EBA226230A320035540A /* PagePadding.swift in Sources */,
298DBC2D2441C98E00341D8E /* ScaleTransformViewOptions.Rotation3dOptions.swift in Sources */,
298DBC262441C98E00341D8E /* SnapshotTransformView.swift in Sources */,
+ 291EC0EA2610B32500C65A34 /* SnapshotPageView.swift in Sources */,
298DBC252441C98E00341D8E /* SnapshotTransformViewOptions.PiecesValue.swift in Sources */,
298DBC292441C98E00341D8E /* SnapshotTransformViewOptions.swift in Sources */,
298DBC342441C98E00341D8E /* StackTransformView.swift in Sources */,
298DBC352441C98E00341D8E /* StackTransformViewOptions.swift in Sources */,
+ 29DF4687261DF863007E0FA4 /* CollectionViewPagingLayout.ZPositionHandler.swift in Sources */,
298DBC242441C98E00341D8E /* BlurEffectView.swift in Sources */,
298DBC302441C98E00341D8E /* Multipliable.swift in Sources */,
+ 291EC0E92610B32500C65A34 /* PagingCollectionViewController.swift in Sources */,
298DBC382441C98E00341D8E /* TransformableView.swift in Sources */,
+ 291EC0E82610B32500C65A34 /* TransformPageView.swift in Sources */,
298DBC312441C98E00341D8E /* CGFloat+Interpolate.swift in Sources */,
+ 29DF4686261DF863007E0FA4 /* ViewAnimator.swift in Sources */,
298DBC2C2441C98E00341D8E /* ScaleTransformView.swift in Sources */,
298DBC2F2441C98E00341D8E /* CGFloat+Range.swift in Sources */,
+ 291EC0E72610B32500C65A34 /* PagingCollectionViewCell.swift in Sources */,
+ 29CD47CE26235E8A00376CC8 /* ScaleTransformViewOptions+Layout.swift in Sources */,
298DBC2A2441C98E00341D8E /* TransformCurve.swift in Sources */,
+ 2967EBA526230A570035540A /* PagingCollectionViewModifierData.swift in Sources */,
298DBC2B2441C98E00341D8E /* ScaleTransformViewOptions.swift in Sources */,
298DBC282441C98E00341D8E /* SnapshotContainerView.swift in Sources */,
+ 291EC0E42610B32500C65A34 /* StackPageView.swift in Sources */,
298DBC332441C98E00341D8E /* CollectionViewPagingLayout.swift in Sources */,
298DBC2E2441C98E00341D8E /* ScaleTransformViewOptions.Translation3dOptions.swift in Sources */,
298DBC272441C98E00341D8E /* SnapshotTransformViewOptions.PiecePosition.swift in Sources */,
+ 291EC0E32610B32500C65A34 /* PagingCollectionViewControllerBuilder.swift in Sources */,
298DBC322441C98E00341D8E /* UIView+Utilities.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
+/* Begin PBXTargetDependency section */
+ 291FDECA262327FD00AD1C14 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 298DBBF52441C94900341D8E /* CollectionViewPagingLayout */;
+ targetProxy = 291FDEC9262327FD00AD1C14 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
/* Begin XCBuildConfiguration section */
+ 291FDECB262327FD00AD1C14 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = 4J5W7CJ2ZV;
+ INFOPLIST_FILE = CollectionViewPagingLayoutTests/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.4;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = app.amir.CollectionViewPagingLayoutTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 291FDECC262327FD00AD1C14 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = 4J5W7CJ2ZV;
+ INFOPLIST_FILE = CollectionViewPagingLayoutTests/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.4;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = app.amir.CollectionViewPagingLayoutTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
298DBBFC2441C94900341D8E /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -333,6 +487,7 @@
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;
@@ -358,7 +513,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 13.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
@@ -396,6 +551,7 @@
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;
@@ -415,7 +571,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 13.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
@@ -438,14 +594,17 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = CollectionViewPagingLayout/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
+ MARKETING_VERSION = 1.1.0;
PRODUCT_BUNDLE_IDENTIFIER = amir.app.CollectionViewPagingLayout;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
+ SUPPORTS_MACCATALYST = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
@@ -462,14 +621,17 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = CollectionViewPagingLayout/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
+ MARKETING_VERSION = 1.1.0;
PRODUCT_BUNDLE_IDENTIFIER = amir.app.CollectionViewPagingLayout;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
+ SUPPORTS_MACCATALYST = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
@@ -478,6 +640,15 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
+ 291FDECD262327FD00AD1C14 /* Build configuration list for PBXNativeTarget "CollectionViewPagingLayoutTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 291FDECB262327FD00AD1C14 /* Debug */,
+ 291FDECC262327FD00AD1C14 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
298DBBF02441C94900341D8E /* Build configuration list for PBXProject "CollectionViewPagingLayout" */ = {
isa = XCConfigurationList;
buildConfigurations = (
diff --git a/CollectionViewPagingLayout.xcodeproj/xcshareddata/xcschemes/CollectionViewPagingLayout.xcscheme b/CollectionViewPagingLayout.xcodeproj/xcshareddata/xcschemes/CollectionViewPagingLayout.xcscheme
index 3cd80cd..250ff93 100644
--- a/CollectionViewPagingLayout.xcodeproj/xcshareddata/xcschemes/CollectionViewPagingLayout.xcscheme
+++ b/CollectionViewPagingLayout.xcodeproj/xcshareddata/xcschemes/CollectionViewPagingLayout.xcscheme
@@ -1,6 +1,6 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CollectionViewPagingLayout/Info.plist b/CollectionViewPagingLayout/Info.plist
index 9bcb244..c0701c6 100644
--- a/CollectionViewPagingLayout/Info.plist
+++ b/CollectionViewPagingLayout/Info.plist
@@ -15,7 +15,7 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 1.0
+ $(MARKETING_VERSION)
CFBundleVersion
$(CURRENT_PROJECT_VERSION)
diff --git a/CollectionViewPagingLayoutTests/CollectionViewPagingLayoutTests.swift b/CollectionViewPagingLayoutTests/CollectionViewPagingLayoutTests.swift
new file mode 100644
index 0000000..d8f4c24
--- /dev/null
+++ b/CollectionViewPagingLayoutTests/CollectionViewPagingLayoutTests.swift
@@ -0,0 +1,20 @@
+//
+// CollectionViewPagingLayoutTests.swift
+// CollectionViewPagingLayoutTests
+//
+// Created by Amir on 11/04/2021.
+// Copyright © 2021 Amir. All rights reserved.
+//
+
+import XCTest
+@testable import CollectionViewPagingLayout
+
+class CollectionViewPagingLayoutTests: XCTestCase {
+
+ func testAssignToCollectionView() throws {
+ let layout = CollectionViewPagingLayout()
+ let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
+ XCTAssert(collectionView.collectionViewLayout == layout)
+ }
+
+}
diff --git a/CollectionViewPagingLayoutTests/Info.plist b/CollectionViewPagingLayoutTests/Info.plist
new file mode 100644
index 0000000..64d65ca
--- /dev/null
+++ b/CollectionViewPagingLayoutTests/Info.plist
@@ -0,0 +1,22 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+
+
diff --git a/HOW_TO_USE_SWIFTUI.md b/HOW_TO_USE_SWIFTUI.md
new file mode 100644
index 0000000..a436d9e
--- /dev/null
+++ b/HOW_TO_USE_SWIFTUI.md
@@ -0,0 +1,255 @@
+
+## How to use with `SwiftUI`
+
+*If you'd like to follow code instead of docs go to [Sample Code](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/HOW_TO_USE_SWIFTUI.md#sample-code)*
+
+
+- First, make sure you imported the framework
+```swift
+import CollectionViewPagingLayout
+```
+
+### Prepared Page views
+
+There are three prepared page views to make it easier to use this framework.
+- `ScalePageView` (orange previews)
+- `SnapshotPageView` (green previews)
+- `StackPageView` (blue previews)
+
+These views are highly customizable, you can make tons of different effects using them.
+Here is a simple example for `StackPageView`:
+
+```swift
+ var body: some View {
+ StackPageView(items) { item in
+ // Build your view here
+ ZStack {
+ Rectangle().fill(Color.orange)
+ Text("\(item.number)")
+ }
+ }
+ .pagePadding(.absolute(50))
+ .options(.init(
+ scaleFactor: 0.12,
+ maxStackSize: 4,
+ popAngle: .pi / 10,
+ popOffsetRatio: .init(width: -1.45, height: 0.3),
+ stackPosition: .init(x: 1.5, y: 0)
+ ))
+ }
+```
+There is an "options" property for each of these page views where you can customize the effect, check the struct to find out what each parameter does.
+
+
+### TransformPageView
+
+You can build your custom effects using this view.
+
+```swift
+ var body: some View {
+ TransformPageView(items) { item, progress in
+ // Build your view here
+ ZStack {
+ Rectangle().fill(Color.orange)
+ Text("\(item.number)")
+ }
+ // Apply transform here
+ .transformEffect(.init(translationX: progress * 200, y: 0))
+ }
+ // The padding around each page
+ // you can use `.fractionalWidth` and
+ // `.fractionalHeight` too
+ .pagePadding(
+ vertical: .absolute(100),
+ horizontal: .absolute(80)
+ )
+ }
+
+```
+As you see above, you get a `progress` value. Use that to apply any effect you want.
+
+> `progress` is a float value that represents the current position of the page view.
+> When it's `0` that means the page position is exactly in the center of TransformPageView.
+> The value could be negative or positive and that represents the distance to the center of your TransformPageView.
+> for instance `1` means the distance between the center of the page and the center of TransformPageView is equal to your TransformPageView width.
+
+in above example we `translationX: progress * 200` meaning current page will be in the center (translationX = 0), the page before current page will be -200 pixels behind and so on ...
+
+### Selection
+
+You can pass a binding value when you initialize your page view along with `items`:
+`selection: Binding`
+
+This is a two-way binding selection, which means you can change it to animate the page to the selected page, and the value gets changed when the user navigates between pages.
+
+
+## Modifiers
+### - `.pagePadding`
+By default, the content view size will be equal to the parent view, you can use this modifier to add padding around pages. Padding can be `absolute`, `fractionalHeight` or `fractionalWidth`:
+```swift
+ enum Padding {
+ /// Creates a padding with an absolute point value.
+ case absolute(CGFloat)
+
+ /// Creates a padding that is computed as a fraction of the height of the container view.
+ case fractionalHeight(CGFloat)
+
+ /// Creates a padding that is computed as a fraction of the width of the container view.
+ case fractionalWidth(CGFloat)
+ }
+```
+
+You can use one of these or combine them:
+```swift
+pagePadding(_ padding: )
+pagePadding(vertical: horizontal: )
+pagePadding(top: left: bottom: right:)
+```
+
+
+### - `.numberOfVisibleItems`
+By default, all of the pages get load immediately.
+If you have lots of pages and you want to have lazy loading, use this modifier.
+
+```swift
+numberOfVisibleItems(_ count: Int)
+```
+
+
+### - `.zPosition`
+Use this modifier to provide zPosition(zIndex) for each page.
+The default value: `Int(-abs(round(progress)))`
+
+```swift
+zPosition(_ zPosition: (progress: CGFloat) -> Int)
+```
+
+### - `.onTapPage`
+*Note: in most cases you want to use `selection: Binding` instead of this modifer.*
+
+As the name says this modifier can be used to handle tap on page.
+This is equivalent for `collectionView(_ collectionView:, didSelectItemAt indexPath:)`
+
+```swift
+onTapPage(_ closure: (ValueType.ID) -> Void)
+```
+
+### - `.animator`
+Use this modifer to define your animator.
+[`DefaultViewAnimator`](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/ViewAnimator.swift#L29) allows you to specify animation duration and curve function.
+
+You can implement your custom animator too!.
+
+```swift
+animator(_ animator: ViewAnimator)
+```
+
+### - `.scrollToSelectedPage`
+if this is enabled page view automaticly scrolls to the selected page.
+default value: `true`
+
+```swift
+scrollToSelectedPage(_ goToSelectedPage: Bool)
+```
+
+### - `.scrollDirection`
+Use to specify the scroll direction `.vertical` or `.horizontal`
+
+
+### - `.collectionView`
+Using this modifier, you can set value for the underlying UICollectionView's properties
+
+for instance, if you want to show vertical scroll indicators:
+`.collectionView(\.showsVerticalScrollIndicator, true)`
+
+
+```swift
+collectionView(_ key: WritableKeyPath, _ value: T)
+```
+
+
+
+## Sample Code
+
+`ContentView` implementing a scale effect:
+
+```swift
+import SwiftUI
+import CollectionViewPagingLayout
+
+struct ContentView: View {
+
+ // Replace with your data
+ struct Item: Identifiable {
+ let id: UUID = .init()
+ let number: Int
+ }
+ let items = Array(0..<10).map {
+ Item(number: $0)
+ }
+
+ // Use the options to customize the layout
+ var options: ScaleTransformViewOptions {
+ .layout(.linear)
+ }
+
+ var body: some View {
+ ScalePageView(items) { item in
+ // Build your view here
+ ZStack {
+ Rectangle().fill(Color.orange)
+ Text("\(item.number)")
+ }
+ }
+ .options(options)
+ // The padding around each page
+ // you can use `.fractionalWidth` and
+ // `.fractionalHeight` too
+ .pagePadding(
+ vertical: .absolute(100),
+ horizontal: .absolute(80)
+ )
+ }
+
+}
+```
+
+`ContentView` implementing a custom effect:
+
+
+```swift
+struct ContentView: View {
+
+ // Replace with your data
+ struct Item: Identifiable {
+ let id: UUID = .init()
+ let number: Int
+ }
+ let items = Array(0..<10).map {
+ Item(number: $0)
+ }
+
+ var body: some View {
+ TransformPageView(items) { item, progress in
+ // Build your view here
+ ZStack {
+ Rectangle().fill(Color.orange)
+ Text("\(item.number)")
+ }
+ // Apply transform and other effects
+ .scaleEffect(1 - abs(progress) * 0.6)
+ .transformEffect(.init(translationX: progress * 200, y: 0))
+ .blur(radius: abs(progress) * 20)
+ .opacity(1.8 - Double(abs(progress)))
+ }
+ // The padding around each page
+ // you can use `.fractionalWidth` and
+ // `.fractionalHeight` too
+ .pagePadding(
+ vertical: .absolute(100),
+ horizontal: .absolute(80)
+ )
+ }
+
+}
+```
\ No newline at end of file
diff --git a/HOW_TO_USE_UIKIT.md b/HOW_TO_USE_UIKIT.md
new file mode 100644
index 0000000..ac0a125
--- /dev/null
+++ b/HOW_TO_USE_UIKIT.md
@@ -0,0 +1,245 @@
+
+## How to use with `UIKit`
+
+*If you'd like to follow code instead of docs go to [Sample Code](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/HOW_TO_USE_UIKIT.md#sample-code)*
+
+- First, make sure you imported the framework
+```swift
+import CollectionViewPagingLayout
+```
+- Set up your `UICollectionView` as you always do (you need a custom class for cells)
+- Set the layout for your collection view:
+(in most cases you want a paging effect so enable that too)
+```swift
+let layout = CollectionViewPagingLayout()
+collectionView.collectionViewLayout = layout
+collectionView.isPagingEnabled = true // enabling paging effect
+layout.numberOfVisibleItems = nil // default=nil means it's equal to number of items in CollectionView
+```
+
+### Prepared Transformable Protocols
+
+*If you'd like to build your custom effect, go to [Make custom effects](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/HOW_TO_USE_UIKIT.md#make-custom-effects)*
+
+There are some prepared transformable protocols to make it easier to use this framework.
+Using them is simple. You only need to conform your `UICollectionViewCell` to the protocol.
+You can use the options property to tweak it as you want.
+There are three types:
+- `ScaleTransformView` (orange previews)
+- `SnapshotTransformView` (green previews)
+- `StackTransformView` (blue previews)
+These protocols are highly customizable, you can make tons of different effects using them.
+Here is a simple example for `ScaleTransformView` which gives you a simple paging with scaling effect:
+```swift
+extension YourCell: ScaleTransformView {
+ var scaleOptions: ScaleTransformViewOptions {
+ ScaleTransformViewOptions(
+ minScale: 0.6,
+ scaleRatio: 0.4,
+ translationRatio: CGPoint(x: 0.66, y: 0.2),
+ maxTranslationRatio: CGPoint(x: 2, y: 0)
+ )
+ }
+}
+```
+There is an "options" property for each of these protocols where you can customize the effect, check the struct to find out what each parameter does.
+A short comment on the top of each parameter explains what that does.
+
+If you want to replicate the same effects that you see in previews, use the `Layout` extension file.
+
+`ScaleTransformView` -> [`ScaleTransformViewOptions`](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Scale/ScaleTransformViewOptions.swift) [`.Layout`](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Scale/ScaleTransformViewOptions%2BLayout.swift)
+`SnapshotTransformView` -> [`SnapshotTransformViewOptions`](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Snapshot/SnapshotTransformViewOptions.swift) [`.Layout`](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Snapshot/SnapshotTransformViewOptions%2BLayout.swift)
+`StackTransformView` -> [`StackTransformViewOptions`](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Stack/StackTransformViewOptions.swift) [`.Layout`](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Stack/StackTransformViewOptions%2BLayout.swift)
+
+#### Target View
+All the effects apply to one subview of your cell. that is the target view.
+By default, target view is the first subview of `cell.contentView`. (Override it If that's not what you want)
+The contentView size will always be equal to the UICollectionView size. So, It's important to consider the padding inside your cell.
+For instance, if you want to show 20 percent of the next and the previous page, your target view width should be 60 percent of `cell.contentView`.
+
+Target View named as below:
+
+`ScaleTransformView` -> [`scalableView`](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Scale/ScaleTransformView.swift#L18)
+`SnapshotTransformView` -> [`targetView`](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Snapshot/SnapshotTransformView.swift#L17)
+`StackTransformView` -> [`cardView`](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Stack/StackTransformView.swift#L18)
+
+
+
+### Customize or Combine Prepared Transformables
+
+Yes, you can customize them or even combine them.
+To do that, implement `TransformableView.transform` function and call the transformable function manually, like this:
+```swift
+extension LayoutTypeCollectionViewCell: ScaleTransformView {
+
+ func transform(progress: CGFloat) {
+ applyScaleTransform(progress: progress)
+ // customize views here, like this:
+ titleLabel.alpha = 1 - abs(progress)
+ subtitleLabel.alpha = titleLabel.alpha
+ }
+
+}
+```
+As you see, `applyScaleTransform` applies the scale transforms and right after that we change the alpha for `titleLabel` and `subtitleLabel`.
+To find the public function(s) of each protocol check the definition of that.
+
+
+## Make custom effects.
+
+Conform your cell class to `TransformableView` and start implementing your custom transforms.
+for instance:
+```swift
+class YourCell: UICollectionViewCell { /*...*/ }
+extension YourCell: TransformableView {
+ func transform(progress: CGFloat) {
+ // apply changes on any view of your cell
+ }
+}
+```
+As you see above, you get a `progress` value. Use that to apply any changes you want.
+
+> `progress` is a float value that represents the current position of your cell in the collection view.
+> When it's `0` that means the current position of the cell is exactly in the center of your CollectionView.
+> the value could be negative or positive and that represents the distance to the center of your CollectionView.
+> for instance `1` means the distance between the center of the cell and the center of your collection view is equal to your CollectionView width.
+
+
+you can start with a simple transform like this:
+```swift
+extension YourCell: TransformableView {
+ func transform(progress: CGFloat) {
+ let transform = CGAffineTransform(translationX: bounds.width/2 * progress, y: 0)
+ let alpha = 1 - abs(progress)
+
+ contentView.subviews.forEach { $0.transform = transform }
+ contentView.alpha = alpha
+ }
+}
+```
+
+## Features
+
+### Control current page
+
+You can control the current page by the following functions of `CollectionViewPagingLayout`:
+- [`func setCurrentPage(Int)`](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/CollectionViewPagingLayout.swift#L101)
+- [`func goToNextPage()`](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/CollectionViewPagingLayout.swift#L107)
+- [`func goToPreviousPage()`](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/CollectionViewPagingLayout.swift#L113)
+
+These are safe wrappers around setting the `ContentOffset` of `UICollectionview`.
+You can get the current page by a public variable: `CollectionViewPagingLayout.currentPage`.
+Listen to the changes using [`CollectionViewPagingLayout.delegate`](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/CollectionViewPagingLayout.swift#L11):
+```swift
+public protocol CollectionViewPagingLayoutDelegate: class {
+ func onCurrentPageChanged(layout: CollectionViewPagingLayout, currentPage: Int)
+}
+```
+
+### ViewAnimator
+
+A custom animator that you can use to animate `ContentOffset`.
+
+[`DefaultViewAnimator`](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/ViewAnimator.swift#L29) allows you to specify animation duration and curve function.
+
+You can implement your custom animator too!.
+
+You can specify a default animator `CollectionViewPagingLayout.defaultAnimator` which will be used for all animations unless you specify animator per animation:
+`setCurrentPage(_ page: Int, animated: Bool, animator: ViewAnimator?)`,
+
+
+
+## Sample Code
+
+UIViewController:
+
+```swift
+import UIKit
+import CollectionViewPagingLayout
+
+// A simple View Controller that filled with a UICollectionView
+// You can use `UICollectionViewController` too
+class ViewController: UIViewController, UICollectionViewDataSource {
+
+ var collectionView: UICollectionView!
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ setupCollectionView()
+ }
+
+ private func setupCollectionView() {
+ let layout = CollectionViewPagingLayout()
+ collectionView = UICollectionView(frame: view.frame, collectionViewLayout: layout)
+ collectionView.isPagingEnabled = true
+ collectionView.register(MyCell.self, forCellWithReuseIdentifier: "cell")
+ collectionView.dataSource = self
+ view.addSubview(collectionView)
+ }
+
+ func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+ 10
+ }
+
+ func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+ collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
+ }
+
+}
+```
+
+UICollectionViewCell:
+
+```swift
+class MyCell: UICollectionViewCell {
+
+ // The card view that we apply transforms on
+ var card: UIView!
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ setup()
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ setup()
+ }
+
+ func setup() {
+ // Adjust the card view frame
+ // you can use Auto-layout too
+ let cardFrame = CGRect(
+ x: 80,
+ y: 100,
+ width: frame.width - 160,
+ height: frame.height - 200
+ )
+ card = UIView(frame: cardFrame)
+ card.backgroundColor = .systemOrange
+ contentView.addSubview(card)
+ }
+}
+
+```
+
+`MyCell` implementing a [scale effect](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/HOW_TO_USE_UIKIT.md#prepared-transformable-protocols):
+
+```swift
+extension MyCell: ScaleTransformView {
+ var scaleOptions: ScaleTransformViewOptions {
+ .layout(.linear)
+ }
+}
+```
+
+`MyCell` implementing a [custom effect]((https://github.com/amirdew/CollectionViewPagingLayout/blob/master/HOW_TO_USE_UIKIT.md#make-custom-effects)):
+
+```swift
+extension MyCell: TransformableView {
+ func transform(progress: CGFloat) {
+ let alpha = 1 - abs(progress)
+ contentView.alpha = alpha
+ }
+}
+```
diff --git a/Lib/BlurEffectView.swift b/Lib/BlurEffectView.swift
index 52aeb3f..c7ac78d 100644
--- a/Lib/BlurEffectView.swift
+++ b/Lib/BlurEffectView.swift
@@ -8,7 +8,6 @@
import UIKit
-@available(iOS 10.0, *)
public class BlurEffectView: UIVisualEffectView {
// MARK: Parameters
diff --git a/Lib/CollectionViewPagingLayout.ZPositionHandler.swift b/Lib/CollectionViewPagingLayout.ZPositionHandler.swift
new file mode 100644
index 0000000..3773683
--- /dev/null
+++ b/Lib/CollectionViewPagingLayout.ZPositionHandler.swift
@@ -0,0 +1,22 @@
+//
+// CollectionViewPagingLayout.ZPositionHandler.swift
+// CollectionViewPagingLayout
+//
+// Created by Amir on 03/04/2021.
+// Copyright © 2021 Amir Khorsandi. All rights reserved.
+//
+
+import Foundation
+
+public extension CollectionViewPagingLayout {
+ enum ZPositionHandler {
+ /// Sets cell.layer.zPosition
+ case cellLayer
+
+ /// Sets UICollectionViewLayoutAttributes.zIndex
+ case layoutAttribute
+
+ /// Sets both of `cellLayer` and `layoutAttribute`
+ case both
+ }
+}
diff --git a/Lib/CollectionViewPagingLayout.swift b/Lib/CollectionViewPagingLayout.swift
index c4e5386..f9f1b86 100644
--- a/Lib/CollectionViewPagingLayout.swift
+++ b/Lib/CollectionViewPagingLayout.swift
@@ -8,43 +8,56 @@
import UIKit
-public protocol CollectionViewPagingLayoutDelegate: class {
+public protocol CollectionViewPagingLayoutDelegate: AnyObject {
/// Calls when the current page changes
///
/// - Parameter layout: a reference to the layout class
/// - Parameter currentPage: the new current page index
func onCurrentPageChanged(layout: CollectionViewPagingLayout, currentPage: Int)
-
- /// Calls when the user taps on the `TransformableView.selectableView`
- /// to enable this functionality you need to call `configureTapOnCollectionView()` after setting the layout
- ///
- /// - Parameter layout: a reference to the layout class
- /// - Parameter indexPath: IndexPath for the selected cell
- func collectionViewPagingLayout(_ layout: CollectionViewPagingLayout, didSelectItemAt indexPath: IndexPath)
}
public extension CollectionViewPagingLayoutDelegate {
func onCurrentPageChanged(layout: CollectionViewPagingLayout, currentPage: Int) {}
- func collectionViewPagingLayout(_ layout: CollectionViewPagingLayout, didSelectItemAt indexPath: IndexPath) {}
}
public class CollectionViewPagingLayout: UICollectionViewLayout {
// MARK: Properties
-
+
+ /// Number of visible items at the same time
+ ///
+ /// nil = no limit
public var numberOfVisibleItems: Int?
-
+
+ /// Constants that indicate the direction of scrolling for the layout.
public var scrollDirection: UICollectionView.ScrollDirection = .horizontal
+
+ /// See `ZPositionHandler` for details
+ public var zPositionHandler: ZPositionHandler = .both
+
+ /// Set `alpha` to zero when the cell is not loaded yet by collection view, enabling this prevents showing a cell before applying
+ /// transforms but may cause flashing when you reload the data
+ public var transparentAttributeWhenCellNotLoaded: Bool = false
+
+ /// The animator for setting `contentOffset`
+ ///
+ /// See `ViewAnimator` for details
+ public var defaultAnimator: ViewAnimator?
+
+ public private(set) var isAnimating: Bool = false
public weak var delegate: CollectionViewPagingLayoutDelegate?
override public var collectionViewContentSize: CGSize {
getContentSize()
}
-
+
+ /// Current page index
+ ///
+ /// Use `setCurrentPage` to change it
public private(set) var currentPage: Int = 0 {
didSet {
delegate?.onCurrentPageChanged(layout: self, currentPage: currentPage)
@@ -70,27 +83,58 @@ public class CollectionViewPagingLayout: UICollectionViewLayout {
}
private var currentPageCache: Int?
- private var attributesCache: [(page: Int, attributes:UICollectionViewLayoutAttributes)]?
- private var scrollToSelectedCell: Bool = false
-
-
+ private var attributesCache: [(page: Int, attributes: UICollectionViewLayoutAttributes)]?
+ private var boundsObservation: NSKeyValueObservation?
+ private var lastBounds: CGRect?
+ private var currentViewAnimatorCancelable: ViewAnimatorCancelable?
+ private var originalIsUserInteractionEnabled: Bool?
+ private var contentOffsetObservation: NSKeyValueObservation?
+
+
// MARK: Public functions
- public func setCurrentPage(_ page: Int, animated: Bool = true) {
- safelySetCurrentPage(page, animated: animated)
+ public func setCurrentPage(_ page: Int,
+ animated: Bool = true,
+ animator: ViewAnimator? = nil,
+ completion: (() -> Void)? = nil) {
+ safelySetCurrentPage(page, animated: animated, animator: animator, completion: completion)
}
-
- public func goToNextPage(animated: Bool = true) {
- setCurrentPage(currentPage + 1, animated: animated)
+
+ public func setCurrentPage(_ page: Int,
+ animated: Bool = true,
+ completion: (() -> Void)? = nil) {
+ safelySetCurrentPage(page, animated: animated, animator: defaultAnimator, completion: completion)
}
- public func goToPreviousPage(animated: Bool = true) {
- setCurrentPage(currentPage - 1, animated: animated)
+ public func goToNextPage(animated: Bool = true,
+ animator: ViewAnimator? = nil,
+ completion: (() -> Void)? = nil) {
+ setCurrentPage(currentPage + 1, animated: animated, animator: animator, completion: completion)
}
- public func configureTapOnCollectionView(goToSelectedPage: Bool = false) {
- self.scrollToSelectedCell = goToSelectedPage
- addTapGestureToCollectionView()
+ public func goToPreviousPage(animated: Bool = true,
+ animator: ViewAnimator? = nil,
+ completion: (() -> Void)? = nil) {
+ setCurrentPage(currentPage - 1, animated: animated, animator: animator, completion: completion)
+ }
+
+ /// Calls `invalidateLayout` wrapped in `performBatchUpdates`
+ /// - Parameter invalidateOffset: change offset and revert it immediately
+ /// this fixes the zIndex issue more: https://stackoverflow.com/questions/12659301/uicollectionview-setlayoutanimated-not-preserving-zindex
+ public func invalidateLayoutInBatchUpdate(invalidateOffset: Bool = false) {
+ DispatchQueue.main.async { [weak self] in
+ if invalidateOffset,
+ let collectionView = self?.collectionView,
+ self?.isAnimating == false {
+ let original = collectionView.contentOffset
+ collectionView.contentOffset = .init(x: original.x + 1, y: original.y + 1)
+ collectionView.contentOffset = original
+ }
+
+ self?.collectionView?.performBatchUpdates({ [weak self] in
+ self?.invalidateLayout()
+ })
+ }
}
@@ -102,7 +146,7 @@ public class CollectionViewPagingLayout: UICollectionViewLayout {
}
return true
}
-
+
override public func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let currentScrollOffset = self.currentScrollOffset
let numberOfItems = self.numberOfItems
@@ -123,7 +167,7 @@ public class CollectionViewPagingLayout: UICollectionViewLayout {
let startIndex = max(0, initialStartIndex - endIndexOutOfBounds)
let endIndex = min(numberOfItems, initialEndIndex + startIndexOutOfBounds)
- var attributesArray: [(page: Int, attributes:UICollectionViewLayoutAttributes)] = []
+ var attributesArray: [(page: Int, attributes: UICollectionViewLayoutAttributes)] = []
var section = 0
var numberOfItemsInSection = collectionView?.numberOfItems(inSection: section) ?? 0
var numberOfItemsInPrevSections = 0
@@ -150,14 +194,26 @@ public class CollectionViewPagingLayout: UICollectionViewLayout {
if cell == nil || cell is TransformableView {
cellAttributes.frame = visibleRect
+ if cell == nil, transparentAttributeWhenCellNotLoaded {
+ cellAttributes.alpha = 0
+ }
} else {
- cellAttributes.frame = CGRect(origin: CGPoint(x: pageIndex * visibleRect.width, y: 0), size: visibleRect.size)
+ cellAttributes.frame = CGRect(origin: CGPoint(x: pageIndex * visibleRect.width, y: 0),
+ size: visibleRect.size)
+ }
+
+ // In some cases attribute.zIndex doesn't work so this is the work-around
+ if let cell = cell, [ZPositionHandler.both, .cellLayer].contains(zPositionHandler) {
+ cell.layer.zPosition = CGFloat(zIndex)
+ }
+
+ if [ZPositionHandler.both, .layoutAttribute].contains(zPositionHandler) {
+ cellAttributes.zIndex = zIndex
}
-
- cellAttributes.zIndex = zIndex
attributesArray.append((page: Int(pageIndex), attributes: cellAttributes))
}
attributesCache = attributesArray
+ addBoundsObserverIfNeeded()
return attributesArray.map(\.attributes)
}
@@ -186,7 +242,7 @@ public class CollectionViewPagingLayout: UICollectionViewLayout {
currentPage = Int(round(offset / pageSize))
}
}
- if currentPage != self.currentPage {
+ if currentPage != self.currentPage, !isAnimating {
self.currentPage = currentPage
}
}
@@ -205,60 +261,80 @@ public class CollectionViewPagingLayout: UICollectionViewLayout {
}
}
- private func safelySetCurrentPage(_ page: Int, animated: Bool) {
+ private func safelySetCurrentPage(_ page: Int, animated: Bool, animator: ViewAnimator?, completion: (() -> Void)? = nil) {
+ if isAnimating {
+ currentViewAnimatorCancelable?.cancel()
+ isAnimating = false
+ if let isEnabled = originalIsUserInteractionEnabled {
+ collectionView?.isUserInteractionEnabled = isEnabled
+ }
+ }
let pageSize = scrollDirection == .horizontal ? visibleRect.width : visibleRect.height
let contentSize = scrollDirection == .horizontal ? collectionViewContentSize.width : collectionViewContentSize.height
let maxPossibleOffset = contentSize - pageSize
- var offset = pageSize * CGFloat(page)
+ var offset = Double(pageSize) * Double(page)
offset = max(0, offset)
- offset = min(offset, maxPossibleOffset)
+ offset = min(offset, Double(maxPossibleOffset))
let contentOffset: CGPoint = scrollDirection == .horizontal ? CGPoint(x: offset, y: 0) : CGPoint(x: 0, y: offset)
- CATransaction.begin()
- CATransaction.setCompletionBlock { [weak self] in
- self?.invalidateLayout()
+
+ if animated {
+ isAnimating = true
+ }
+
+ if animated, let animator = animator {
+ setContentOffset(with: animator, offset: contentOffset, completion: completion)
+ } else {
+ contentOffsetObservation = collectionView?.observe(\.contentOffset, options: [.new]) { [weak self] _, _ in
+ if self?.collectionView?.contentOffset == contentOffset {
+ self?.contentOffsetObservation = nil
+ DispatchQueue.main.async { [weak self] in
+ self?.invalidateLayoutInBatchUpdate()
+ self?.collectionView?.setContentOffset(contentOffset, animated: false)
+ self?.isAnimating = false
+ completion?()
+ }
+ }
+ }
+ collectionView?.setContentOffset(contentOffset, animated: animated)
}
- collectionView?.setContentOffset(contentOffset, animated: animated)
- CATransaction.commit()
// this is necessary when we want to set the current page without animation
- if !animated, page != currentPage, let collectionView = collectionView {
- collectionView.performBatchUpdates({
- collectionView.collectionViewLayout.invalidateLayout()
- })
+ if !animated, page != currentPage {
+ invalidateLayoutInBatchUpdate()
}
}
-
- private func addTapGestureToCollectionView() {
- let gesture = UITapGestureRecognizer(target: self, action: #selector(tapOnCollectionView(gesture:)))
- collectionView?.addGestureRecognizer(gesture)
- }
-
- @objc private func tapOnCollectionView(gesture: UITapGestureRecognizer) {
- var items = collectionView?.visibleCells.compactMap { cell -> (cell: UICollectionViewCell, rect: CGRect, attributes: UICollectionViewLayoutAttributes, page: Int)? in
- guard let indexPath = collectionView?.indexPath(for: cell),
- let view = cell as? TransformableView,
- let selectableView = view.selectableView,
- let attributesAndPage = attributesCache?.first(where: { $0.attributes.indexPath == indexPath }) else {
- return nil
- }
- let rect = selectableView.superview?.convert(selectableView.frame, to: collectionView) ?? .zero
- return (cell: cell, rect: rect, attributes: attributesAndPage.attributes, page: attributesAndPage.page)
- } ?? []
-
- items.sort { $0.attributes.zIndex > $1.attributes.zIndex }
-
- let location = gesture.location(in: gesture.view)
- var findSelected = false
- for item in items {
- if !findSelected, item.rect.contains(location) {
- delegate?.collectionViewPagingLayout(self, didSelectItemAt: item.attributes.indexPath)
- item.cell.isSelected = true
- findSelected = true
- if scrollToSelectedCell {
- setCurrentPage(item.page, animated: true)
- }
+
+ private func setContentOffset(with animator: ViewAnimator, offset: CGPoint, completion: (() -> Void)? = nil) {
+ guard let start = collectionView?.contentOffset else { return }
+ let x = offset.x - start.x
+ let y = offset.y - start.y
+ originalIsUserInteractionEnabled = collectionView?.isUserInteractionEnabled ?? true
+ collectionView?.isUserInteractionEnabled = false
+ currentViewAnimatorCancelable = animator.animate { [weak self] progress, finished in
+ guard let collectionView = self?.collectionView else { return }
+ collectionView.contentOffset = CGPoint(x: start.x + x * CGFloat(progress),
+ y: start.y + y * CGFloat(progress))
+ if finished {
+ self?.currentViewAnimatorCancelable = nil
+ self?.isAnimating = false
+ self?.collectionView?.isUserInteractionEnabled = self?.originalIsUserInteractionEnabled ?? true
+ self?.originalIsUserInteractionEnabled = nil
+ self?.collectionView?.delegate?.scrollViewDidEndScrollingAnimation?(collectionView)
+ self?.invalidateLayoutInBatchUpdate()
+ completion?()
}
- item.cell.isSelected = false
+ }
+ }
+}
+
+
+extension CollectionViewPagingLayout {
+ private func addBoundsObserverIfNeeded() {
+ guard boundsObservation == nil else { return }
+ boundsObservation = collectionView?.observe(\.bounds, options: [.old, .new, .initial, .prior]) { [weak self] collectionView, _ in
+ guard collectionView.bounds.size != self?.lastBounds?.size else { return }
+ self?.lastBounds = collectionView.bounds
+ self?.invalidateLayoutInBatchUpdate(invalidateOffset: true)
}
}
}
diff --git a/Lib/Scale/ScaleTransformView.swift b/Lib/Scale/ScaleTransformView.swift
index 5fb44ed..9d10c22 100644
--- a/Lib/Scale/ScaleTransformView.swift
+++ b/Lib/Scale/ScaleTransformView.swift
@@ -18,7 +18,7 @@ public protocol ScaleTransformView: TransformableView {
var scalableView: UIView { get }
/// The view to apply blur effect on
- var blurViewHost: UIView { get }
+ var scaleBlurViewHost: UIView { get }
/// the main function for applying transforms
func applyScaleTransform(progress: CGFloat)
@@ -28,7 +28,7 @@ public protocol ScaleTransformView: TransformableView {
public extension ScaleTransformView {
/// The default value is the super view of `scalableView`
- var blurViewHost: UIView {
+ var scaleBlurViewHost: UIView {
scalableView.superview ?? scalableView
}
}
@@ -82,12 +82,17 @@ public extension ScaleTransformView {
}
let layer = scalableView.layer
layer.shadowColor = scaleOptions.shadowColor.cgColor
+
+ let progressMultiplier = 1 - abs(progress)
+ let widthProgressValue = progressMultiplier * scaleOptions.shadowOffsetMax.width
+ let heightProgressValue = progressMultiplier * scaleOptions.shadowOffsetMax.height
+
let offset = CGSize(
- width: max(scaleOptions.shadowOffsetMin.width, (1 - abs(progress)) * scaleOptions.shadowOffsetMax.width),
- height: max(scaleOptions.shadowOffsetMin.height, (1 - abs(progress)) * scaleOptions.shadowOffsetMax.height)
+ width: max(scaleOptions.shadowOffsetMin.width, widthProgressValue),
+ height: max(scaleOptions.shadowOffsetMin.height, heightProgressValue)
)
layer.shadowOffset = offset
- layer.shadowRadius = max(scaleOptions.shadowRadiusMin, (1 - abs(progress)) * scaleOptions.shadowRadiusMax)
+ layer.shadowRadius = max(scaleOptions.shadowRadiusMin, progressMultiplier * scaleOptions.shadowRadiusMax)
layer.shadowOpacity = max(scaleOptions.shadowOpacityMin, (1 - abs(Float(progress))) * scaleOptions.shadowOpacityMax)
}
@@ -155,19 +160,18 @@ public extension ScaleTransformView {
}
scalableView.layer.transform = transform
}
-
- @available(iOS 10.0, *)
+
private func applyBlurEffect(progress: CGFloat) {
guard scaleOptions.blurEffectRadiusRatio > 0, scaleOptions.blurEffectEnabled else {
- blurViewHost.subviews.first(where: { $0 is BlurEffectView })?.removeFromSuperview()
+ scaleBlurViewHost.subviews.first(where: { $0 is BlurEffectView })?.removeFromSuperview()
return
}
let blurView: BlurEffectView
- if let view = blurViewHost.subviews.first(where: { $0 is BlurEffectView }) as? BlurEffectView {
+ if let view = scaleBlurViewHost.subviews.first(where: { $0 is BlurEffectView }) as? BlurEffectView {
blurView = view
} else {
blurView = BlurEffectView(effect: UIBlurEffect(style: scaleOptions.blurEffectStyle))
- blurViewHost.fill(with: blurView)
+ scaleBlurViewHost.fill(with: blurView)
}
blurView.setBlurRadius(radius: abs(progress) * scaleOptions.blurEffectRadiusRatio)
blurView.transform = CGAffineTransform.identity.translatedBy(x: scalableView.transform.tx, y: scalableView.transform.ty)
diff --git a/Samples/PagingLayoutSamples/Modules/Shapes/ShapeLayout+ScaleOptions.swift b/Lib/Scale/ScaleTransformViewOptions+Layout.swift
similarity index 62%
rename from Samples/PagingLayoutSamples/Modules/Shapes/ShapeLayout+ScaleOptions.swift
rename to Lib/Scale/ScaleTransformViewOptions+Layout.swift
index 2d62863..6625331 100644
--- a/Samples/PagingLayoutSamples/Modules/Shapes/ShapeLayout+ScaleOptions.swift
+++ b/Lib/Scale/ScaleTransformViewOptions+Layout.swift
@@ -1,19 +1,30 @@
//
-// ShapeLayout+ScaleOptions.swift
-// PagingLayoutSamples
+// ScaleTransformViewOptions+Layout.swift
+// CollectionViewPagingLayout
//
-// Created by Amir on 27/06/2020.
-// Copyright © 2020 Amir Khorsandi. All rights reserved.
+// Created by Amir on 28/03/2021.
+// Copyright © 2021 Amir Khorsandi. All rights reserved.
//
+import UIKit
import Foundation
-import CollectionViewPagingLayout
-extension ShapeLayout {
- var scaleOptions: ScaleTransformViewOptions? {
- switch self {
- case .scaleBlur:
- return ScaleTransformViewOptions(
+public extension ScaleTransformViewOptions {
+ enum Layout: String, CaseIterable {
+ case invertedCylinder
+ case cylinder
+ case coverFlow
+ case rotary
+ case linear
+ case easeIn
+ case easeOut
+ case blur
+ }
+
+ static func layout(_ layout: Layout) -> Self {
+ switch layout {
+ case .blur:
+ return Self(
minScale: 0.6,
scaleRatio: 0.4,
translationRatio: CGPoint(x: 0.66, y: 0.2),
@@ -21,8 +32,8 @@ extension ShapeLayout {
blurEffectEnabled: true,
blurEffectRadiusRatio: 0.2
)
- case .scaleLinear:
- return ScaleTransformViewOptions(
+ case .linear:
+ return Self(
minScale: 0.6,
scaleRatio: 0.4,
translationRatio: CGPoint(x: 0.66, y: 0.2),
@@ -32,8 +43,8 @@ extension ShapeLayout {
scaleCurve: .linear,
translationCurve: .linear
)
- case .scaleEaseIn:
- return ScaleTransformViewOptions(
+ case .easeIn:
+ return Self(
minScale: 0.6,
scaleRatio: 0.4,
translationRatio: CGPoint(x: 0.66, y: 0.2),
@@ -42,8 +53,8 @@ extension ShapeLayout {
scaleCurve: .easeIn,
translationCurve: .linear
)
- case .scaleEaseOut:
- return ScaleTransformViewOptions(
+ case .easeOut:
+ return Self(
minScale: 0.6,
scaleRatio: 0.4,
translationRatio: CGPoint(x: 0.66, y: 0.2),
@@ -52,8 +63,8 @@ extension ShapeLayout {
scaleCurve: .linear,
translationCurve: .easeIn
)
- case .scaleRotary:
- return ScaleTransformViewOptions(
+ case .rotary:
+ return Self(
minScale: 0,
scaleRatio: 0.4,
translationRatio: CGPoint(x: 0.1, y: 0.1),
@@ -64,8 +75,8 @@ extension ShapeLayout {
minTranslateRatios: (-3, -0.8, -0.3),
maxTranslateRatios: (3, 0.8, -0.3))
)
- case .scaleCylinder:
- return ScaleTransformViewOptions(
+ case .cylinder:
+ return Self(
minScale: 0.55,
maxScale: 0.55,
scaleRatio: 0,
@@ -76,8 +87,8 @@ extension ShapeLayout {
rotation3d: .init(angle: .pi / 4, minAngle: -.pi, maxAngle: .pi, x: 0, y: 1, z: 0, m34: -0.000_4 - 0.8 * 0.000_2 ),
translation3d: .init(translateRatios: (0, 0, 0), minTranslateRatios: (0, 0, 1.25), maxTranslateRatios: (0, 0, 1.25))
)
- case .scaleInvertedCylinder:
- return ScaleTransformViewOptions(
+ case .invertedCylinder:
+ return Self(
minScale: 1.2,
maxScale: 1.2,
scaleRatio: 0,
@@ -90,8 +101,31 @@ extension ShapeLayout {
minTranslateRatios: (-0.05, 0, 0.86),
maxTranslateRatios: (0.05, 0, -0.86))
)
- case .scaleCoverFlow:
- return ScaleTransformViewOptions(
+ case .coverFlow:
+ let defaultAngle: Double = .pi / 1.65
+ let minAngle: Double = -.pi / 3
+ let maxAngle: Double = .pi / 3
+ let rotation3d = Rotation3dOptions(
+ angle: defaultAngle,
+ minAngle: minAngle,
+ maxAngle: maxAngle,
+ x: 0,
+ y: -1,
+ z: 0,
+ m34: -0.000_5
+ )
+
+
+ let translateRatios: (CGFloat, CGFloat, CGFloat) = (0.1, 0, -0.7)
+ let minTranslateRatios: (CGFloat, CGFloat, CGFloat) = (-0.1, 0, -3)
+ let maxTranslateRatios: (CGFloat, CGFloat, CGFloat) = (0.1, 0, 0)
+ let translation3d = Translation3dOptions(
+ translateRatios: translateRatios,
+ minTranslateRatios: minTranslateRatios,
+ maxTranslateRatios: maxTranslateRatios
+ )
+
+ return Self(
minScale: 0.7,
maxScale: 0.7,
scaleRatio: 0,
@@ -99,11 +133,9 @@ extension ShapeLayout {
minTranslationRatio: .zero,
maxTranslationRatio: .zero,
shadowEnabled: true,
- rotation3d: .init(angle: .pi / 1.65, minAngle: -.pi / 3, maxAngle: .pi / 3, x: 0, y: -1, z: 0, m34: -0.000_5),
- translation3d: .init(translateRatios: (0.1, 0, -0.7), minTranslateRatios: (-0.1, 0, -3), maxTranslateRatios: (0.1, 0, 0))
+ rotation3d: rotation3d,
+ translation3d: translation3d
)
- default:
- return nil
}
}
}
diff --git a/Lib/Scale/ScaleTransformViewOptions.Translation3dOptions.swift b/Lib/Scale/ScaleTransformViewOptions.Translation3dOptions.swift
index 6264ba1..127eb3b 100644
--- a/Lib/Scale/ScaleTransformViewOptions.Translation3dOptions.swift
+++ b/Lib/Scale/ScaleTransformViewOptions.Translation3dOptions.swift
@@ -11,7 +11,7 @@ import UIKit
public extension ScaleTransformViewOptions {
struct Translation3dOptions {
-
+
// MARK: Properties
/// The translates(x,y,z) ratios
@@ -39,5 +39,5 @@ public extension ScaleTransformViewOptions {
self.maxTranslateRatios = maxTranslateRatios
}
}
-
+
}
diff --git a/Lib/Snapshot/SnapshotContainerView.swift b/Lib/Snapshot/SnapshotContainerView.swift
index c4ed4b6..378461f 100644
--- a/Lib/Snapshot/SnapshotContainerView.swift
+++ b/Lib/Snapshot/SnapshotContainerView.swift
@@ -17,7 +17,7 @@ public class SnapshotContainerView: UIView {
public let snapshotSize: CGSize
public let pieceSizeRatio: CGSize
- private let targetView: UIView
+ private weak var targetView: UIView?
// MARK: Lifecycle
diff --git a/Lib/Snapshot/SnapshotTransformView.swift b/Lib/Snapshot/SnapshotTransformView.swift
index 7445bfa..8491414 100644
--- a/Lib/Snapshot/SnapshotTransformView.swift
+++ b/Lib/Snapshot/SnapshotTransformView.swift
@@ -16,15 +16,18 @@ public protocol SnapshotTransformView: TransformableView {
/// The view to apply the effect on
var targetView: UIView { get }
- /// The identifier for snapshot, it won't make a new snapshot if
+ /// A unique identifier for the snapshot, a new snapshot won't be made if
/// there is a cashed snapshot with the same identifier
- var identifier: String { get }
+ var snapshotIdentifier: String { get }
/// the function for getting the cached snapshot or make a new one and cache it
func getSnapshot() -> SnapshotContainerView?
/// the main function for applying transforms on the snapshot
func applySnapshotTransform(snapshot: SnapshotContainerView, progress: CGFloat)
+
+ /// Check if the snapshot can be reused
+ func canReuse(snapshot: SnapshotContainerView) -> Bool
}
@@ -37,9 +40,9 @@ public extension SnapshotTransformView where Self: UICollectionViewCell {
}
/// Default `identifier` for `UICollectionViewCell` is it's index
- /// if you have the same content with different indexes (like infinite list)
+ /// if you have the same content with different indexes (like an infinite list)
/// you should override this and provide a content-based identifier
- var identifier: String {
+ var snapshotIdentifier: String {
var collectionView: UICollectionView?
var superview = self.superview
while superview != nil {
@@ -49,7 +52,22 @@ public extension SnapshotTransformView where Self: UICollectionViewCell {
}
superview = superview?.superview
}
- return "\(collectionView?.indexPath(for: self) ?? IndexPath())"
+ var identifier = "\(collectionView?.indexPath(for: self) ?? IndexPath())"
+
+ if let scrollView = targetView as? UIScrollView {
+ identifier.append("\(scrollView.contentOffset)")
+ }
+
+ if let scrollView = targetView.subviews.first(where: { $0 is UIScrollView }) as? UIScrollView {
+ identifier.append("\(scrollView.contentOffset)")
+ }
+
+ return identifier
+ }
+
+ /// Default implementation only compares the size of snapshot with the view
+ func canReuse(snapshot: SnapshotContainerView) -> Bool {
+ snapshot.snapshotSize == targetView.bounds.size
}
}
@@ -92,25 +110,46 @@ public extension SnapshotTransformView {
// MARK: Private functions
-
+
+ private func hideOtherSnapshots() {
+ targetView.superview?.subviews.filter { $0 is SnapshotContainerView }.forEach {
+ guard let snapshot = $0 as? SnapshotContainerView else { return }
+ if snapshot.identifier != snapshotIdentifier {
+ snapshot.alpha = 0
+ }
+ }
+ }
+
private func findSnapshot() -> SnapshotContainerView? {
- let snapshot = targetView.superview?.subviews.first(where: { $0 is SnapshotContainerView }) as? SnapshotContainerView
- if let snapshot = snapshot,
- (snapshot.identifier != identifier ||
- snapshot.snapshotSize != targetView.bounds.size ||
- snapshot.pieceSizeRatio != snapshotOptions.pieceSizeRatio)
- {
+ hideOtherSnapshots()
+
+ let snapshot = targetView.superview?.subviews.first {
+ ($0 as? SnapshotContainerView)?.identifier == snapshotIdentifier
+ } as? SnapshotContainerView
+
+ if let snapshot = snapshot, snapshot.pieceSizeRatio != snapshotOptions.pieceSizeRatio {
snapshot.removeFromSuperview()
return nil
}
+ if let snapshot = snapshot, !canReuse(snapshot: snapshot) {
+ snapshot.removeFromSuperview()
+ return nil
+ }
+ snapshot?.alpha = 1
return snapshot
}
private func makeSnapshot() -> SnapshotContainerView? {
- guard let view = SnapshotContainerView(targetView: targetView, pieceSizeRatio: snapshotOptions.pieceSizeRatio, identifier: identifier) else {
- return nil
- }
- targetView.superview?.subviews.first(where: { $0 is SnapshotContainerView })?.removeFromSuperview()
+ targetView.superview?.subviews.first {
+ ($0 as? SnapshotContainerView)?.identifier == snapshotIdentifier
+ }?
+ .removeFromSuperview()
+
+ guard let view = SnapshotContainerView(targetView: targetView,
+ pieceSizeRatio: snapshotOptions.pieceSizeRatio,
+ identifier: snapshotIdentifier)
+ else { return nil }
+
targetView.superview?.insertSubview(view, aboveSubview: targetView)
targetView.equalSize(to: view)
targetView.center(to: view)
diff --git a/Samples/PagingLayoutSamples/Modules/Shapes/ShapeLayout+SnapshotOptions.swift b/Lib/Snapshot/SnapshotTransformViewOptions+Layout.swift
similarity index 80%
rename from Samples/PagingLayoutSamples/Modules/Shapes/ShapeLayout+SnapshotOptions.swift
rename to Lib/Snapshot/SnapshotTransformViewOptions+Layout.swift
index 6cea80b..1c2b8d9 100644
--- a/Samples/PagingLayoutSamples/Modules/Shapes/ShapeLayout+SnapshotOptions.swift
+++ b/Lib/Snapshot/SnapshotTransformViewOptions+Layout.swift
@@ -1,19 +1,30 @@
//
-// ShapeLayout+SnapshotOptions.swift
-// PagingLayoutSamples
+// SnapshotTransformViewOptions+Layout.swift
+// CollectionViewPagingLayout
//
-// Created by Amir on 27/06/2020.
-// Copyright © 2020 Amir Khorsandi. All rights reserved.
+// Created by Amir on 28/03/2021.
+// Copyright © 2021 Amir Khorsandi. All rights reserved.
//
+import UIKit
import Foundation
-import CollectionViewPagingLayout
-extension ShapeLayout {
- var snapshotOptions: SnapshotTransformViewOptions? {
- switch self {
- case .snapshotGrid:
- return SnapshotTransformViewOptions(
+public extension SnapshotTransformViewOptions {
+ enum Layout: String, CaseIterable {
+ case grid
+ case space
+ case chess
+ case tiles
+ case lines
+ case bars
+ case puzzle
+ case fade
+ }
+
+ static func layout(_ layout: Layout) -> Self {
+ switch layout {
+ case .grid:
+ return Self(
pieceSizeRatio: .init(width: 1.0 / 4.0, height: 1.0 / 10.0),
piecesCornerRadiusRatio: .static(1),
piecesAlphaRatio: .static(0),
@@ -22,8 +33,8 @@ extension ShapeLayout {
containerScaleRatio: 0.1,
containerTranslationRatio: .init(x: 0.7, y: 0)
)
- case .snapshotSpace:
- return SnapshotTransformViewOptions(
+ case .space:
+ return Self(
pieceSizeRatio: .init(width: 1.0 / 3.0, height: 1.0 / 4.0),
piecesCornerRadiusRatio: .static(0.7),
piecesAlphaRatio: .aggregated([.rowBasedMirror(0.2), .columnBasedMirror(0.4)], +),
@@ -32,8 +43,8 @@ extension ShapeLayout {
containerScaleRatio: 0.1,
containerTranslationRatio: .init(x: 0.7, y: 0)
)
- case .snapshotChess:
- return SnapshotTransformViewOptions(
+ case .chess:
+ return Self(
pieceSizeRatio: .init(width: 1.0 / 5.0, height: 1.0 / 10.0),
piecesCornerRadiusRatio: .static(0.5),
piecesAlphaRatio: .columnBasedMirror(0.4),
@@ -42,8 +53,8 @@ extension ShapeLayout {
containerScaleRatio: 0.1,
containerTranslationRatio: .init(x: 0.7, y: 0)
)
- case .snapshotTiles:
- return SnapshotTransformViewOptions(
+ case .tiles:
+ return Self(
pieceSizeRatio: .init(width: 1, height: 1.0 / 10.0),
piecesCornerRadiusRatio: .static(0),
piecesAlphaRatio: .static(0.4),
@@ -52,8 +63,8 @@ extension ShapeLayout {
containerScaleRatio: 0.1,
containerTranslationRatio: .init(x: 1, y: 0)
)
- case .snapshotLines:
- return SnapshotTransformViewOptions(
+ case .lines:
+ return Self(
pieceSizeRatio: .init(width: 1, height: 1.0 / 16.0),
piecesCornerRadiusRatio: .static(0),
piecesAlphaRatio: .static(0.4),
@@ -62,8 +73,8 @@ extension ShapeLayout {
containerScaleRatio: 0.1,
containerTranslationRatio: .init(x: 0.8, y: 0)
)
- case .snapshotBars:
- return SnapshotTransformViewOptions(
+ case .bars:
+ return Self(
pieceSizeRatio: .init(width: 1.0 / 10.0, height: 1),
piecesCornerRadiusRatio: .static(1.2),
piecesAlphaRatio: .static(0.4),
@@ -72,8 +83,8 @@ extension ShapeLayout {
containerScaleRatio: 0.1,
containerTranslationRatio: .init(x: 1, y: 0)
)
- case .snapshotPuzzle:
- return SnapshotTransformViewOptions(
+ case .puzzle:
+ return Self(
pieceSizeRatio: .init(width: 1.0 / 4.0, height: 1.0 / 10.0),
piecesCornerRadiusRatio: .static(0),
piecesAlphaRatio: .aggregated([.rowOddEven(0.2, 0), .columnOddEven(0, 0.2)], +),
@@ -82,8 +93,8 @@ extension ShapeLayout {
containerScaleRatio: 0.2,
containerTranslationRatio: .init(x: 1, y: 0)
)
- case .snapshotFade:
- return SnapshotTransformViewOptions(
+ case .fade:
+ return Self(
pieceSizeRatio: .init(width: 1, height: 1.0 / 10.0),
piecesCornerRadiusRatio: .static(0.1),
piecesAlphaRatio: .rowBased(0.1),
@@ -92,9 +103,6 @@ extension ShapeLayout {
containerScaleRatio: 0.7,
containerTranslationRatio: .init(x: 1.9, y: 0)
)
- default:
- return nil
}
-
}
}
diff --git a/Lib/Snapshot/SnapshotTransformViewOptions.PiecePosition.swift b/Lib/Snapshot/SnapshotTransformViewOptions.PiecePosition.swift
index a28aeec..0333704 100644
--- a/Lib/Snapshot/SnapshotTransformViewOptions.PiecePosition.swift
+++ b/Lib/Snapshot/SnapshotTransformViewOptions.PiecePosition.swift
@@ -10,7 +10,7 @@ import UIKit
public extension SnapshotTransformViewOptions {
- class PiecePosition {
+ struct PiecePosition {
// MARK: Properties
diff --git a/Lib/Snapshot/SnapshotTransformViewOptions.PiecesValue.swift b/Lib/Snapshot/SnapshotTransformViewOptions.PiecesValue.swift
index 47a6f3c..4310611 100644
--- a/Lib/Snapshot/SnapshotTransformViewOptions.PiecesValue.swift
+++ b/Lib/Snapshot/SnapshotTransformViewOptions.PiecesValue.swift
@@ -11,7 +11,7 @@ import UIKit
public extension SnapshotTransformViewOptions {
enum PiecesValue {
-
+
// MARK: Cases
case columnBased(Type, reversed: Bool = false)
diff --git a/Lib/Stack/StackTransformView.swift b/Lib/Stack/StackTransformView.swift
index 90b0eb9..aa8e4e8 100644
--- a/Lib/Stack/StackTransformView.swift
+++ b/Lib/Stack/StackTransformView.swift
@@ -18,7 +18,7 @@ public protocol StackTransformView: TransformableView {
var cardView: UIView { get }
/// The view to apply blur effect on
- var blurViewHost: UIView { get }
+ var stackBlurViewHost: UIView { get }
/// the main function for applying transforms
func applyStackTransform(progress: CGFloat)
@@ -28,7 +28,7 @@ public protocol StackTransformView: TransformableView {
public extension StackTransformView {
/// The default value is the super view of `cardView`
- var blurViewHost: UIView {
+ var stackBlurViewHost: UIView {
cardView.superview ?? cardView
}
@@ -187,19 +187,18 @@ public extension StackTransformView {
cardView.transform = cardView.transform.rotated(by: angle)
}
-
- @available(iOS 10.0, *)
+
private func applyBlurEffect(progress: CGFloat) {
guard stackOptions.maxBlurEffectRadius > 0, stackOptions.blurEffectEnabled else {
- blurViewHost.subviews.first(where: { $0 is BlurEffectView })?.removeFromSuperview()
+ stackBlurViewHost.subviews.first(where: { $0 is BlurEffectView })?.removeFromSuperview()
return
}
let blurView: BlurEffectView
- if let view = blurViewHost.subviews.first(where: { $0 is BlurEffectView }) as? BlurEffectView {
+ if let view = stackBlurViewHost.subviews.first(where: { $0 is BlurEffectView }) as? BlurEffectView {
blurView = view
} else {
blurView = BlurEffectView()
- blurViewHost.fill(with: blurView)
+ stackBlurViewHost.fill(with: blurView)
}
let radius = max(progress, 0).interpolate(in: .init(0, CGFloat(stackOptions.maxStackSize)))
blurView.setBlurRadius(effect: UIBlurEffect(style: stackOptions.blurEffectStyle), radius: radius * stackOptions.maxBlurEffectRadius)
diff --git a/Samples/PagingLayoutSamples/Modules/Shapes/ShapeLayout+StackOptions.swift b/Lib/Stack/StackTransformViewOptions+Layout.swift
similarity index 76%
rename from Samples/PagingLayoutSamples/Modules/Shapes/ShapeLayout+StackOptions.swift
rename to Lib/Stack/StackTransformViewOptions+Layout.swift
index c13502d..35a7490 100644
--- a/Samples/PagingLayoutSamples/Modules/Shapes/ShapeLayout+StackOptions.swift
+++ b/Lib/Stack/StackTransformViewOptions+Layout.swift
@@ -1,19 +1,28 @@
//
-// ShapeLayout+StackOptions.swift
-// PagingLayoutSamples
+// StackTransformViewOptions+Layout.swift
+// CollectionViewPagingLayout
//
-// Created by Amir on 27/06/2020.
-// Copyright © 2020 Amir Khorsandi. All rights reserved.
+// Created by Amir on 28/03/2021.
+// Copyright © 2021 Amir Khorsandi. All rights reserved.
//
+import UIKit
import Foundation
-import CollectionViewPagingLayout
-extension ShapeLayout {
- var stackOptions: StackTransformViewOptions? {
- switch self {
- case .stackTransparent:
- return StackTransformViewOptions(
+public extension StackTransformViewOptions {
+ enum Layout: String, CaseIterable {
+ case transparent
+ case perspective
+ case rotary
+ case vortex
+ case reverse
+ case blur
+ }
+
+ static func layout(_ layout: Layout) -> Self {
+ switch layout {
+ case .transparent:
+ return Self(
scaleFactor: 0.12,
minScale: 0.0,
maxStackSize: 4,
@@ -23,8 +32,8 @@ extension ShapeLayout {
popAngle: .pi / 10,
popOffsetRatio: .init(width: -1.45, height: 0.3)
)
- case .stackPerspective:
- return StackTransformViewOptions(
+ case .perspective:
+ return Self(
scaleFactor: 0.1,
minScale: 0.2,
maxStackSize: 6,
@@ -36,8 +45,8 @@ extension ShapeLayout {
popOffsetRatio: .init(width: -1.45, height: 0.3),
stackPosition: CGPoint(x: 1, y: 0)
)
- case .stackRotary:
- return StackTransformViewOptions(
+ case .rotary:
+ return Self(
scaleFactor: -0.03,
minScale: 0.2,
maxStackSize: 3,
@@ -49,8 +58,8 @@ extension ShapeLayout {
popOffsetRatio: .init(width: -1.45, height: 0.4),
stackPosition: CGPoint(x: 0, y: 1)
)
- case .stackVortex:
- return StackTransformViewOptions(
+ case .vortex:
+ return Self(
scaleFactor: -0.15,
minScale: 0.2,
maxScale: nil,
@@ -64,8 +73,8 @@ extension ShapeLayout {
popOffsetRatio: .zero,
stackPosition: CGPoint(x: 0, y: 1)
)
- case .stackReverse:
- return StackTransformViewOptions(
+ case .reverse:
+ return Self(
scaleFactor: 0.1,
maxScale: nil,
maxStackSize: 4,
@@ -76,8 +85,8 @@ extension ShapeLayout {
stackPosition: CGPoint(x: -1, y: -0.2),
reverse: true
)
- case .stackBlur:
- return StackTransformViewOptions(
+ case .blur:
+ return Self(
scaleFactor: 0.1,
maxScale: nil,
maxStackSize: 7,
@@ -92,8 +101,6 @@ extension ShapeLayout {
blurEffectEnabled: true,
maxBlurEffectRadius: 0.08
)
- default:
- return nil
}
}
}
diff --git a/Lib/SwiftUI/PagePadding.swift b/Lib/SwiftUI/PagePadding.swift
new file mode 100644
index 0000000..0e2b6b5
--- /dev/null
+++ b/Lib/SwiftUI/PagePadding.swift
@@ -0,0 +1,30 @@
+//
+// PagePadding.swift
+// CollectionViewPagingLayout
+//
+// Created by Amir Khorsandi on 10/04/2021.
+// Copyright © 2021 Amir Khorsandi. All rights reserved.
+//
+
+import Foundation
+import UIKit
+
+/// Provides paddings around the page
+public struct PagePadding {
+
+ let top: Padding?
+ let left: Padding?
+ let bottom: Padding?
+ let right: Padding?
+
+ public enum Padding {
+ /// Creates a padding with an absolute point value.
+ case absolute(CGFloat)
+
+ /// Creates a padding that is computed as a fraction of the height of the container view.
+ case fractionalHeight(CGFloat)
+
+ /// Creates a padding that is computed as a fraction of the width of the container view.
+ case fractionalWidth(CGFloat)
+ }
+}
diff --git a/Lib/SwiftUI/PagingCollectionViewCell.swift b/Lib/SwiftUI/PagingCollectionViewCell.swift
new file mode 100644
index 0000000..fe156ed
--- /dev/null
+++ b/Lib/SwiftUI/PagingCollectionViewCell.swift
@@ -0,0 +1,180 @@
+//
+// PagingCollectionViewCell.swift
+// CollectionViewPagingLayout
+//
+// Created by Amir on 28/03/2021.
+// Copyright © 2021 Amir Khorsandi. All rights reserved.
+//
+
+import SwiftUI
+import UIKit
+
+class PagingCollectionViewCell: UICollectionViewCell {
+ typealias Parent = PagingCollectionViewController
+
+ // MARK: Properties
+
+ private weak var hostingController: UIHostingController?
+ private var viewBuilder: ((ValueType, CGFloat) -> Content)?
+ private var value: ValueType!
+ private var index: IndexPath!
+ private weak var parent: Parent?
+ private var parentBoundsObserver: NSKeyValueObservation?
+ private var parentSize: CGSize?
+
+ // MARK: Public functions
+
+ func update(value: ValueType, index: IndexPath, parent: Parent) {
+ self.parent = parent
+ viewBuilder = parent.pageViewBuilder
+ self.value = value
+ self.index = index
+ if hostingController != nil {
+ updateView()
+ } else {
+ let viewController = UIHostingController(rootView: updateView()!)
+ hostingController = viewController
+ viewController.view.backgroundColor = .clear
+
+ parent.addChild(viewController)
+ contentView.addSubview(viewController.view)
+ viewController.view.translatesAutoresizingMaskIntoConstraints = false
+
+ parentBoundsObserver = parent.view
+ .observe(\.bounds, options: [.initial, .new, .old, .prior]) { [weak self] _, _ in
+ self?.updatePagePaddings()
+ }
+
+ viewController.didMove(toParent: parent)
+ viewController.view.layoutIfNeeded()
+ }
+ }
+
+ // MARK: Private functions
+
+ @discardableResult private func updateView(progress: CGFloat? = nil) -> Content? {
+ guard let viewBuilder = viewBuilder
+ else { return nil }
+
+ let view = viewBuilder(value, progress ?? 0)
+ hostingController?.rootView = view
+ hostingController?.view.layoutIfNeeded()
+ return view
+ }
+
+ private func updatePagePaddings() {
+ guard let parent = parent,
+ let viewController = hostingController,
+ parent.view.bounds.size != parentSize
+ else { return }
+
+ parentSize = parent.view.bounds.size
+
+ func constraint(_ first: NSLayoutAnchor,
+ _ second: NSLayoutAnchor,
+ _ paddingKeyPath: KeyPath,
+ _ inside: Bool) {
+ let padding = parent.modifierData?.pagePadding?[keyPath: paddingKeyPath] ?? .absolute(0)
+ let constant: CGFloat
+ switch padding {
+ case .fractionalWidth(let fraction):
+ constant = parent.view.bounds.size.width * fraction
+ case .fractionalHeight(let fraction):
+ constant = parent.view.bounds.size.height * fraction
+ case .absolute(let absolute):
+ constant = absolute
+ }
+ let identifier = "pagePaddingConstraint_\(inside)_\(T.self)"
+ if let constraint = contentView.constraints.first(where: { $0.identifier == identifier }) ??
+ viewController.view.constraints.first(where: { $0.identifier == identifier }) {
+ constraint.constant = constant * (inside ? 1 : -1)
+ } else {
+ let constraint = first.constraint(equalTo: second, constant: constant * (inside ? 1 : -1))
+ constraint.identifier = identifier
+ constraint.isActive = true
+ }
+ }
+
+ constraint(contentView.leadingAnchor, viewController.view.leadingAnchor, \.left, false)
+ constraint(contentView.trailingAnchor, viewController.view.trailingAnchor, \.right, true)
+ constraint(contentView.topAnchor, viewController.view.topAnchor, \.top, false)
+ constraint(contentView.bottomAnchor, viewController.view.bottomAnchor, \.bottom, true)
+ }
+}
+
+extension PagingCollectionViewCell: TransformableView,
+ ScaleTransformView,
+ StackTransformView,
+ SnapshotTransformView {
+ var scalableView: UIView {
+ hostingController?.view ?? contentView
+ }
+
+ var cardView: UIView {
+ hostingController?.view ?? contentView
+ }
+
+ var targetView: UIView {
+ hostingController?.view ?? contentView
+ }
+
+ var selectableView: UIView? {
+ scalableView
+ }
+
+ var scaleOptions: ScaleTransformViewOptions {
+ parent?.modifierData?.scaleOptions ?? .init()
+ }
+
+ var stackOptions: StackTransformViewOptions {
+ parent?.modifierData?.stackOptions ?? .init()
+ }
+
+ var snapshotOptions: SnapshotTransformViewOptions {
+ parent?.modifierData?.snapshotOptions ?? .init()
+ }
+
+ func transform(progress: CGFloat) {
+ if parent?.modifierData?.scaleOptions != nil {
+ applyScaleTransform(progress: progress)
+ }
+ if parent?.modifierData?.stackOptions != nil {
+ applyStackTransform(progress: progress)
+ }
+ if parent?.modifierData?.snapshotOptions != nil {
+ if let snapshot = getSnapshot() {
+ applySnapshotTransform(snapshot: snapshot, progress: progress)
+ }
+ }
+ updateView(progress: progress)
+ }
+
+ func zPosition(progress: CGFloat) -> Int {
+ parent?.modifierData?.zPositionProvider?(progress) ?? Int(-abs(round(progress)))
+ }
+
+ var snapshotIdentifier: String {
+ if let snapshotIdentifier = parent?.modifierData?.snapshotIdentifier {
+ return snapshotIdentifier(index.item, hostingController?.view)
+ }
+
+ var identifier = String(describing: value.id)
+
+ if let scrollView = targetView as? UIScrollView {
+ identifier.append("\(scrollView.contentOffset)")
+ }
+
+ if let scrollView = targetView.subviews.first(where: { $0 is UIScrollView }) as? UIScrollView {
+ identifier.append("\(scrollView.contentOffset)")
+ }
+
+ return identifier
+ }
+
+ func canReuse(snapshot: SnapshotContainerView) -> Bool {
+ if let canReuse = parent?.modifierData?.canReuseSnapshot {
+ return canReuse(snapshot, hostingController?.view)
+ }
+ return snapshot.snapshotSize == targetView.bounds.size
+ }
+}
diff --git a/Lib/SwiftUI/PagingCollectionViewController.swift b/Lib/SwiftUI/PagingCollectionViewController.swift
new file mode 100644
index 0000000..3969f1e
--- /dev/null
+++ b/Lib/SwiftUI/PagingCollectionViewController.swift
@@ -0,0 +1,175 @@
+//
+// PagingCollectionViewController.swift
+// CollectionViewPagingLayout
+//
+// Created by Amir on 28/03/2021.
+// Copyright © 2021 Amir Khorsandi. All rights reserved.
+//
+
+import UIKit
+import SwiftUI
+
+public class PagingCollectionViewController: UIViewController,
+ UICollectionViewDataSource,
+ CollectionViewPagingLayoutDelegate,
+ UICollectionViewDelegate,
+ UIScrollViewDelegate {
+
+ // MARK: Properties
+
+ var modifierData: PagingCollectionViewModifierData?
+ var pageViewBuilder: ((ValueType, CGFloat) -> PageContent)!
+ var onCurrentPageChanged: ((Int) -> Void)?
+
+ private var collectionView: UICollectionView!
+ private var list: [ValueType] = []
+ private let layout = CollectionViewPagingLayout()
+
+
+ // MARK: UIViewController
+
+ public override func viewDidLoad() {
+ super.viewDidLoad()
+ view.backgroundColor = .clear
+ setupCollectionView()
+ }
+
+
+ // MARK: Public functions
+
+ public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+ list.count
+ }
+
+ public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+ let cell: PagingCollectionViewCell = collectionView.dequeueReusableCellClass(for: indexPath)
+ cell.update(value: list[indexPath.row], index: indexPath, parent: self)
+ return cell
+ }
+
+ public func onCurrentPageChanged(layout: CollectionViewPagingLayout, currentPage: Int) {
+ onCurrentPageChanged?(currentPage)
+ }
+
+
+ // MARK: Internal functions
+
+ func update(list: [ValueType], currentIndex: Int?) {
+ var needsUpdate = false
+
+ if let self = self as? PagingCollectionViewControllerEquatableList {
+ needsUpdate = !self.isListSame(as: list)
+ } else {
+ let oldIds = self.list.map(\.id)
+ needsUpdate = list.map(\.id) != oldIds
+ }
+ self.list = list
+ if needsUpdate {
+ collectionView?.reloadData()
+ layout.invalidateLayoutInBatchUpdate(invalidateOffset: true)
+ }
+ let index = currentIndex ?? layout.currentPage
+ if index < list.count {
+ guard index != layout.currentPage else { return }
+ view.isUserInteractionEnabled = false
+ layout.setCurrentPage(index) { [weak view] in
+ view?.isUserInteractionEnabled = true
+ }
+ } else {
+ layout.invalidateLayoutInBatchUpdate()
+ }
+ }
+
+ // MARK: UICollectionViewDelegate
+
+ public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+ modifierData?.onTapPage?(indexPath.row)
+
+ if modifierData?.goToSelectedPage ?? true {
+ (collectionView.collectionViewLayout as? CollectionViewPagingLayout)?.setCurrentPage(indexPath.row)
+ }
+ }
+
+
+ // MARK: Private functions
+
+ private func setupCollectionView() {
+ collectionView = UICollectionView(
+ frame: view.frame,
+ collectionViewLayout: layout
+ )
+ layout.delegate = self
+ collectionView.backgroundColor = .clear
+ collectionView.registerClass(PagingCollectionViewCell.self)
+ collectionView.dataSource = self
+ view.fill(with: collectionView)
+ layout.numberOfVisibleItems = modifierData?.numberOfVisibleItems
+ layout.scrollDirection = modifierData?.scrollDirection ?? layout.scrollDirection
+ layout.defaultAnimator = modifierData?.animator
+ layout.transparentAttributeWhenCellNotLoaded = modifierData?.transparentAttributeWhenCellNotLoaded ?? layout.transparentAttributeWhenCellNotLoaded
+ collectionView.delegate = self
+
+ collectionView.showsHorizontalScrollIndicator = false
+ collectionView.showsVerticalScrollIndicator = false
+ collectionView.isPagingEnabled = true
+ modifierData?.collectionViewProperties.forEach { property in
+ if let keyPath: WritableKeyPath = property.getKey(),
+ let value: Bool = property.getValue() {
+ collectionView[keyPath: keyPath] = value
+ }
+ if let keyPath: WritableKeyPath = property.getKey(),
+ let value: UIView = property.getValue() {
+ collectionView[keyPath: keyPath] = value
+ }
+ if let keyPath: WritableKeyPath = property.getKey(),
+ let value: UIColor = property.getValue() {
+ collectionView[keyPath: keyPath] = value
+ }
+ if let keyPath: WritableKeyPath = property.getKey(),
+ let value: UIScrollView.ContentInsetAdjustmentBehavior = property.getValue() {
+ collectionView[keyPath: keyPath] = value
+ }
+ if let keyPath: WritableKeyPath = property.getKey(),
+ let value: UIEdgeInsets = property.getValue() {
+ collectionView[keyPath: keyPath] = value
+ }
+ if let keyPath: WritableKeyPath = property.getKey(),
+ let value: CGFloat = property.getValue() {
+ collectionView[keyPath: keyPath] = value
+ }
+ if let keyPath: WritableKeyPath = property.getKey(),
+ let value: CGSize = property.getValue() {
+ collectionView[keyPath: keyPath] = value
+ }
+ }
+ }
+
+
+}
+
+private protocol PagingCollectionViewControllerEquatableList {
+ func isListSame(as list: [T]) -> Bool
+}
+
+extension PagingCollectionViewController: PagingCollectionViewControllerEquatableList where ValueType: Equatable {
+ func isListSame(as list: [T]) -> Bool {
+ self.list == (list as? [ValueType])
+ }
+}
+
+private extension UICollectionView {
+ func registerClass(_ cellType: T.Type, reuseIdentifier: String = T.reuseIdentifier) {
+ register(cellType, forCellWithReuseIdentifier: reuseIdentifier)
+ }
+
+ func dequeueReusableCellClass(for indexPath: IndexPath, type: T.Type? = nil, reuseIdentifier: String = T.reuseIdentifier) -> T {
+ (dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as? T)!
+ }
+}
+
+
+private extension UICollectionViewCell {
+ static var reuseIdentifier: String {
+ String(describing: self)
+ }
+}
diff --git a/Lib/SwiftUI/PagingCollectionViewControllerBuilder.swift b/Lib/SwiftUI/PagingCollectionViewControllerBuilder.swift
new file mode 100644
index 0000000..ed15a56
--- /dev/null
+++ b/Lib/SwiftUI/PagingCollectionViewControllerBuilder.swift
@@ -0,0 +1,78 @@
+//
+// PagingCollectionViewControllerBuilder.swift
+// CollectionViewPagingLayout
+//
+// Created by Amir on 28/03/2021.
+// Copyright © 2021 Amir Khorsandi. All rights reserved.
+//
+
+import SwiftUI
+
+public class PagingCollectionViewControllerBuilder {
+
+ public typealias ViewController = PagingCollectionViewController
+
+ // MARK: Properties
+
+ let data: [ValueType]
+ let pageViewBuilder: (ValueType, CGFloat) -> PageContent
+ let selection: Binding?
+
+ var modifierData: PagingCollectionViewModifierData = .init()
+
+ weak var viewController: ViewController?
+
+
+ // MARK: Lifecycle
+
+ public init(
+ data: [ValueType],
+ pageViewBuilder: @escaping (ValueType, CGFloat) -> PageContent,
+ selection: Binding?
+ ) {
+ self.data = data
+ self.pageViewBuilder = pageViewBuilder
+ self.selection = selection
+ }
+
+ public init(
+ data: [ValueType],
+ pageViewBuilder: @escaping (ValueType) -> PageContent,
+ selection: Binding?
+ ) {
+ self.data = data
+ self.pageViewBuilder = { value, _ in pageViewBuilder(value) }
+ self.selection = selection
+ }
+
+
+ // MARK: Public functions
+
+ func make() -> ViewController {
+ let viewController = ViewController()
+ viewController.pageViewBuilder = pageViewBuilder
+ viewController.modifierData = modifierData
+ viewController.update(list: data, currentIndex: nil)
+ setupOnCurrentPageChanged(viewController)
+ return viewController
+ }
+
+ func update(viewController: ViewController) {
+ let selectedIndex = data.enumerated().first {
+ $0.element.id == selection?.wrappedValue
+ }?.offset
+ viewController.modifierData = modifierData
+ viewController.update(list: data, currentIndex: selectedIndex)
+ setupOnCurrentPageChanged(viewController)
+ }
+
+
+ // MARK: Private functions
+
+ private func setupOnCurrentPageChanged(_ viewController: ViewController) {
+ viewController.onCurrentPageChanged = { [data, selection] in
+ guard $0 >= 0 && $0 < data.count else { return }
+ selection?.wrappedValue = data[$0].id
+ }
+ }
+}
diff --git a/Lib/SwiftUI/PagingCollectionViewModifierData.swift b/Lib/SwiftUI/PagingCollectionViewModifierData.swift
new file mode 100644
index 0000000..0406559
--- /dev/null
+++ b/Lib/SwiftUI/PagingCollectionViewModifierData.swift
@@ -0,0 +1,45 @@
+//
+// PagingCollectionViewModifierData.swift
+// CollectionViewPagingLayout
+//
+// Created by Amir Khorsandi on 10/04/2021.
+// Copyright © 2021 Amir Khorsandi. All rights reserved.
+//
+
+import Foundation
+import UIKit
+
+struct PagingCollectionViewModifierData {
+ var scaleOptions: ScaleTransformViewOptions?
+ var stackOptions: StackTransformViewOptions?
+ var snapshotOptions: SnapshotTransformViewOptions?
+ var snapshotIdentifier: ((Int, UIView?) -> String)?
+ var canReuseSnapshot: ((SnapshotContainerView, UIView?) -> Bool)?
+ var numberOfVisibleItems: Int?
+ var zPositionProvider: ((CGFloat) -> Int)?
+ var animator: ViewAnimator?
+ var goToSelectedPage: Bool?
+ var collectionViewProperties: [CollectionViewPropertyProtocol] = []
+ var onTapPage: ((Int) -> Void)?
+ var scrollDirection: UICollectionView.ScrollDirection?
+ var pagePadding: PagePadding?
+ var transparentAttributeWhenCellNotLoaded: Bool?
+}
+
+protocol CollectionViewPropertyProtocol {
+ func getKey() -> WritableKeyPath?
+ func getValue() -> T?
+}
+
+struct CollectionViewProperty: CollectionViewPropertyProtocol {
+ let keyPath: WritableKeyPath
+ let value: T
+
+ func getKey() -> WritableKeyPath? {
+ keyPath as? WritableKeyPath
+ }
+
+ func getValue() -> T? {
+ value as? T
+ }
+}
diff --git a/Lib/SwiftUI/ScalePageView.swift b/Lib/SwiftUI/ScalePageView.swift
new file mode 100644
index 0000000..937324b
--- /dev/null
+++ b/Lib/SwiftUI/ScalePageView.swift
@@ -0,0 +1,37 @@
+//
+// ScalePageView.swift
+// CollectionViewPagingLayout
+//
+// Created by Amir on 28/03/2021.
+// Copyright © 2021 Amir Khorsandi. All rights reserved.
+//
+
+import Foundation
+import SwiftUI
+
+public struct ScalePageView: UIViewControllerRepresentable, TransformPageViewProtocol {
+
+ // MARK: Properties
+
+ public var builder: Builder
+
+
+ // MARK: Lifecycle
+
+ public init(
+ _ data: [ValueType],
+ selection: Binding? = nil,
+ @ViewBuilder viewBuilder: @escaping (ValueType) -> PageContent
+ ) {
+ builder = .init(data: data, pageViewBuilder: viewBuilder, selection: selection)
+ builder.modifierData.scaleOptions = .init()
+ }
+}
+
+
+public extension ScalePageView {
+ func options(_ options: ScaleTransformViewOptions) -> Self {
+ builder.modifierData.scaleOptions = options
+ return self
+ }
+}
diff --git a/Lib/SwiftUI/SnapshotPageView.swift b/Lib/SwiftUI/SnapshotPageView.swift
new file mode 100644
index 0000000..f2c1e87
--- /dev/null
+++ b/Lib/SwiftUI/SnapshotPageView.swift
@@ -0,0 +1,60 @@
+//
+// SnapshotPageView.swift
+// CollectionViewPagingLayout
+//
+// Created by Amir on 28/03/2021.
+// Copyright © 2021 Amir Khorsandi. All rights reserved.
+//
+
+import Foundation
+import SwiftUI
+
+public struct SnapshotPageView: UIViewControllerRepresentable, TransformPageViewProtocol {
+
+ // MARK: Properties
+
+ public var builder: Builder
+
+
+ // MARK: Lifecycle
+
+ public init(
+ _ data: [ValueType],
+ selection: Binding? = nil,
+ @ViewBuilder viewBuilder: @escaping (ValueType) -> PageContent
+ ) {
+ builder = .init(data: data, pageViewBuilder: viewBuilder, selection: selection)
+ builder.modifierData.snapshotOptions = .init()
+ }
+}
+
+
+public extension SnapshotPageView {
+ func options(_ options: SnapshotTransformViewOptions) -> Self {
+ builder.modifierData.snapshotOptions = options
+ return self
+ }
+}
+
+public extension SnapshotPageView {
+ /// A unique identifier for the snapshot, a new snapshot won't be made if
+ /// there is a cashed snapshot with the same identifier
+ /// - Parameter index: The index of item
+ /// - Parameter view: The `UIView` converted from `PageContent`
+ /// - Returns: Self
+ func snapshotIdentifier(_ snapshotIdentifier: @escaping (_ index: Int, _ view: UIView?) -> String) -> Self {
+ builder.modifierData.snapshotIdentifier = snapshotIdentifier
+ return self
+ }
+}
+
+public extension SnapshotPageView {
+ /// Check if the snapshot can be reused
+ /// - Parameter snapshotContainer: The container for snapshot pieces, see `SnapshotContainerView`
+ /// - Parameter view: The `UIView` converted from `PageContent`
+ /// - Returns: Self
+ func canReuseSnapshot(_ canReuseSnapshot: @escaping (_ snapshotContainer: SnapshotContainerView, _ view: UIView?) -> Bool) -> Self {
+ builder.modifierData.canReuseSnapshot = canReuseSnapshot
+ return self
+ }
+}
diff --git a/Lib/SwiftUI/StackPageView.swift b/Lib/SwiftUI/StackPageView.swift
new file mode 100644
index 0000000..6562acc
--- /dev/null
+++ b/Lib/SwiftUI/StackPageView.swift
@@ -0,0 +1,37 @@
+//
+// StackPageView.swift
+// CollectionViewPagingLayout
+//
+// Created by Amir on 28/03/2021.
+// Copyright © 2021 Amir Khorsandi. All rights reserved.
+//
+
+import Foundation
+import SwiftUI
+
+public struct StackPageView: UIViewControllerRepresentable, TransformPageViewProtocol {
+
+ // MARK: Properties
+
+ public var builder: Builder
+
+
+ // MARK: Lifecycle
+
+ public init(
+ _ data: [ValueType],
+ selection: Binding? = nil,
+ @ViewBuilder viewBuilder: @escaping (ValueType) -> PageContent
+ ) {
+ builder = .init(data: data, pageViewBuilder: viewBuilder, selection: selection)
+ builder.modifierData.stackOptions = .init()
+ }
+}
+
+
+public extension StackPageView {
+ func options(_ options: StackTransformViewOptions) -> Self {
+ builder.modifierData.stackOptions = options
+ return self
+ }
+}
diff --git a/Lib/SwiftUI/TransformPageView.swift b/Lib/SwiftUI/TransformPageView.swift
new file mode 100644
index 0000000..564e728
--- /dev/null
+++ b/Lib/SwiftUI/TransformPageView.swift
@@ -0,0 +1,27 @@
+//
+// TransformPageView.swift
+// CollectionViewPagingLayout
+//
+// Created by Amir on 28/03/2021.
+// Copyright © 2021 Amir Khorsandi. All rights reserved.
+//
+import Foundation
+import SwiftUI
+
+public struct TransformPageView: UIViewControllerRepresentable, TransformPageViewProtocol {
+
+ // MARK: Properties
+
+ public var builder: Builder
+
+
+ // MARK: Lifecycle
+
+ public init(
+ _ data: [ValueType],
+ selection: Binding? = nil,
+ @ViewBuilder viewBuilder: @escaping (ValueType, CGFloat) -> PageContent
+ ) {
+ builder = .init(data: data, pageViewBuilder: viewBuilder, selection: selection)
+ }
+}
diff --git a/Lib/SwiftUI/TransformPageViewProtocol.swift b/Lib/SwiftUI/TransformPageViewProtocol.swift
new file mode 100644
index 0000000..a0dd393
--- /dev/null
+++ b/Lib/SwiftUI/TransformPageViewProtocol.swift
@@ -0,0 +1,172 @@
+//
+// TransformPageViewProtocol.swift
+// CollectionViewPagingLayout
+//
+// Created by Amir on 28/03/2021.
+// Copyright © 2021 Amir Khorsandi. All rights reserved.
+//
+
+import Foundation
+import SwiftUI
+
+public protocol TransformPageViewProtocol {
+ associatedtype ValueType: Identifiable
+ associatedtype PageContent: View
+
+ typealias Builder = PagingCollectionViewControllerBuilder
+
+ var builder: Builder { get }
+}
+
+
+public extension TransformPageViewProtocol {
+ func numberOfVisibleItems(_ count: Int) -> Self {
+ self.builder.modifierData.numberOfVisibleItems = count
+ return self
+ }
+
+ func zPosition(_ zPosition: @escaping (CGFloat) -> Int) -> Self {
+ self.builder.modifierData.zPositionProvider = zPosition
+ return self
+ }
+
+ func onTapPage(_ onTapPage: @escaping (ValueType.ID) -> Void) -> Self {
+ self.builder.modifierData.onTapPage = { index in
+ guard index < builder.data.count else { return }
+ onTapPage(builder.data[index].id)
+ }
+ return self
+ }
+
+ func pagePadding(top: PagePadding.Padding? = nil,
+ left: PagePadding.Padding? = nil,
+ bottom: PagePadding.Padding? = nil,
+ right: PagePadding.Padding? = nil) -> Self {
+ let current = self.builder.modifierData.pagePadding
+ let topPadding: PagePadding.Padding? = {
+ if let top = top {
+ return top
+ } else {
+ return current?.top
+ }
+ }()
+
+ let leftPadding: PagePadding.Padding? = {
+ if let left = left {
+ return left
+ } else {
+ return current?.left
+ }
+ }()
+
+ let bottomPadding: PagePadding.Padding? = {
+ if let bottom = bottom {
+ return bottom
+ } else {
+ return current?.bottom
+ }
+ }()
+
+ let rightPadding: PagePadding.Padding? = {
+ if let right = right {
+ return right
+ } else {
+ return current?.right
+ }
+ }()
+
+ self.builder.modifierData.pagePadding = .init(
+ top: topPadding,
+ left: leftPadding,
+ bottom: bottomPadding,
+ right: rightPadding
+ )
+ return self
+ }
+
+ func pagePadding(_ padding: PagePadding.Padding? = nil) -> Self {
+ pagePadding(vertical: padding, horizontal: padding)
+ }
+
+ func pagePadding(vertical: PagePadding.Padding? = nil,
+ horizontal: PagePadding.Padding? = nil) -> Self {
+ let current = self.builder.modifierData.pagePadding
+
+ let topPadding: PagePadding.Padding? = {
+ if let top = vertical {
+ return top
+ } else {
+ return current?.top
+ }
+ }()
+
+ let leftPadding: PagePadding.Padding? = {
+ if let left = horizontal {
+ return left
+ } else {
+ return current?.left
+ }
+ }()
+
+ let bottomPadding: PagePadding.Padding? = {
+ if let bottom = vertical {
+ return bottom
+ } else {
+ return current?.bottom
+ }
+ }()
+
+ let rightPadding: PagePadding.Padding? = {
+ if let right = horizontal {
+ return right
+ } else {
+ return current?.right
+ }
+ }()
+
+ self.builder.modifierData.pagePadding = .init(
+ top: topPadding,
+ left: leftPadding,
+ bottom: bottomPadding,
+ right: rightPadding
+ )
+ return self
+ }
+
+ func animator(_ animator: ViewAnimator) -> Self {
+ self.builder.modifierData.animator = animator
+ return self
+ }
+
+ func scrollToSelectedPage(_ goToSelectedPage: Bool) -> Self {
+ self.builder.modifierData.goToSelectedPage = goToSelectedPage
+ return self
+ }
+
+ func scrollDirection(_ direction: UICollectionView.ScrollDirection) -> Self {
+ self.builder.modifierData.scrollDirection = direction
+ return self
+ }
+
+ func hideCellWhenNotLoaded(_ value: Bool) -> Self {
+ self.builder.modifierData.transparentAttributeWhenCellNotLoaded = value
+ return self
+ }
+
+ func collectionView(_ key: WritableKeyPath, _ value: T) -> Self {
+ let property = CollectionViewProperty(keyPath: key, value: value)
+ self.builder.modifierData.collectionViewProperties.append(property)
+ return self
+ }
+}
+
+
+public extension TransformPageViewProtocol where Self: UIViewControllerRepresentable {
+ func makeUIViewController(context: UIViewControllerRepresentableContext) -> Builder.ViewController {
+ builder.make()
+ }
+
+ func updateUIViewController(_ uiViewController: Builder.ViewController, context: Context) {
+ builder.update(viewController: uiViewController)
+ }
+}
diff --git a/Lib/TransformableView.swift b/Lib/TransformableView.swift
index 9fd6f8c..f1217d1 100644
--- a/Lib/TransformableView.swift
+++ b/Lib/TransformableView.swift
@@ -11,15 +11,16 @@ import UIKit
public protocol TransformableView {
- /// The view for detecting tap gesture
- /// when you call `CollectionViewPagingLayout.configureTapOnCollectionView()`
- /// a tap gesture will be added to the CollectionView and when the user tap on it
- /// it checks if the tap location was in this view frame it will trigger
- /// `CollectionViewPagingLayoutDelegate.collectionViewPagingLayout(_ layout:, didSelectItemAt indexPath:)`
+ /// The view for detecting gestures
+ ///
+ /// If you want to handle it manually return `nil`
var selectableView: UIView? { get }
/// Sends a float value based on the position of the view (cell)
/// if the view is in the center of CollectionView it sends 0
+ /// the value could be negative or positive and that represents the distance to the center of your CollectionView.
+ /// for instance `1` means the distance between the center of the cell and the center of your CollectionView
+ /// is equal to your CollectionView width.
///
/// - Parameter progress: the interpolated progress for the cell view
func transform(progress: CGFloat)
@@ -47,7 +48,20 @@ public extension TransformableView where Self: UICollectionViewCell {
/// Default `selectableView` for `UICollectionViewCell` is the first subview of
/// `contentView` or the content view itself if there is no subview
var selectableView: UIView? {
- contentView.subviews.first
+ contentView.subviews.first ?? contentView
}
}
+
+public extension UICollectionViewCell {
+ /// This method transfers the event to `selectableView`
+ /// this is necessary since cells are on top of each other and they fill the whole collectionView frame
+ /// Without this, only the first visible cell is selectable
+ // swiftlint:disable:next override_in_extension
+ override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
+ if let view = (self as? TransformableView)?.selectableView {
+ return view.hitTest(convert(point, to: view), with: event)
+ }
+ return super.hitTest(point, with: event)
+ }
+}
diff --git a/Lib/ViewAnimator.swift b/Lib/ViewAnimator.swift
new file mode 100644
index 0000000..20a4b2f
--- /dev/null
+++ b/Lib/ViewAnimator.swift
@@ -0,0 +1,133 @@
+//
+// ViewAnimator.swift
+// CollectionViewPagingLayout
+//
+// Created by Amir on 04/04/2021
+// Copyright © 2021 Amir Khorsandi. All rights reserved.
+//
+
+import QuartzCore
+
+/// Simple protocol to define an animator
+public protocol ViewAnimator {
+ /// Animate
+ /// - Parameter animations: Closure to animate
+ /// - parameter progress: the animation progress, between 0.0 - 1.0
+ /// - parameter finished: animation finished state, set to true at the end
+ @discardableResult func animate(animations: @escaping (_ progress: Double, _ finished: Bool) -> Void) -> ViewAnimatorCancelable?
+}
+
+
+public protocol ViewAnimatorCancelable {
+
+ /// Cancel the animation with changing progress
+ func cancel()
+}
+
+
+/// Default implementation for `ViewAnimator`
+public class DefaultViewAnimator: ViewAnimator {
+
+ private let animationDuration: TimeInterval
+ private let curve: Curve
+
+ private var displayLink: CADisplayLink?
+ private var start: CFTimeInterval!
+ private var animationsClosure: ((Double, Bool) -> Void)?
+
+ private var duration: TimeInterval {
+ #if targetEnvironment(simulator)
+ return Double(animationDragCoefficient()) * animationDuration
+ #else
+ return animationDuration
+ #endif
+ }
+
+ public init(_ duration: TimeInterval, curve: Curve) {
+ self.animationDuration = duration
+ self.curve = curve
+ }
+
+ public func animate(animations: @escaping (Double, Bool) -> Void) -> ViewAnimatorCancelable? {
+ if !Thread.isMainThread {
+ fatalError("only from main thread")
+ }
+ guard duration > 0 else {
+ animations(1.0, true)
+ return nil
+ }
+ invalidateDisplayLink()
+ start = CACurrentMediaTime()
+ animationsClosure = animations
+ let displayLink = CADisplayLink(target: self, selector: #selector(update))
+ displayLink.add(to: .current, forMode: .common)
+ self.displayLink = displayLink
+ return Cancelable { [weak self] in
+ self?.invalidateDisplayLink()
+ }
+ }
+
+ @objc private func update() {
+ guard displayLink != nil else { return }
+ let delta = CACurrentMediaTime() - start
+ let progress = curve.fromLinear(progress: delta / duration)
+
+ animationsClosure?(progress, false)
+ if delta / duration > 1 {
+ animationsClosure?(progress, true)
+ invalidateDisplayLink()
+ }
+ }
+
+ private func invalidateDisplayLink() {
+ displayLink?.invalidate()
+ displayLink = nil
+ }
+}
+
+public extension DefaultViewAnimator {
+ enum Curve {
+ case linear, parametric, easeInOut, easeIn, easeOut
+
+ func fromLinear(progress: Double) -> Double {
+ let p = min(max(progress, 0), 1)
+ let result: Double
+ switch self {
+ case .linear:
+ result = p
+ case .parametric:
+ result = ((p * p) / (2.0 * ((p * p) - p) + 1.0))
+ case .easeInOut:
+ if p < 0.5 {
+ result = 2 * p * p
+ } else {
+ result = (-2 * p * p) + (4 * p) - 1
+ }
+ case .easeIn:
+ result = -p * (p - 2)
+ case .easeOut:
+ result = p * p
+ }
+ return min(max(result, 0), 1)
+ }
+
+ }
+}
+
+extension DefaultViewAnimator {
+ private struct Cancelable: ViewAnimatorCancelable {
+ var onCancel: () -> Void
+
+ func cancel() {
+ onCancel()
+ }
+ }
+}
+
+#if targetEnvironment(simulator)
+@_silgen_name("UIAnimationDragCoefficient") func UIAnimationDragCoefficient() -> Float
+
+private func animationDragCoefficient() -> Float {
+ UIAnimationDragCoefficient()
+}
+#endif
diff --git a/Package.swift b/Package.swift
index 38c2cf1..ec040a6 100644
--- a/Package.swift
+++ b/Package.swift
@@ -5,7 +5,7 @@ import PackageDescription
let package = Package(
name: "CollectionViewPagingLayout",
platforms: [
- .iOS(.v8)
+ .iOS(.v13)
],
products: [
.library(
@@ -15,7 +15,6 @@ let package = Package(
targets: [
.target(
name: "CollectionViewPagingLayout",
- path: "Lib",
- exclude: ["Samples"])
+ path: "Lib")
]
)
diff --git a/PrivacyPolicy.md b/PrivacyPolicy.md
index 6c4ec37..6afdebc 100644
--- a/PrivacyPolicy.md
+++ b/PrivacyPolicy.md
@@ -1,5 +1,5 @@
## Privacy Policy
-- This app doesn't collect any data and by removing the app all the data will be permanently removed.
-- This app doesn't send any data to any public or private server
+- This app doesn't collect any data and by removing the app all local data will be permanently removed.
+- This app doesn't send any data to any server
diff --git a/README.md b/README.md
index cdc9587..81ef52c 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# CollectionViewPagingLayout
+# CollectionViewPagingLayout - PagingView for SwiftUI
[](http://cocoapods.org/pods/CollectionViewPagingLayout)

@@ -6,65 +6,86 @@
[](https://github.com/Carthage/Carthage)
[](https://github.com/apple/swift-package-manager)
-## Layout Designer
-
+## Previews
+
+### Layout Designer
+[
](https://apps.apple.com/nl/app/layout-designer/id1507238011?l=en&mt=12)
-### SnapshotTransformView
+#### Custom implementations, UIKit: `TransformableView`, SwiftUI: `TransformPageView`
+Click on image to see the code
-
-
-
-
-
-
-
-
-
-### ScaleTransformView
+[
](https://github.com/amirdew/CollectionViewPagingLayout/tree/master/Samples/PagingLayoutSamples/Modules/UIKit/Fruits)
+[
](https://github.com/amirdew/CollectionViewPagingLayout/tree/master/Samples/PagingLayoutSamples/Modules/UIKit/Gallery)
+[
](https://github.com/amirdew/CollectionViewPagingLayout/tree/master/Samples/PagingLayoutSamples/Modules/UIKit/Cards)
+[
](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Samples/PagingLayoutSamples/Modules/SwiftUI/DevicesView.swift)
+[
](https://github.com/amirdew/CollectionViewPagingLayout/tree/master/Samples/PagingLayoutSamples/Modules/SwiftUI/WeatherTabView)
+
+
+#### UIKit: `SnapshotTransformView`, SwiftUI: `SnapshotPageView`
+
+
-
-
-
-
-
-
-
-
-
-### StackTransformView
+[
](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Snapshot/SnapshotTransformViewOptions%2BLayout.swift#L14)
+[
](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Snapshot/SnapshotTransformViewOptions%2BLayout.swift#L15)
+[
](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Snapshot/SnapshotTransformViewOptions%2BLayout.swift#L16)
+[
](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Snapshot/SnapshotTransformViewOptions%2BLayout.swift#L17)
+[
](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Snapshot/SnapshotTransformViewOptions%2BLayout.swift#L18)
+[
](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Snapshot/SnapshotTransformViewOptions%2BLayout.swift#L19)
+[
](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Snapshot/SnapshotTransformViewOptions%2BLayout.swift#L20)
+[
](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Snapshot/SnapshotTransformViewOptions%2BLayout.swift#L21)
+
+
+#### UIKit: `ScaleTransformView`, SwiftUI: `ScalePageView`
-
-
-
-
-
-
+
+[
](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Scale/ScaleTransformViewOptions%2BLayout.swift#L14)
+[
](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Scale/ScaleTransformViewOptions%2BLayout.swift#L15)
+[
](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Scale/ScaleTransformViewOptions%2BLayout.swift#L16)
+[
](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Scale/ScaleTransformViewOptions%2BLayout.swift#L17)
+[
](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Scale/ScaleTransformViewOptions%2BLayout.swift#L18)
+[
](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Scale/ScaleTransformViewOptions%2BLayout.swift#L19)
+[
](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Scale/ScaleTransformViewOptions%2BLayout.swift#L20)
+[
](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Scale/ScaleTransformViewOptions%2BLayout.swift#L21)
-## Custom implementations
+### UIKit: `StackTransformView`, SwiftUI: `StackPageView`
-
-
-
+
+[
](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Stack/StackTransformViewOptions%2BLayout.swift#L14)
+[
](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Stack/StackTransformViewOptions%2BLayout.swift#L15)
+[
](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Stack/StackTransformViewOptions%2BLayout.swift#L16)
+[
](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Stack/StackTransformViewOptions%2BLayout.swift#L17)
+[
](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Stack/StackTransformViewOptions%2BLayout.swift#L18)
+[
](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Stack/StackTransformViewOptions%2BLayout.swift#L19)
## About
-CollectionViewPagingLayout is a simple but powerful tool for making complex layouts for your UICollectionView.
-The implementation is very simple, there is a custom `UICollectionViewLayout` that gives you the ability to apply transforms on the cells.
-No inheritance or anything like that.
-See [How to use](https://github.com/amirdew/CollectionViewPagingLayout#how-to-use) for more details.
+**`UIKit:`**
+A simple but powerful framework that lets you make complex layouts for your `UICollectionView`.
+The implementation is quite simple. Just a custom `UICollectionViewLayout` that gives you the ability to apply transforms to the cells.
+No UICollectionView inheritance or anything like that.
+
+**`SwiftUI:`**
+A simple `View` that lets you make page-view effects.
+Powered by `UICollectionView`
+
+
+
+
+
+For more details, see [How to use](https://github.com/amirdew/CollectionViewPagingLayout#how-to-use)
## Installation
-CollectionViewPagingLayout doesn't contain any external dependencies.
+This framework doesn't contain any external dependencies.
#### [CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html)
@@ -106,171 +127,33 @@ Just add all the files under `Lib` directory to your project
## How to use
-### Using Layout Designer
+### Using [Layout Designer](https://apps.apple.com/nl/app/layout-designer/id1507238011?l=en&mt=1)
-There is a macOS app to make it even easier for you to build your own custom layout.
-It allows you to tweak many options and see the result in real time.
-- You can [download](https://apps.apple.com/nl/app/layout-designer/id1507238011?l=en&mt=1) the app from App Store and support this repository or build it yourself from the source.
-- Open the app, tweak options and design your layout.
-- Copy the code from the right panel and paste it on Xcode or click on "save it as project" and open it with Xcode.
-- Don't forget to [install](https://github.com/amirdew/CollectionViewPagingLayout#installation) the libray.
-
-
-### Manually
-
-- Make sure you imported the library
-```swift
-import CollectionViewPagingLayout
-```
-- Set up your `UICollectionView` as you always do (you need to use a custom class for cells)
-- Set the layout for your collection view:
-(in most cases you want a paging effect so enable that too)
-```swift
-let layout = CollectionViewPagingLayout()
-collectionView.collectionViewLayout = layout
-collectionView.isPagingEnabled = true // enabling paging effect
-```
-
-- Now you just need to conform your `UICollectionViewCell` class to `TransformableView` and start implementing your custom transforms. By conforming your cell class to `TransformableView` protocol you will get a `progress` value and you can use it to apply any changes on your cell view.
-
-
-*Note:* you can use [Prepared Transformable Protocols](#prepared-transformable-protocols) instead of `TransformableView` if you want to use prepared effects!
+There is a macOS app to make it even easier for you to build your custom layout.
+It allows you to tweak many options and see the result in real-time.
+It also generates the code for you. So, you can copy it to your project.
-```swift
-extension MyCollectionViewCell: TransformableView {
- func transform(progress: CGFloat) {
- ...
- }
-}
-```
-> `progress` is a float value that represents the current position of your cell in the collection view.
-> When it's `0` that means the current position of the cell is exactly in the center of your collection view.
-> the value could be negative or positive and that represents the distance to the center of your collection view.
-> for instance `1` means the distance between the center of the cell and the center of your collection view is equal to your collection view width.
-
-
-you can start with a simple transform like this:
-```swift
-extension MyCollectionViewCell: TransformableView {
- func transform(progress: CGFloat) {
- let transform = CGAffineTransform(translationX: bounds.width/2 * progress, y: 0)
- let alpha = 1 - abs(progress)
-
- contentView.subviews.forEach { $0.transform = transform }
- contentView.alpha = alpha
- }
-}
-```
-
-- Don't forget to set `numberOfVisibleItems`, by default it's null and that means it will load all of the cells at a time
-```swift
-layout.numberOfVisibleItems = ...
-```
-
-## Prepared Transformable Protocols
-
-There are prepared transformables to make it easier to use this library,
-using them is very simple, you just need to conform your `UICollectionViewCell` to the prepared protocol
-and then set the options for that to customize it as you want.
-there are three types of transformables protocol at the moment `ScaleTransformView`, `SnapshotTransformView`, and `StackTransformView`.
-as you can see in the samples app these protocols are highly customizable and you can make tons of different effects with them.
-here is a simple example for `ScaleTransformView` which gives you a simple paging with scaling effect:
-```swift
-extension MyCollectionViewCell: ScaleTransformView {
- var scaleOptions = ScaleTransformViewOptions(
- minScale: 0.6,
- scaleRatio: 0.4,
- translationRatio: CGPoint(x: 0.66, y: 0.2),
- maxTranslationRatio: CGPoint(x: 2, y: 0),
- )
-}
-```
-there is an options struct for each transformable where you can customize the effect, check the struct to find out what each parameter does.
-a short comment on the top of each parameter explains how you can use it.
-`ScaleTransformView` -> [`ScaleTransformViewOptions`](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Scale/ScaleTransformViewOptions.swift)
-`SnapshotTransformView` -> [`SnapshotTransformViewOptions`](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Snapshot/SnapshotTransformViewOptions.swift)
-`StackTransformView` -> [`StackTransformViewOptions`](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Lib/Stack/StackTransformViewOptions.swift)
-
-you can see some examples in the samples app for these transformables.
-check [here](https://github.com/amirdew/CollectionViewPagingLayout/tree/master/PagingLayoutSamples/Modules/Shapes/ShapeCell) to see used options for each: `/PagingLayoutSamples/Modules/Shapes/ShapeCell/`
-
-#### Target view
-You may wonder how does it figure out the view for applying transforms on, if you check each transformable protocol you can see the target views are defined for each, you can also see there is an extension to provide the default target views.
-for instance we have `ScaleTransformView.scalableView` which is the view that we apply scale transforms on, and for `UICollectionViewCell` the default view is the first subview of `contentView`:
-
-```swift
-public extension ScaleTransformView where Self: UICollectionViewCell {
-
- /// Default `scalableView` for `UICollectionViewCell` is the first subview of
- /// `contentView` or the content view itself if there is no subview
- var scalableView: UIView {
- contentView.subviews.first ?? contentView
- }
-}
-```
-of course you can easily override this
-
-
-## Customize Prepared Transformables
-
-Yes, you can customize them or even combine them, to do that like before conform your cell class to the transformable protocol(s) and then implement `TransformableView.transform` function and call the transformable function manually, like this:
-```swift
-extension LayoutTypeCollectionViewCell: ScaleTransformView {
-
- func transform(progress: CGFloat) {
- applyScaleTransform(progress: progress)
- // customize views here, like this:
- titleLabel.alpha = 1 - abs(progress)
- subtitleLabel.alpha = titleLabel.alpha
- }
-
-}
-```
-as you can see `applyScaleTransform` applies the scale transforms and right after that we change the alpha for `titleLabel` and `subtitleLabel`.
-to find the public function(s) for each tansformable check the protocol definition.
-
-## Other features
-
-### Control current page
-
-You can control the current page by the following functions of `CollectionViewPagingLayout`:
-- `func setCurrentPage(_ page: Int, animated: Bool = true)`
-- `func goToNextPage(animated: Bool = true)`
-- `func goToPreviousPage(animated: Bool = true)`
-
-these are safe wrappers for setting `ContentOffset` of `UICollectionview`
-you can also get current page by a public variable `CollectionViewPagingLayout.currentPage` or listen to changes by using `CollectionViewPagingLayout.delegate`:
-```swift
-public protocol CollectionViewPagingLayoutDelegate: class {
- func onCurrentPageChanged(layout: CollectionViewPagingLayout, currentPage: Int)
-}
-```
+You can [purchase](https://apps.apple.com/nl/app/layout-designer/id1507238011?l=en&mt=1) the app from App Store and support this repository,
+or you can build it yourself from the source.
+Yes, the macOS app is open-source too!.
-### Handle touches
-There is built-in solution for handling tap gestues on the views
-- Implement `TransformableView.selectableView` and pass the view that you would like to handle tap gestues for (by default this is the first subview of your `UICollectionViewCell.contentView`)
-- Call `CollectionViewPagingLayout.configureTapOnCollectionView()` after setting the layout for you collection view
-- Set the delegate for layout (`CollectionViewPagingLayout.delegate`)
-- Use didSelectItemAt function `func collectionViewPagingLayout(_ layout: CollectionViewPagingLayout, didSelectItemAt indexPath: IndexPath)`
+Continue for [`SwiftUI`](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/HOW_TO_USE_SWIFTUI.md) or [`UIKit`](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/HOW_TO_USE_UIKIT.md)
## Limitations
-- Specify the number of visible cells:
-You need to specify the number of visible cells since this layout gives you the flexibility to show the next and previous cells.
-By default, the layout loads all of the cells in the collection view frame and that means it keeps all of them in memory.
-You can specify the number of cells that you need to show at a time by considering your design.
+- **Specify the number of visible items:**
+You need to specify the number of visible items.
+Since this layout gives you the flexibility to show the next and previous cells,
+By default, it loads all of the cells in the collectionview's frame, which means iOS keeps all of them in the memory.
+Based on your design, you can specify the number of items that you need to show.
-- Touches on cells:
-The way that this layout works is putting all the cells in the collectionview frame (doesn't matter which TransformView you use)
-and then it applies transforms on the target view (StackTransformView.cardView, ScaleTransformView.scalableView etc).
-so you can use `func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)` but only for one cell at the same time
-you can implement `func zPosition(progress: CGFloat) -> Int` to specify wich cell should be on top
-this also means you can't handle any gesture for the cell that is not on the top!
+- **It doesn't support RTL layouts:**
+however, you can achieve a similar result by tweaking options, for instance try `StackTransformViewOptions.Layout.reverse`
-if you would like to handle tap on multiple cells at the same time see [Handle touches](https://github.com/amirdew/CollectionViewPagingLayout#handle-touches)
+## Credit
-- It doesn't support RTL layouts
+- [DevicesView](https://github.com/amirdew/CollectionViewPagingLayout/blob/master/Samples/PagingLayoutSamples/Modules/SwiftUI/DevicesView.swift) inspired by this [Cuberto's post](https://dribbble.com/shots/12580831-Principle-Tutorial-Onboarding-Flow-Animation)
## License
diff --git a/Samples/AppKitGlue/Info.plist b/Samples/AppKitGlue/Info.plist
index 977fbbd..ca5c428 100644
--- a/Samples/AppKitGlue/Info.plist
+++ b/Samples/AppKitGlue/Info.plist
@@ -15,9 +15,9 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 1.0
+ $(MARKETING_VERSION)
CFBundleVersion
- 1
+ $(CURRENT_PROJECT_VERSION)
NSHumanReadableCopyright
Copyright © 2020 Amir Khorsandi. All rights reserved.
NSPrincipalClass
diff --git a/Samples/AppKitGlue/MacApp.swift b/Samples/AppKitGlue/MacApp.swift
index 557690d..647c6d8 100644
--- a/Samples/AppKitGlue/MacApp.swift
+++ b/Samples/AppKitGlue/MacApp.swift
@@ -7,34 +7,53 @@
//
import Cocoa
+import Combine
class MacApp: NSObject, AppKitBridge {
-
+
+ private var window: NSWindow!
+ private var cancellable: Cancellable!
+
func initialise() {
- DispatchQueue.main.async {
+ cancellable = DispatchQueue.main.schedule(after: .init(.now()), interval: .milliseconds(10)) {
if NSApplication.shared.mainWindow != nil {
+ self.window = NSApplication.shared.mainWindow
self.onMainWindowReady()
}
}
}
private func onMainWindowReady() {
+ cancellable.cancel()
hideToolbar()
setSize()
+ addVisualEffectView()
}
private func setSize() {
- guard let window = NSApplication.shared.mainWindow else {
- return
+ if window.minSize.width < 1_200 || window.minSize.height < 768 {
+ window.minSize = NSSize(width: 1_200, height: 768)
+ }
+ if window.frame.size.width < window.minSize.width ||
+ window.frame.size.height < window.minSize.height {
+ let size = NSSize(width: max(window.minSize.width, window.frame.size.width),
+ height: max(window.minSize.height, window.frame.size.height))
+ window.setFrame(.init(origin: window.frame.origin, size: size), display: true, animate: true)
}
- window.minSize = NSSize(width: 1_200, height: 768)
- window.setFrame(.init(origin: window.frame.origin, size: window.minSize), display: true, animate: true)
}
private func hideToolbar() {
- if let window = NSApplication.shared.mainWindow {
- window.titleVisibility = .hidden
- window.toolbar = nil
- }
+ window.titleVisibility = .hidden
+ window.toolbar = nil
+ }
+
+ private func addVisualEffectView() {
+ let visualEffectView = NSVisualEffectView(frame: .zero)
+ visualEffectView.blendingMode = .behindWindow
+ visualEffectView.material = .hudWindow
+ visualEffectView.appearance = NSAppearance(named: .vibrantDark)
+ window.contentView?.addSubview(visualEffectView, positioned: .below, relativeTo: nil)
+ visualEffectView.frame = window.contentView?.bounds ?? .zero
+ visualEffectView.autoresizingMask = [.width, .height]
}
}
diff --git a/Samples/PagingLayoutSamples.xcodeproj/project.pbxproj b/Samples/PagingLayoutSamples.xcodeproj/project.pbxproj
index 89a9542..829be8e 100644
--- a/Samples/PagingLayoutSamples.xcodeproj/project.pbxproj
+++ b/Samples/PagingLayoutSamples.xcodeproj/project.pbxproj
@@ -15,9 +15,13 @@
2925CDDF23D4D21F00243F5F /* Card.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2925CDDE23D4D21F00243F5F /* Card.swift */; };
2949CB272476EA8C000CC073 /* UITableView+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2949CB262476EA8C000CC073 /* UITableView+Utilities.swift */; };
294BA3C324A77A73008D0569 /* LayoutDesignerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 294BA3C224A77A73008D0569 /* LayoutDesignerViewModel.swift */; };
+ 29519ACC262B5DE400D8A4A3 /* ShapesListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29519ACB262B5DE400D8A4A3 /* ShapesListView.swift */; };
+ 29519ACE262B646E00D8A4A3 /* ShapesListView.ShapeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29519ACD262B646E00D8A4A3 /* ShapesListView.ShapeView.swift */; };
+ 296EF99A2628B13000B72439 /* WeatherTabView.PageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296EF9992628B13000B72439 /* WeatherTabView.PageView.swift */; };
2977657C2474531200835DBD /* LayoutDesignerOptionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2977657B2474531200835DBD /* LayoutDesignerOptionCell.swift */; };
2977657E2474531D00835DBD /* LayoutDesignerOptionCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2977657D2474531D00835DBD /* LayoutDesignerOptionCell.xib */; };
29776580247454BC00835DBD /* LayoutDesignerOptionCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2977657F247454BC00835DBD /* LayoutDesignerOptionCellViewModel.swift */; };
+ 29834F2B25C5977300896343 /* DevicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29834F2A25C5977300896343 /* DevicesView.swift */; };
2993722324A79A9C0026D52F /* ShapeLayout+ScaleOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2993722224A79A9C0026D52F /* ShapeLayout+ScaleOptions.swift */; };
29A2D3AA24B72895005A0F6B /* LayoutDesignerCodePreviewViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 29A2D3A924B72895005A0F6B /* LayoutDesignerCodePreviewViewController.xib */; };
29A2D3CD24B738CC005A0F6B /* LayoutDesignerIntroViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29A2D3CC24B738CC005A0F6B /* LayoutDesignerIntroViewController.swift */; };
@@ -44,6 +48,11 @@
29D9F95323F8685C00656A67 /* BaseShapeCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D9F95223F8685C00656A67 /* BaseShapeCollectionViewCell.swift */; };
29D9F95923F874E900656A67 /* GradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D9F95823F874E900656A67 /* GradientView.swift */; };
29D9F95B23F88A6900656A67 /* ShapeCollectionViewCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D9F95A23F88A6900656A67 /* ShapeCollectionViewCells.swift */; };
+ 29DD1E18262597EF00846F7B /* WeatherTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29DD1E17262597EF00846F7B /* WeatherTabView.swift */; };
+ 29DD1E2B26275E1D00846F7B /* VisualEffectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29DD1E2A26275E1D00846F7B /* VisualEffectView.swift */; };
+ 29DD1E2F2627702C00846F7B /* WeatherTabView.Overlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29DD1E2E2627702C00846F7B /* WeatherTabView.Overlay.swift */; };
+ 29DD1E342627708B00846F7B /* WeatherPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29DD1E332627708B00846F7B /* WeatherPage.swift */; };
+ 29DD1E3A2627774400846F7B /* WeatherTabView.TabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29DD1E392627774400846F7B /* WeatherTabView.TabView.swift */; };
29FF296224A6321100C83DF9 /* LayoutDesignerCodePreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29FF296124A6321100C83DF9 /* LayoutDesignerCodePreviewViewController.swift */; };
29FF296424A6321B00C83DF9 /* LayoutDesignerCodePreviewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29FF296324A6321B00C83DF9 /* LayoutDesignerCodePreviewViewModel.swift */; };
55923226155A0B6E5A55C691 /* Pods_PagingLayoutSamples.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A489BA28D16931A0BE34BD0B /* Pods_PagingLayoutSamples.framework */; };
@@ -125,9 +134,13 @@
2925CDDE23D4D21F00243F5F /* Card.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Card.swift; sourceTree = ""; };
2949CB262476EA8C000CC073 /* UITableView+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+Utilities.swift"; sourceTree = ""; };
294BA3C224A77A73008D0569 /* LayoutDesignerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutDesignerViewModel.swift; sourceTree = ""; };
+ 29519ACB262B5DE400D8A4A3 /* ShapesListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapesListView.swift; sourceTree = ""; };
+ 29519ACD262B646E00D8A4A3 /* ShapesListView.ShapeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapesListView.ShapeView.swift; sourceTree = ""; };
+ 296EF9992628B13000B72439 /* WeatherTabView.PageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherTabView.PageView.swift; sourceTree = ""; };
2977657B2474531200835DBD /* LayoutDesignerOptionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutDesignerOptionCell.swift; sourceTree = ""; };
2977657D2474531D00835DBD /* LayoutDesignerOptionCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LayoutDesignerOptionCell.xib; sourceTree = ""; };
2977657F247454BC00835DBD /* LayoutDesignerOptionCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutDesignerOptionCellViewModel.swift; sourceTree = ""; };
+ 29834F2A25C5977300896343 /* DevicesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevicesView.swift; sourceTree = ""; };
2993722224A79A9C0026D52F /* ShapeLayout+ScaleOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ShapeLayout+ScaleOptions.swift"; sourceTree = ""; };
29A2D3A924B72895005A0F6B /* LayoutDesignerCodePreviewViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LayoutDesignerCodePreviewViewController.xib; sourceTree = ""; };
29A2D3CC24B738CC005A0F6B /* LayoutDesignerIntroViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutDesignerIntroViewController.swift; sourceTree = ""; };
@@ -154,6 +167,11 @@
29D9F95223F8685C00656A67 /* BaseShapeCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseShapeCollectionViewCell.swift; sourceTree = ""; };
29D9F95823F874E900656A67 /* GradientView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientView.swift; sourceTree = ""; };
29D9F95A23F88A6900656A67 /* ShapeCollectionViewCells.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapeCollectionViewCells.swift; sourceTree = ""; };
+ 29DD1E17262597EF00846F7B /* WeatherTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherTabView.swift; sourceTree = ""; };
+ 29DD1E2A26275E1D00846F7B /* VisualEffectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisualEffectView.swift; sourceTree = ""; };
+ 29DD1E2E2627702C00846F7B /* WeatherTabView.Overlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherTabView.Overlay.swift; sourceTree = ""; };
+ 29DD1E332627708B00846F7B /* WeatherPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherPage.swift; sourceTree = ""; };
+ 29DD1E392627774400846F7B /* WeatherTabView.TabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherTabView.TabView.swift; sourceTree = ""; };
29FF296124A6321100C83DF9 /* LayoutDesignerCodePreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutDesignerCodePreviewViewController.swift; sourceTree = ""; };
29FF296324A6321B00C83DF9 /* LayoutDesignerCodePreviewViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutDesignerCodePreviewViewModel.swift; sourceTree = ""; };
444EC9E8B3BA262F3697984F /* Pods-PagingLayoutSamples.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PagingLayoutSamples.release.xcconfig"; path = "Target Support Files/Pods-PagingLayoutSamples/Pods-PagingLayoutSamples.release.xcconfig"; sourceTree = ""; };
@@ -252,6 +270,15 @@
path = AppKitGlue;
sourceTree = "";
};
+ 29519ACA262B5DD400D8A4A3 /* Shapes */ = {
+ isa = PBXGroup;
+ children = (
+ 29519ACB262B5DE400D8A4A3 /* ShapesListView.swift */,
+ 29519ACD262B646E00D8A4A3 /* ShapesListView.ShapeView.swift */,
+ );
+ path = Shapes;
+ sourceTree = "";
+ };
2977657A247452BC00835DBD /* Options */ = {
isa = PBXGroup;
children = (
@@ -261,6 +288,16 @@
path = Options;
sourceTree = "";
};
+ 29834F2925C5976300896343 /* SwiftUI */ = {
+ isa = PBXGroup;
+ children = (
+ 29519ACA262B5DD400D8A4A3 /* Shapes */,
+ 29DD1E322627707A00846F7B /* WeatherTabView */,
+ 29834F2A25C5977300896343 /* DevicesView.swift */,
+ );
+ path = SwiftUI;
+ sourceTree = "";
+ };
29A2D3CB24B73870005A0F6B /* Intro */ = {
isa = PBXGroup;
children = (
@@ -317,6 +354,29 @@
path = ShapeCell;
sourceTree = "";
};
+ 29DD1E21262753B900846F7B /* UIKit */ = {
+ isa = PBXGroup;
+ children = (
+ 29D9F94123F7F92000656A67 /* Shapes */,
+ AB500A4823B13B9D0056BE37 /* Fruits */,
+ AB1BBA8B23C935BF004E5C3B /* Cards */,
+ ABC242C123B681F800DBD4D6 /* Gallery */,
+ );
+ path = UIKit;
+ sourceTree = "";
+ };
+ 29DD1E322627707A00846F7B /* WeatherTabView */ = {
+ isa = PBXGroup;
+ children = (
+ 29DD1E17262597EF00846F7B /* WeatherTabView.swift */,
+ 29DD1E392627774400846F7B /* WeatherTabView.TabView.swift */,
+ 29DD1E2E2627702C00846F7B /* WeatherTabView.Overlay.swift */,
+ 296EF9992628B13000B72439 /* WeatherTabView.PageView.swift */,
+ 29DD1E332627708B00846F7B /* WeatherPage.swift */,
+ );
+ path = WeatherTabView;
+ sourceTree = "";
+ };
29FF296024A631DC00C83DF9 /* Code */ = {
isa = PBXGroup;
children = (
@@ -435,12 +495,10 @@
AB500A5223B152170056BE37 /* Modules */ = {
isa = PBXGroup;
children = (
+ 29DD1E21262753B900846F7B /* UIKit */,
+ 29834F2925C5976300896343 /* SwiftUI */,
292489AE2461A96600A316B0 /* LayoutDesigner */,
AB7C1E0423B4E292006441DE /* Main */,
- 29D9F94123F7F92000656A67 /* Shapes */,
- AB500A4823B13B9D0056BE37 /* Fruits */,
- AB1BBA8B23C935BF004E5C3B /* Cards */,
- ABC242C123B681F800DBD4D6 /* Gallery */,
);
path = Modules;
sourceTree = "";
@@ -457,6 +515,7 @@
29B5A72724A8D8F700C9843E /* TransformCurve+Name.swift */,
29B5A72B24A8DD7100C9843E /* UIBlurEffect.Style+Name.swift */,
29B5A72924A8D94300C9843E /* Values+Pair.swift */,
+ 29DD1E2A26275E1D00846F7B /* VisualEffectView.swift */,
);
path = Utilities;
sourceTree = "";
@@ -578,7 +637,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1120;
- LastUpgradeCheck = 1120;
+ LastUpgradeCheck = 1220;
ORGANIZATIONNAME = "Amir Khorsandi";
TargetAttributes = {
292489B62461F07D00A316B0 = {
@@ -723,6 +782,7 @@
2925CDDF23D4D21F00243F5F /* Card.swift in Sources */,
29D9F95023F806C400656A67 /* Optional+Let.swift in Sources */,
ABA1A72C23B42240006A46A3 /* PriceTagView.swift in Sources */,
+ 29DD1E2F2627702C00846F7B /* WeatherTabView.Overlay.swift in Sources */,
ABA0DA0C23F98C78004A9C18 /* ShapeCardViewModel.swift in Sources */,
29A2D3CD24B738CC005A0F6B /* LayoutDesignerIntroViewController.swift in Sources */,
ABA0DA0E23F98ECD004A9C18 /* UICollectionViewCell+Utilities.swift in Sources */,
@@ -733,6 +793,7 @@
AB1BBA9B23CA5179004E5C3B /* CardCellViewModel.swift in Sources */,
AB500A5023B151780056BE37 /* FruitsViewModel.swift in Sources */,
2977657C2474531200835DBD /* LayoutDesignerOptionCell.swift in Sources */,
+ 29519ACE262B646E00D8A4A3 /* ShapesListView.ShapeView.swift in Sources */,
2949CB272476EA8C000CC073 /* UITableView+Utilities.swift in Sources */,
29B5A72A24A8D94300C9843E /* Values+Pair.swift in Sources */,
AB500A4A23B13BBD0056BE37 /* FruitsViewController.swift in Sources */,
@@ -742,6 +803,7 @@
29B5A72824A8D8F700C9843E /* TransformCurve+Name.swift in Sources */,
AB500A4E23B13C5D0056BE37 /* NibBased.swift in Sources */,
ABC242C723B6823600DBD4D6 /* GalleryViewModel.swift in Sources */,
+ 296EF99A2628B13000B72439 /* WeatherTabView.PageView.swift in Sources */,
AB1BBA9E23CA7BD9004E5C3B /* CardsViewController.swift in Sources */,
ABA0DA0323F93CDB004A9C18 /* Shape.swift in Sources */,
ABC242CA23B682DD00DBD4D6 /* PhotoCellViewModel.swift in Sources */,
@@ -757,19 +819,25 @@
ABA0DA0823F98B65004A9C18 /* ShapeCardView.swift in Sources */,
ABC242CC23B6831400DBD4D6 /* Photo.swift in Sources */,
29B5A72424A8CC4B00C9843E /* CGFloat+String.swift in Sources */,
+ 29834F2B25C5977300896343 /* DevicesView.swift in Sources */,
+ 29519ACC262B5DE400D8A4A3 /* ShapesListView.swift in Sources */,
29FF296224A6321100C83DF9 /* LayoutDesignerCodePreviewViewController.swift in Sources */,
29D9F94323F7F98800656A67 /* ShapesViewController.swift in Sources */,
AB500A5823B154210056BE37 /* Fruit.swift in Sources */,
29B5A72C24A8DD7100C9843E /* UIBlurEffect.Style+Name.swift in Sources */,
29B5A72624A8D8B300C9843E /* OptionsCodeGenerator.swift in Sources */,
29D9F94523F7F99400656A67 /* ShapesViewModel.swift in Sources */,
+ 29DD1E18262597EF00846F7B /* WeatherTabView.swift in Sources */,
29B5A72024A7B02900C9843E /* ShapeLayout+StackOptions.swift in Sources */,
29A2D3D124B738E5005A0F6B /* LayoutDesignerIntroViewModel.swift in Sources */,
AB500A5B23B154640056BE37 /* FruitsCollectionViewCell.swift in Sources */,
ABA1A73123B422B2006A46A3 /* QuantityControllerView.swift in Sources */,
+ 29DD1E342627708B00846F7B /* WeatherPage.swift in Sources */,
29BEC4D52476DD9D004BA505 /* LayoutDesignerOptionsTableView.swift in Sources */,
+ 29DD1E2B26275E1D00846F7B /* VisualEffectView.swift in Sources */,
292489B02461A97900A316B0 /* LayoutDesignerViewController.swift in Sources */,
29D9F95923F874E900656A67 /* GradientView.swift in Sources */,
+ 29DD1E3A2627774400846F7B /* WeatherTabView.TabView.swift in Sources */,
29FF296424A6321B00C83DF9 /* LayoutDesignerCodePreviewViewModel.swift in Sources */,
AB500A5523B152500056BE37 /* ViewModelBased.swift in Sources */,
292489C52461FE2700A316B0 /* Catalyst.swift in Sources */,
@@ -878,6 +946,7 @@
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;
@@ -885,6 +954,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
+ CURRENT_PROJECT_VERSION = 150;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
@@ -902,7 +972,8 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 13.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.0;
+ MARKETING_VERSION = 1.5.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
@@ -939,6 +1010,7 @@
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;
@@ -946,6 +1018,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
+ CURRENT_PROJECT_VERSION = 150;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -957,7 +1030,8 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 13.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.0;
+ MARKETING_VERSION = 1.5.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
@@ -973,22 +1047,23 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "PagingLayoutSamples/Paging Layout.entitlements";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_TEAM = 4J5W7CJ2ZV;
INFOPLIST_FILE = PagingLayoutSamples/Info.plist;
+ "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = app.amir.paginglayout;
PRODUCT_NAME = "Layout Designer";
SUPPORTS_MACCATALYST = YES;
+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_OBJC_BRIDGING_HEADER = "PagingLayoutSamples/PagingLayoutSamples-Bridging-Header.h";
SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = "1,2";
+ TARGETED_DEVICE_FAMILY = "1,2,6";
};
name = Debug;
};
@@ -998,22 +1073,23 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "PagingLayoutSamples/Paging Layout.entitlements";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_TEAM = 4J5W7CJ2ZV;
INFOPLIST_FILE = PagingLayoutSamples/Info.plist;
+ "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = app.amir.paginglayout;
PRODUCT_NAME = "Layout Designer";
SUPPORTS_MACCATALYST = YES;
+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_OBJC_BRIDGING_HEADER = "PagingLayoutSamples/PagingLayoutSamples-Bridging-Header.h";
SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = "1,2";
+ TARGETED_DEVICE_FAMILY = "1,2,6";
};
name = Release;
};
diff --git a/Samples/PagingLayoutSamples.xcodeproj/xcshareddata/xcschemes/PagingLayoutSamples.xcscheme b/Samples/PagingLayoutSamples.xcodeproj/xcshareddata/xcschemes/PagingLayoutSamples.xcscheme
index 2cc40ad..c405f46 100644
--- a/Samples/PagingLayoutSamples.xcodeproj/xcshareddata/xcschemes/PagingLayoutSamples.xcscheme
+++ b/Samples/PagingLayoutSamples.xcodeproj/xcshareddata/xcschemes/PagingLayoutSamples.xcscheme
@@ -1,6 +1,6 @@
Bool {
window = UIWindow()
+ window?.backgroundColor = .clear
navigationController = UINavigationController()
+ navigationController.view.backgroundColor = .clear
navigationController.isNavigationBarHidden = true
- let mainVC = UIDevice.current.userInterfaceIdiom == .pad ?
+ let mainVC = UIDevice.current.userInterfaceIdiom != .phone ?
LayoutDesignerViewController.instantiate(viewModel: LayoutDesignerViewModel()) :
MainViewController.instantiate()
-
+
+ #if targetEnvironment(macCatalyst)
+ if let titlebar = window?.windowScene?.titlebar {
+ titlebar.titleVisibility = .hidden
+ titlebar.toolbar = nil
+ }
+ #endif
navigationController.setViewControllers([mainVC], animated: false)
window!.rootViewController = navigationController
+ // UIHostingController(rootView: DevicesView().ignoresSafeArea())
window!.makeKeyAndVisible()
return true
}
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AccentColor.colorset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 0000000..f67f483
--- /dev/null
+++ b/Samples/PagingLayoutSamples/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "255",
+ "green" : "186",
+ "red" : "19"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6.png
new file mode 100644
index 0000000..767efad
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6_128.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6_128.png
new file mode 100644
index 0000000..2b773d0
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6_128.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6_16.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6_16.png
new file mode 100644
index 0000000..85a9540
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6_16.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6_256-1.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6_256-1.png
new file mode 100644
index 0000000..7e75ced
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6_256-1.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6_256.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6_256.png
new file mode 100644
index 0000000..7e75ced
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6_256.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6_32-1.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6_32-1.png
new file mode 100644
index 0000000..67b4cbc
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6_32-1.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6_32.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6_32.png
new file mode 100644
index 0000000..67b4cbc
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6_32.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6_512-1.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6_512-1.png
new file mode 100644
index 0000000..dd823d8
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6_512-1.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6_512.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6_512.png
new file mode 100644
index 0000000..dd823d8
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6_512.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6_64.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6_64.png
new file mode 100644
index 0000000..4393e74
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard Copy 6_64.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard.png
new file mode 100644
index 0000000..3ab72cd
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_20pt@1x.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_20pt@1x.png
new file mode 100644
index 0000000..d90ba24
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_20pt@1x.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_20pt@2x-1.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_20pt@2x-1.png
new file mode 100644
index 0000000..a5ead61
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_20pt@2x-1.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_20pt@2x.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_20pt@2x.png
new file mode 100644
index 0000000..a5ead61
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_20pt@2x.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_20pt@3x.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_20pt@3x.png
new file mode 100644
index 0000000..1e6f89a
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_20pt@3x.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_29pt@1x.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_29pt@1x.png
new file mode 100644
index 0000000..4cc50ff
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_29pt@1x.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_29pt@2x-1.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_29pt@2x-1.png
new file mode 100644
index 0000000..ecb8579
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_29pt@2x-1.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_29pt@2x.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_29pt@2x.png
new file mode 100644
index 0000000..ecb8579
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_29pt@2x.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_29pt@3x.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_29pt@3x.png
new file mode 100644
index 0000000..dec2de0
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_29pt@3x.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_40pt@1x.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_40pt@1x.png
new file mode 100644
index 0000000..a5ead61
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_40pt@1x.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_40pt@2x-1.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_40pt@2x-1.png
new file mode 100644
index 0000000..0609b31
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_40pt@2x-1.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_40pt@2x.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_40pt@2x.png
new file mode 100644
index 0000000..0609b31
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_40pt@2x.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_40pt@3x.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_40pt@3x.png
new file mode 100644
index 0000000..373e707
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_40pt@3x.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_60pt@2x.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_60pt@2x.png
new file mode 100644
index 0000000..373e707
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_60pt@2x.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_60pt@3x.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_60pt@3x.png
new file mode 100644
index 0000000..1a7a5d3
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_60pt@3x.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_76pt@1x.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_76pt@1x.png
new file mode 100644
index 0000000..58f3331
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_76pt@1x.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_76pt@2x.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_76pt@2x.png
new file mode 100644
index 0000000..cdba86d
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_76pt@2x.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_83pt@2x.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_83pt@2x.png
new file mode 100644
index 0000000..3a2118a
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Artboard_83pt@2x.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Contents.json
index de20adc..f824938 100644
--- a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -1 +1,176 @@
-{"images":[{"size":"20x20","idiom":"iphone","filename":"icon_20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"icon_20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"icon_29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"icon_29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"icon_40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"icon_40x40@3x.png","scale":"3x"},{"size":"60x60","idiom":"iphone","filename":"icon_60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"icon_60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"icon_20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"icon_20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"icon_29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"icon_29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"icon_40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"icon_40x40@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"icon_76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"icon_76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"icon_83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"icon_1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}
\ No newline at end of file
+{
+ "images" : [
+ {
+ "filename" : "Artboard_20pt@2x.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "Artboard_20pt@3x.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "Artboard_29pt@2x.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "Artboard_29pt@3x.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "Artboard_40pt@2x.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "Artboard_40pt@3x.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "Artboard_60pt@2x.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "60x60"
+ },
+ {
+ "filename" : "Artboard_60pt@3x.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "60x60"
+ },
+ {
+ "filename" : "Artboard_20pt@1x.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "Artboard_20pt@2x-1.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "Artboard_29pt@1x.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "Artboard_29pt@2x-1.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "Artboard_40pt@1x.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "Artboard_40pt@2x-1.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "Artboard_76pt@1x.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "76x76"
+ },
+ {
+ "filename" : "Artboard_76pt@2x.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "76x76"
+ },
+ {
+ "filename" : "Artboard_83pt@2x.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
+ },
+ {
+ "filename" : "Artboard.png",
+ "idiom" : "ios-marketing",
+ "scale" : "1x",
+ "size" : "1024x1024"
+ },
+ {
+ "filename" : "Artboard Copy 6_16.png",
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "16x16"
+ },
+ {
+ "filename" : "Artboard Copy 6_32.png",
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "16x16"
+ },
+ {
+ "filename" : "Artboard Copy 6_32-1.png",
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "32x32"
+ },
+ {
+ "filename" : "Artboard Copy 6_64.png",
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "32x32"
+ },
+ {
+ "filename" : "Artboard Copy 6_128.png",
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "128x128"
+ },
+ {
+ "filename" : "Artboard Copy 6_256.png",
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "128x128"
+ },
+ {
+ "filename" : "Artboard Copy 6_256-1.png",
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "256x256"
+ },
+ {
+ "filename" : "Artboard Copy 6_512.png",
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "256x256"
+ },
+ {
+ "filename" : "Artboard Copy 6_512-1.png",
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "512x512"
+ },
+ {
+ "filename" : "Artboard Copy 6.png",
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "512x512"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_1024x1024@1x.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_1024x1024@1x.png
deleted file mode 100644
index e76fcf7..0000000
Binary files a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_1024x1024@1x.png and /dev/null differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_20x20@1x.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_20x20@1x.png
deleted file mode 100644
index fc30987..0000000
Binary files a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_20x20@1x.png and /dev/null differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_20x20@2x.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_20x20@2x.png
deleted file mode 100644
index 5a86302..0000000
Binary files a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_20x20@2x.png and /dev/null differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_20x20@3x.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_20x20@3x.png
deleted file mode 100644
index 29ddbc0..0000000
Binary files a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_20x20@3x.png and /dev/null differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_29x29@1x.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_29x29@1x.png
deleted file mode 100644
index dd5caa0..0000000
Binary files a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_29x29@1x.png and /dev/null differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_29x29@2x.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_29x29@2x.png
deleted file mode 100644
index 974bf0f..0000000
Binary files a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_29x29@2x.png and /dev/null differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_29x29@3x.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_29x29@3x.png
deleted file mode 100644
index ec0bdf3..0000000
Binary files a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_29x29@3x.png and /dev/null differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_40x40@1x.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_40x40@1x.png
deleted file mode 100644
index 5a86302..0000000
Binary files a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_40x40@1x.png and /dev/null differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_40x40@2x.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_40x40@2x.png
deleted file mode 100644
index e2f268c..0000000
Binary files a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_40x40@2x.png and /dev/null differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_40x40@3x.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_40x40@3x.png
deleted file mode 100644
index 01cdce8..0000000
Binary files a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_40x40@3x.png and /dev/null differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_60x60@2x.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_60x60@2x.png
deleted file mode 100644
index 01cdce8..0000000
Binary files a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_60x60@2x.png and /dev/null differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_60x60@3x.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_60x60@3x.png
deleted file mode 100644
index c290121..0000000
Binary files a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_60x60@3x.png and /dev/null differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_76x76@1x.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_76x76@1x.png
deleted file mode 100644
index 3b81d78..0000000
Binary files a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_76x76@1x.png and /dev/null differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_76x76@2x.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_76x76@2x.png
deleted file mode 100644
index 2611f74..0000000
Binary files a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_76x76@2x.png and /dev/null differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_83.5x83.5@2x.png b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_83.5x83.5@2x.png
deleted file mode 100644
index c48a4a9..0000000
Binary files a/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/icon_83.5x83.5@2x.png and /dev/null differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Colors/Background.colorset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Colors/Background.colorset/Contents.json
index 377daa5..79225bf 100644
--- a/Samples/PagingLayoutSamples/Assets.xcassets/Colors/Background.colorset/Contents.json
+++ b/Samples/PagingLayoutSamples/Assets.xcassets/Colors/Background.colorset/Contents.json
@@ -1,20 +1,20 @@
{
- "info" : {
- "version" : 1,
- "author" : "xcode"
- },
"colors" : [
{
- "idiom" : "universal",
"color" : {
"color-space" : "srgb",
"components" : {
- "red" : "0xF3",
"alpha" : "1.000",
- "blue" : "0xF3",
- "green" : "0xF3"
+ "blue" : "243",
+ "green" : "243",
+ "red" : "243"
}
- }
+ },
+ "idiom" : "universal"
}
- ]
-}
\ No newline at end of file
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Contents.json
index da4a164..73c0059 100644
--- a/Samples/PagingLayoutSamples/Assets.xcassets/Contents.json
+++ b/Samples/PagingLayoutSamples/Assets.xcassets/Contents.json
@@ -1,6 +1,6 @@
{
"info" : {
- "version" : 1,
- "author" : "xcode"
+ "author" : "xcode",
+ "version" : 1
}
-}
\ No newline at end of file
+}
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Weather/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Weather/WikipediaLogo.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/WikipediaLogo.imageset/Contents.json
new file mode 100644
index 0000000..2ab326e
--- /dev/null
+++ b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/WikipediaLogo.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "WikipediaLogo.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Weather/WikipediaLogo.imageset/WikipediaLogo.png b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/WikipediaLogo.imageset/WikipediaLogo.png
new file mode 100644
index 0000000..e18827b
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/WikipediaLogo.imageset/WikipediaLogo.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Weather/lightning1.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/lightning1.imageset/Contents.json
new file mode 100644
index 0000000..62beec5
--- /dev/null
+++ b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/lightning1.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "Image.png",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Weather/lightning1.imageset/Image.png b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/lightning1.imageset/Image.png
new file mode 100644
index 0000000..0870e67
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/lightning1.imageset/Image.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Weather/lightning2.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/lightning2.imageset/Contents.json
new file mode 100644
index 0000000..62beec5
--- /dev/null
+++ b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/lightning2.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "Image.png",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Weather/lightning2.imageset/Image.png b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/lightning2.imageset/Image.png
new file mode 100644
index 0000000..97c7fb9
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/lightning2.imageset/Image.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Weather/moon1.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/moon1.imageset/Contents.json
new file mode 100644
index 0000000..62beec5
--- /dev/null
+++ b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/moon1.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "Image.png",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Weather/moon1.imageset/Image.png b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/moon1.imageset/Image.png
new file mode 100644
index 0000000..54eec62
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/moon1.imageset/Image.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Weather/moon2.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/moon2.imageset/Contents.json
new file mode 100644
index 0000000..62beec5
--- /dev/null
+++ b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/moon2.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "Image.png",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Weather/moon2.imageset/Image.png b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/moon2.imageset/Image.png
new file mode 100644
index 0000000..b6537bd
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/moon2.imageset/Image.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Weather/snow1.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/snow1.imageset/Contents.json
new file mode 100644
index 0000000..62beec5
--- /dev/null
+++ b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/snow1.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "Image.png",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Weather/snow1.imageset/Image.png b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/snow1.imageset/Image.png
new file mode 100644
index 0000000..69c60d7
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/snow1.imageset/Image.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Weather/snow2.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/snow2.imageset/Contents.json
new file mode 100644
index 0000000..62beec5
--- /dev/null
+++ b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/snow2.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "Image.png",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Weather/snow2.imageset/Image.png b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/snow2.imageset/Image.png
new file mode 100644
index 0000000..edb1648
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/snow2.imageset/Image.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Weather/sun1.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/sun1.imageset/Contents.json
new file mode 100644
index 0000000..62beec5
--- /dev/null
+++ b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/sun1.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "Image.png",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Weather/sun1.imageset/Image.png b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/sun1.imageset/Image.png
new file mode 100644
index 0000000..f1be408
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/sun1.imageset/Image.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Weather/sun2.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/sun2.imageset/Contents.json
new file mode 100644
index 0000000..62beec5
--- /dev/null
+++ b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/sun2.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "Image.png",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Weather/sun2.imageset/Image.png b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/sun2.imageset/Image.png
new file mode 100644
index 0000000..a1b9f85
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/sun2.imageset/Image.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Weather/tornado1.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/tornado1.imageset/Contents.json
new file mode 100644
index 0000000..62beec5
--- /dev/null
+++ b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/tornado1.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "Image.png",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Weather/tornado1.imageset/Image.png b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/tornado1.imageset/Image.png
new file mode 100644
index 0000000..7390fc4
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/tornado1.imageset/Image.png differ
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Weather/tornado2.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/tornado2.imageset/Contents.json
new file mode 100644
index 0000000..62beec5
--- /dev/null
+++ b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/tornado2.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "Image.png",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Weather/tornado2.imageset/Image.png b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/tornado2.imageset/Image.png
new file mode 100644
index 0000000..71fd5c5
Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Weather/tornado2.imageset/Image.png differ
diff --git a/Samples/PagingLayoutSamples/Base.lproj/LaunchScreen.storyboard b/Samples/PagingLayoutSamples/Base.lproj/LaunchScreen.storyboard
index 346fff9..a2ebcf3 100644
--- a/Samples/PagingLayoutSamples/Base.lproj/LaunchScreen.storyboard
+++ b/Samples/PagingLayoutSamples/Base.lproj/LaunchScreen.storyboard
@@ -1,8 +1,8 @@
-
+
-
+
@@ -16,15 +16,15 @@
-
+
+
-
+
-
diff --git a/Samples/PagingLayoutSamples/CustomViews/QuantityControllerView/QuantityControllerView.swift b/Samples/PagingLayoutSamples/CustomViews/QuantityControllerView/QuantityControllerView.swift
index faa715e..f36dc9e 100644
--- a/Samples/PagingLayoutSamples/CustomViews/QuantityControllerView/QuantityControllerView.swift
+++ b/Samples/PagingLayoutSamples/CustomViews/QuantityControllerView/QuantityControllerView.swift
@@ -8,7 +8,7 @@
import UIKit
-protocol QuantityControllerViewDelegate: class {
+protocol QuantityControllerViewDelegate: AnyObject {
func onIncreaseButtonTouched(view: QuantityControllerView)
func onDecreaseButtonTouched(view: QuantityControllerView)
}
diff --git a/Samples/PagingLayoutSamples/Info.plist b/Samples/PagingLayoutSamples/Info.plist
index 8b9cf88..11347b9 100644
--- a/Samples/PagingLayoutSamples/Info.plist
+++ b/Samples/PagingLayoutSamples/Info.plist
@@ -18,6 +18,8 @@
$(MARKETING_VERSION)
CFBundleVersion
$(CURRENT_PROJECT_VERSION)
+ LSApplicationCategoryType
+ public.app-category.developer-tools
LSRequiresIPhoneOS
UILaunchStoryboardName
@@ -40,8 +42,6 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
- LSApplicationCategoryType
- public.app-category.developer-tools
UIUserInterfaceStyle
Light
diff --git a/Samples/PagingLayoutSamples/Models/Shape.swift b/Samples/PagingLayoutSamples/Models/Shape.swift
index 4a23797..46c24cd 100644
--- a/Samples/PagingLayoutSamples/Models/Shape.swift
+++ b/Samples/PagingLayoutSamples/Models/Shape.swift
@@ -6,7 +6,11 @@
// Copyright © 2020 Amir Khorsandi. All rights reserved.
//
-struct Shape {
+struct Shape: Identifiable {
let name: String
let iconName: String
+
+ var id: String {
+ name
+ }
}
diff --git a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Code/LayoutDesignerCodePreviewViewController.swift b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Code/LayoutDesignerCodePreviewViewController.swift
index 899a9ee..4ff69f0 100644
--- a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Code/LayoutDesignerCodePreviewViewController.swift
+++ b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Code/LayoutDesignerCodePreviewViewController.swift
@@ -16,7 +16,7 @@ protocol LayoutDesignerCodePreviewViewControllerDelegate: AnyObject {
class LayoutDesignerCodePreviewViewController: UIViewController, NibBased, ViewModelBased {
-
+
// MARK: Properties
var viewModel: LayoutDesignerCodePreviewViewModel! {
@@ -50,8 +50,8 @@ class LayoutDesignerCodePreviewViewController: UIViewController, NibBased, ViewM
@IBAction private func saveButtonTouched() {
guard let exportURL = viewModel.sampleProjectTempURL else { return }
- viewModel.generateSampleProject()
- let controller = UIDocumentPickerViewController(url: exportURL, in: UIDocumentPickerMode.exportToService)
+ viewModel.generateSampleProject(type: selectedCodeType())
+ let controller = UIDocumentPickerViewController(forExporting: [exportURL])
controller.delegate = self
present(controller, animated: true)
}
@@ -82,22 +82,40 @@ class LayoutDesignerCodePreviewViewController: UIViewController, NibBased, ViewM
private func configureCodeTypeSegmentedControl() {
codeModeSegmentedControl.backgroundColor = UIColor.black.withAlphaComponent(0.4)
codeModeSegmentedControl.selectedSegmentTintColor = UIColor.white.withAlphaComponent(0.4)
- codeModeSegmentedControl.setTitleTextAttributes([.foregroundColor: UIColor.white], for: .normal)
- codeModeSegmentedControl.setTitleTextAttributes([.foregroundColor: UIColor.black.withAlphaComponent(0.6)], for: .selected)
- codeModeSegmentedControl.selectedSegmentIndex = 0
+ codeModeSegmentedControl.setTitleTextAttributes(
+ [.foregroundColor: UIColor.white, .font: UIFont.systemFont(ofSize: 12)],
+ for: .normal)
+ codeModeSegmentedControl.setTitleTextAttributes(
+ [.foregroundColor: UIColor.black.withAlphaComponent(0.6), .font: UIFont.systemFont(ofSize: 12)],
+ for: .selected)
+ codeModeSegmentedControl.selectedSegmentIndex = 1
+ codeModeSegmentedControl.tintColor = UIColor.white.withAlphaComponent(0.4)
}
private func configureTextView() {
codeTextView.backgroundColor = .clear
codeTextView.isEditable = false
+ codeTextView.tintColor = .gray
}
private func refreshViews() {
- codeTextView.attributedText = viewModel?.getHighlightedText(
- addViewControllerInCode: codeModeSegmentedControl.selectedSegmentIndex == 0
- )
+ codeTextView.attributedText = viewModel?.getHighlightedText(type: selectedCodeType())
+ codeTextView.contentInset = .init(top: 40 + view.safeAreaInsets.top,
+ left: 0,
+ bottom: 200 + view.safeAreaInsets.bottom,
+ right: 0)
+ codeTextView.contentOffset = .init(x: 0, y: -codeTextView.contentInset.top)
+ }
+
+ private func selectedCodeType() -> LayoutDesignerCodePreviewViewModel.CodeType {
+ if codeModeSegmentedControl.selectedSegmentIndex == 0 {
+ return .uikit
+ }
+ if codeModeSegmentedControl.selectedSegmentIndex == 2 {
+ return .options
+ }
+ return .swiftui
}
-
}
@@ -107,7 +125,7 @@ extension LayoutDesignerCodePreviewViewController: UIDocumentPickerDelegate {
controller.dismiss(animated: true)
viewModel.removeSampleProject()
}
-
+
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
controller.dismiss(animated: true)
viewModel.removeSampleProject()
diff --git a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Code/LayoutDesignerCodePreviewViewController.xib b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Code/LayoutDesignerCodePreviewViewController.xib
index ba00277..1d8671d 100644
--- a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Code/LayoutDesignerCodePreviewViewController.xib
+++ b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Code/LayoutDesignerCodePreviewViewController.xib
@@ -1,9 +1,10 @@
-
+
-
+
+
@@ -23,11 +24,11 @@
-
-
+
+
Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda.
-
-
+
+
-
+
-
-
+
+
+
-
+
-
+
-
+
-
-
+
+
-
@@ -97,5 +103,11 @@
+
+
+
+
+
+
diff --git a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Code/LayoutDesignerCodePreviewViewModel.swift b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Code/LayoutDesignerCodePreviewViewModel.swift
index e8d4e3b..ba86f68 100644
--- a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Code/LayoutDesignerCodePreviewViewModel.swift
+++ b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Code/LayoutDesignerCodePreviewViewModel.swift
@@ -10,37 +10,85 @@ import Foundation
import Splash
struct LayoutDesignerCodePreviewViewModel {
-
+
+ // MARK: Constant
+
+ enum CodeType {
+ case uikit
+ case swiftui
+ case options
+ }
+
+
// MARK: Properties
let code: String
var sampleProjectTempURL: URL? {
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("SampleProject")
}
+
+ static private var fontSize: Double {
+ #if targetEnvironment(macCatalyst)
+ return 13
+ #else
+ return 1_024 * 10 / 1_024
+ #endif
+ }
- private let highlighter = SyntaxHighlighter(format: AttributedStringOutputFormat(theme: .sundellsColors(withFont: Font(size: 14))))
-
+ private let highlighter = SyntaxHighlighter(format: AttributedStringOutputFormat(theme: Theme(
+ font: Font(size: Self.fontSize),
+ plainTextColor: .white,
+ tokenColors: [
+ .keyword: Color(red: 1.00, green: 0.40, blue: 0.56, alpha: 1.00),
+ .string: Color(red: 0.98, green: 0.39, blue: 0.12, alpha: 1),
+ .type: Color(red: 0.57, green: 0.59, blue: 1.00, alpha: 1.00),
+ .call: Color(red: 0.2, green: 0.56, blue: 0.9, alpha: 1),
+ .number: Color(red: 0.97, green: 0.47, blue: 0.37, alpha: 1.00),
+ .comment: Color(red: 0.34, green: 0.72, blue: 0.80, alpha: 1.00),
+ .property: Color(red: 0.13, green: 0.67, blue: 0.62, alpha: 1),
+ .dotAccess: Color(red: 0.57, green: 0.7, blue: 0, alpha: 1),
+ .preprocessing: Color(red: 0.71, green: 0.54, blue: 0, alpha: 1)
+ ],
+ backgroundColor: .clear
+ )
+ ))
+
// MARK: Public functions
- func getHighlightedText(addViewControllerInCode includeVC: Bool) -> NSAttributedString {
- highlighter.highlight(getCode(includeViewController: includeVC))
+ func getHighlightedText(type: CodeType) -> NSAttributedString {
+ highlighter.highlight(getCode(type: type))
}
- func generateSampleProject() {
+ func generateSampleProject(type: CodeType) {
removeSampleProject()
guard let sampleProjectTempURL = sampleProjectTempURL,
- let bundlePath = Bundle.main.url(forResource: "SampleProject", withExtension: "bundle"),
- let sampleProjectURL = Bundle(url: bundlePath)?.url(forResource: "SampleProject", withExtension: nil) else {
- return
+ let bundlePath = Bundle.main.url(forResource: "SampleProject", withExtension: "bundle"),
+ let sampleProjectURL = Bundle(url: bundlePath)?.url(forResource: "SampleProject", withExtension: nil) else {
+ return
}
try? FileManager.default.copyItem(at: sampleProjectURL, to: sampleProjectTempURL)
let baseProjectPath = sampleProjectTempURL.appendingPathComponent("PagingLayout")
let viewControllerPath = baseProjectPath.appendingPathComponent("ViewController.swift")
try? FileManager.default.removeItem(at: viewControllerPath)
+
+ var type = type
- let code = getCode(includeViewController: true)
+ if type == .options {
+ type = .swiftui
+ }
+ var code = getCode(type: type)
+ if type == .swiftui {
+ code.append("""
+
+
+ func ViewController() -> UIViewController {
+ UIHostingController(rootView: ContentView())
+ }
+ """)
+ }
+
try? code.write(to: viewControllerPath, atomically: true, encoding: .utf8)
try? FileManager.default.moveItem(at: sampleProjectTempURL.appendingPathComponent("PagingLayout.xcodeproj_sample"),
@@ -67,11 +115,69 @@ struct LayoutDesignerCodePreviewViewModel {
// MARK: Private functions
-
- private func getCode(includeViewController: Bool) -> String {
- if !includeViewController {
+
+ private func getCode(type: CodeType) -> String {
+ switch type {
+ case .swiftui:
+ return getSwiftUICode()
+ case .uikit:
+ return getUIKitCode()
+ case .options:
return code
}
+ }
+
+ private func getSwiftUICode() -> String {
+ let viewProtocols = ["ScaleTransformView", "StackTransformView", "SnapshotTransformView"]
+ let viewProtocolName = viewProtocols.first { code.contains($0) } ?? ""
+ let viewName = viewProtocolName.replacingOccurrences(of: "Transform", with: "Page")
+ return """
+ import SwiftUI
+
+ // Make sure you added this dependency to your project
+ // More info at https://bit.ly/CVPagingLayout
+ import CollectionViewPagingLayout
+
+ struct ContentView: View {
+
+ // Replace with your data
+ struct Item: Identifiable {
+ let id: UUID = .init()
+ let number: Int
+ }
+ let items = Array(0..<10).map {
+ Item(number: $0)
+ }
+
+ // Use the options to customize the layout
+ \(code.replacingOccurrences(of: "scaleOptions", with: "options")
+ .replacingOccurrences(of: "stackOptions", with: "options")
+ .replacingOccurrences(of: "snapshotOptions", with: "options")
+ .replacingOccurrences(of: "\n", with: "\n "))
+
+ var body: some View {
+ \(viewName)(items) { item in
+ // Build your view here
+ ZStack {
+ Rectangle().fill(Color.orange)
+ Text("\\(item.number)")
+ }
+ }
+ .options(options)
+ // The padding around each page
+ // you can use `.fractionalWidth` and
+ // `.fractionalHeight` too
+ .pagePadding(
+ vertical: .absolute(100),
+ horizontal: .absolute(80)
+ )
+ }
+
+ }
+ """
+ }
+
+ private func getUIKitCode() -> String {
let viewProtocols = ["ScaleTransformView", "StackTransformView", "SnapshotTransformView"]
let viewProtocolName = viewProtocols.first { code.contains($0) } ?? ""
@@ -88,7 +194,7 @@ struct LayoutDesignerCodePreviewViewModel {
\(code.replacingOccurrences(of: "\n", with: "\n "))
- // The card view that we apply effects on
+ // The card view that we apply transforms on
var card: UIView!
override init(frame: CGRect) {
@@ -102,13 +208,17 @@ struct LayoutDesignerCodePreviewViewModel {
}
func setup() {
- // Adjust the card view frame you can use Autolayout too
- let cardFrame = CGRect(x: 80,
- y: 100,
- width: frame.width - 160,
- height: frame.height - 200)
+
+ // Adjust the card view frame
+ // you can use Auto-layout too
+ let cardFrame = CGRect(
+ x: 80,
+ y: 100,
+ width: frame.width - 160,
+ height: frame.height - 200
+ )
card = UIView(frame: cardFrame)
- card.backgroundColor = .gray
+ card.backgroundColor = .systemOrange
contentView.addSubview(card)
}
}
@@ -125,25 +235,25 @@ struct LayoutDesignerCodePreviewViewModel {
}
private func setupCollectionView() {
+ let layout = CollectionViewPagingLayout()
+
collectionView = UICollectionView(
frame: view.frame,
- collectionViewLayout: CollectionViewPagingLayout()
+ collectionViewLayout: layout
)
+
collectionView.isPagingEnabled = true
- collectionView.register(MyCell.self, forCellWithReuseIdentifier: "cell")
+
+ collectionView.register(
+ MyCell.self,
+ forCellWithReuseIdentifier: "cell"
+ )
+
collectionView.dataSource = self
+
view.addSubview(collectionView)
}
- override func viewDidLayoutSubviews() {
- super.viewDidLayoutSubviews()
- DispatchQueue.main.async { [weak self] in
- self?.collectionView?.performBatchUpdates({ [weak self] in
- self?.collectionView?.collectionViewLayout.invalidateLayout()
- })
- }
- }
-
func collectionView(
_ collectionView: UICollectionView,
numberOfItemsInSection section: Int
@@ -153,7 +263,8 @@ struct LayoutDesignerCodePreviewViewModel {
func collectionView(
_ collectionView: UICollectionView,
- cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+ cellForItemAt indexPath: IndexPath
+ ) -> UICollectionViewCell {
collectionView.dequeueReusableCell(
withReuseIdentifier: "cell",
for: indexPath
diff --git a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Intro/LayoutDesignerIntroViewController.swift b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Intro/LayoutDesignerIntroViewController.swift
index 58babbd..c85f585 100644
--- a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Intro/LayoutDesignerIntroViewController.swift
+++ b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Intro/LayoutDesignerIntroViewController.swift
@@ -34,15 +34,6 @@ class LayoutDesignerIntroViewController: UIViewController {
open()
}
- override func viewDidLayoutSubviews() {
- super.viewDidLayoutSubviews()
- DispatchQueue.main.async { [weak self] in
- self?.collectionView?.performBatchUpdates({ [weak self] in
- self?.collectionView?.collectionViewLayout.invalidateLayout()
- })
- }
- }
-
// MARK: Private functions
diff --git a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/LayoutDesignerViewController.swift b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/LayoutDesignerViewController.swift
index 1f453eb..9fcc358 100644
--- a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/LayoutDesignerViewController.swift
+++ b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/LayoutDesignerViewController.swift
@@ -39,17 +39,9 @@ class LayoutDesignerViewController: UIViewController, ViewModelBased, NibBased {
configureViews()
setOptionsList()
registerKeyboardNotifications()
- }
-
- override func viewWillAppear(_ animated: Bool) {
- super.viewWillAppear(animated)
- if viewModel.shouldShowIntro {
- showIntroViewController(viewModel: viewModel.getIntroViewModel())
- }
- }
-
- deinit {
- NotificationCenter.default.removeObserver(self)
+
+ view.backgroundColor = .clear
+ codeContainerView.backgroundColor = codeContainerView.backgroundColor?.withAlphaComponent(0.6)
}
diff --git a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/LayoutDesignerViewController.xib b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/LayoutDesignerViewController.xib
index 739fe8f..e3ec631 100644
--- a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/LayoutDesignerViewController.xib
+++ b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/LayoutDesignerViewController.xib
@@ -1,10 +1,11 @@
-
+
-
+
+
@@ -25,7 +26,7 @@
-
+
@@ -35,33 +36,33 @@
-
+
-
+
-
+
-
-
+
+
-
+
-
+
@@ -70,7 +71,7 @@
-
+
@@ -78,7 +79,7 @@
-
+
@@ -101,30 +102,30 @@
-
+
-
+
-
-
+
+
-
+
-
+
@@ -133,7 +134,7 @@
-
+
@@ -148,7 +149,7 @@
-
+
@@ -164,30 +165,30 @@
-
+
-
+
-
-
+
+
-
+
-
+
@@ -199,7 +200,7 @@
-
+
@@ -210,7 +211,7 @@
-
+
@@ -232,31 +233,31 @@
-
-
+
-
+
-
+
-
+
-
+
+
@@ -264,18 +265,17 @@
-
-
+
+
-
+
-
@@ -286,5 +286,8 @@
+
+
+
diff --git a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/LayoutDesignerViewModel.swift b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/LayoutDesignerViewModel.swift
index f52ae2c..3f163e2 100644
--- a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/LayoutDesignerViewModel.swift
+++ b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/LayoutDesignerViewModel.swift
@@ -24,13 +24,6 @@ class LayoutDesignerViewModel {
var shapesViewModel: ShapesViewModel {
ShapesViewModel(layouts: layouts, showBackButton: false, showPageControl: true)
}
- var shouldShowIntro: Bool {
- if let introShown = UserDefaults.standard.value(forKey: "IntroShown") as? Bool, introShown {
- return false
- }
- UserDefaults.standard.setValue(true, forKey: "IntroShown")
- return true
- }
private(set) var optionViewModels: [LayoutDesignerOptionSectionViewModel] = []
@@ -81,67 +74,67 @@ class LayoutDesignerViewModel {
let generalOptions: [LayoutDesignerOptionViewModel] = [
.init(title: "Min scale", kind: .singleSlider(current: options.minScale, range: 0...2) { n in
update { $0.minScale = n! }
- }),
+ }),
.init(title: "Max scale", kind: .singleSlider(current: options.maxScale, range: 0...2) { n in
update { $0.maxScale = n! }
- }),
+ }),
.init(title: "Scale ratio", kind: .singleSlider(current: options.scaleRatio, range: 0...2) { n in
update { $0.scaleRatio = n! }
- }),
+ }),
.init(title: "Translation ratio", kind: .doubleSlider(current: options.translationRatio.pair, range: -2...2) { n in
update { $0.translationRatio = .by(pair: n!) }
- }),
+ }),
.init(title: "Min translation ratio", kind: .doubleSlider(current: options.minTranslationRatio?.pair, range: -5...5, optional: true) { n in
update { $0.minTranslationRatio = n.map { .by(pair: $0) } }
- }),
+ }),
.init(title: "Max translation ratio", kind: .doubleSlider(current: options.maxTranslationRatio?.pair, range: -5...5, optional: true) { n in
update { $0.maxTranslationRatio = n.map { .by(pair: $0) } }
- }),
+ }),
.init(title: "Keep vertical spacing equal", kind: .toggleSwitch(current: options.keepVerticalSpacingEqual) { n in
update { $0.keepVerticalSpacingEqual = n }
- }),
+ }),
.init(title: "Keep horizontal spacing equal", kind: .toggleSwitch(current: options.keepHorizontalSpacingEqual) { n in
update { $0.keepHorizontalSpacingEqual = n }
- }),
+ }),
.init(title: "Scale curve", kind: .segmented(options: TransformCurve.all.map(\.name), current: options.scaleCurve.name) { n in
update { $0.scaleCurve = .by(name: n)! }
- }),
+ }),
.init(title: "Translation curve", kind: .segmented(options: TransformCurve.all.map(\.name), current: options.translationCurve.name) { n in
update { $0.translationCurve = .by(name: n)! }
- }),
+ }),
.init(title: "Shadow enabled", kind: .toggleSwitch(current: options.shadowEnabled) { n in
update { $0.shadowEnabled = n }
- }),
+ }),
.init(title: "Shadow opacity", kind: .singleSlider(current: CGFloat(options.shadowOpacity)) { n in
update { $0.shadowOpacity = Float(n!) }
- }),
+ }),
.init(title: "Shadow opacity min", kind: .singleSlider(current: CGFloat(options.shadowOpacityMin)) { n in
update { $0.shadowOpacityMin = Float(n!) }
- }),
+ }),
.init(title: "Shadow opacity max", kind: .singleSlider(current: CGFloat(options.shadowOpacityMax)) { n in
update { $0.shadowOpacityMax = Float(n!) }
- }),
+ }),
.init(title: "Shadow radius max", kind: .singleSlider(current: options.shadowRadiusMax, range: 0...15) { n in
update { $0.shadowRadiusMax = n! }
- }),
+ }),
.init(title: "Shadow radius min", kind: .singleSlider(current: options.shadowRadiusMin, range: 0...15) { n in
update { $0.shadowRadiusMin = n! }
- }),
+ }),
.init(title: "Shadow offset min", kind: .doubleSlider(current: options.shadowOffsetMin.pair, range: -7...7) { n in
update { $0.shadowOffsetMin = .by(pair: n!) }
- }),
+ }),
.init(title: "Shadow offset max", kind: .doubleSlider(current: options.shadowOffsetMax.pair, range: -7...7) { n in
update { $0.shadowOffsetMax = .by(pair: n!) }
- }),
+ }),
.init(title: "Blur effect enabled", kind: .toggleSwitch(current: options.blurEffectEnabled) { n in
update { $0.blurEffectEnabled = n }
- }),
+ }),
.init(title: "Blur effect radius ratio", kind: .singleSlider(current: options.blurEffectRadiusRatio) { n in
update { $0.blurEffectRadiusRatio = n! }
- }),
+ }),
.init(title: "Blur effect style", kind: .segmented(options: UIBlurEffect.Style.all.map(\.name), current: options.blurEffectStyle.name) { n in
update { $0.blurEffectStyle = .by(name: n)! }
- })
+ })
]
let originalRotation3dOptions = options.rotation3d ?? ScaleTransformViewOptions.Rotation3dOptions(
@@ -157,28 +150,28 @@ class LayoutDesignerViewModel {
let rotation3dOptions: [LayoutDesignerOptionViewModel] = [
.init(title: "Enabled", kind: .toggleSwitch(current: options.rotation3d != nil) { n in
update { $0.rotation3d = !n ? nil : originalRotation3dOptions }
- }),
+ }),
.init(title: "Angle", kind: .singleSlider(current: options.rotation3d?.angle, range: -.pi...CGFloat.pi) { n in
update { $0.rotation3d?.angle = n! }
- }),
+ }),
.init(title: "Min angle", kind: .singleSlider(current: options.rotation3d?.minAngle, range: -.pi...CGFloat.pi) { n in
update { $0.rotation3d?.minAngle = n! }
- }),
+ }),
.init(title: "Max angle", kind: .singleSlider(current: options.rotation3d?.maxAngle, range: -.pi...CGFloat.pi) { n in
update { $0.rotation3d?.maxAngle = n! }
- }),
+ }),
.init(title: "X", kind: .singleSlider(current: options.rotation3d?.x, range: -1...1) { n in
update { $0.rotation3d?.x = n! }
- }),
+ }),
.init(title: "Y", kind: .singleSlider(current: options.rotation3d?.y, range: -1...1) { n in
update { $0.rotation3d?.y = n! }
- }),
+ }),
.init(title: "Z", kind: .singleSlider(current: options.rotation3d?.z, range: -1...1) { n in
update { $0.rotation3d?.z = n! }
- }),
+ }),
.init(title: "m34", kind: .singleSlider(current: options.rotation3d.map { $0.m34 * 1_000 }, range: -2...2) { n in
update { $0.rotation3d?.m34 = n! / 1_000 }
- })
+ })
]
let originalTranslation3dOptions = options.translation3d ?? ScaleTransformViewOptions.Translation3dOptions(
@@ -190,61 +183,61 @@ class LayoutDesignerViewModel {
let translation3dOptions: [LayoutDesignerOptionViewModel] = [
.init(title: "Enabled", kind: .toggleSwitch(current: options.translation3d != nil) { n in
update { $0.translation3d = !n ? nil : originalTranslation3dOptions }
- }),
+ }),
.init(title: "X ratio", kind: .singleSlider(current: options.translation3d?.translateRatios.0, range: -5...5) { n in
update {
guard let current = $0.translation3d?.translateRatios else { return }
$0.translation3d?.translateRatios = (n!, current.1, current.2)
}
- }),
+ }),
.init(title: "X min ratio", kind: .singleSlider(current: options.translation3d?.minTranslateRatios.0, range: -10...10) { n in
update {
guard let current = $0.translation3d?.minTranslateRatios else { return }
$0.translation3d?.minTranslateRatios = (n!, current.1, current.2)
}
- }),
+ }),
.init(title: "X max ratio", kind: .singleSlider(current: options.translation3d?.maxTranslateRatios.0, range: -10...10) { n in
update {
guard let current = $0.translation3d?.maxTranslateRatios else { return }
$0.translation3d?.maxTranslateRatios = (n!, current.1, current.2)
}
- }),
+ }),
.init(title: "Y ratio", kind: .singleSlider(current: options.translation3d?.translateRatios.1, range: -5...5) { n in
update {
guard let current = $0.translation3d?.translateRatios else { return }
$0.translation3d?.translateRatios = (current.0, n!, current.2)
}
- }),
+ }),
.init(title: "Y min ratio", kind: .singleSlider(current: options.translation3d?.minTranslateRatios.1, range: -10...10) { n in
update {
guard let current = $0.translation3d?.minTranslateRatios else { return }
$0.translation3d?.minTranslateRatios = (current.0, n!, current.2)
}
- }),
+ }),
.init(title: "Y max ratio", kind: .singleSlider(current: options.translation3d?.maxTranslateRatios.1, range: -10...10) { n in
update {
guard let current = $0.translation3d?.maxTranslateRatios else { return }
$0.translation3d?.maxTranslateRatios = (current.0, n!, current.2)
}
- }),
+ }),
.init(title: "Z ratio", kind: .singleSlider(current: options.translation3d?.translateRatios.2, range: -5...5) { n in
update {
guard let current = $0.translation3d?.translateRatios else { return }
$0.translation3d?.translateRatios = (current.0, current.1, n!)
}
- }),
+ }),
.init(title: "Z min ratio", kind: .singleSlider(current: options.translation3d?.minTranslateRatios.2, range: -10...10) { n in
update {
guard let current = $0.translation3d?.minTranslateRatios else { return }
$0.translation3d?.minTranslateRatios = (current.0, current.1, n!)
}
- }),
+ }),
.init(title: "Z max ratio", kind: .singleSlider(current: options.translation3d?.maxTranslateRatios.2, range: -10...10) { n in
update {
guard let current = $0.translation3d?.maxTranslateRatios else { return }
$0.translation3d?.maxTranslateRatios = (current.0, current.1, n!)
}
- })
+ })
]
@@ -265,67 +258,67 @@ class LayoutDesignerViewModel {
let viewModels: [LayoutDesignerOptionViewModel] = [
.init(title: "Scale factor", kind: .singleSlider(current: options.scaleFactor, range: -1...1) { n in
update { $0.scaleFactor = n! }
- }),
+ }),
.init(title: "Min scale", kind: .singleSlider(current: options.minScale, optional: true) { n in
update { $0.minScale = n }
- }),
+ }),
.init(title: "Max scale", kind: .singleSlider(current: options.maxScale, optional: true) { n in
update { $0.maxScale = n }
- }),
+ }),
.init(title: "Spacing factor", kind: .singleSlider(current: options.spacingFactor, range: 0...0.5) { n in
update { $0.spacingFactor = n! }
- }),
+ }),
.init(title: "Max spacing", kind: .singleSlider(current: options.maxSpacing, optional: true) { n in
update { $0.maxSpacing = n }
- }),
+ }),
.init(title: "Alpha factor", kind: .singleSlider(current: options.alphaFactor) { n in
update { $0.alphaFactor = n! }
- }),
+ }),
.init(title: "Bottom stack alpha speed factor", kind: .singleSlider(current: options.bottomStackAlphaSpeedFactor, range: 0...10) { n in
update { $0.bottomStackAlphaSpeedFactor = n! }
- }),
+ }),
.init(title: "Top stack alpha speed factor", kind: .singleSlider(current: options.topStackAlphaSpeedFactor, range: 0...10) { n in
update { $0.topStackAlphaSpeedFactor = n! }
- }),
+ }),
.init(title: "Perspective ratio", kind: .singleSlider(current: options.perspectiveRatio, range: -1...1) { n in
update { $0.perspectiveRatio = n! }
- }),
+ }),
.init(title: "Shadow enabled", kind: .toggleSwitch(current: options.shadowEnabled) { n in
update { $0.shadowEnabled = n }
- }),
+ }),
.init(title: "Shadow opacity", kind: .singleSlider(current: CGFloat(options.shadowOpacity)) { n in
update { $0.shadowOpacity = Float(n!) }
- }),
+ }),
.init(title: "Shadow offset", kind: .doubleSlider(current: options.shadowOffset.pair) { n in
update { $0.shadowOffset = .by(pair: n!) }
- }),
+ }),
.init(title: "Shadow radius", kind: .singleSlider(current: options.shadowRadius, range: 1...10) { n in
update { $0.shadowRadius = n! }
- }),
+ }),
.init(title: "Rotate angel", kind: .singleSlider(current: options.stackRotateAngel, range: -CGFloat.pi...CGFloat.pi) { n in
update { $0.stackRotateAngel = n! }
- }),
+ }),
.init(title: "Pop angle", kind: .singleSlider(current: options.popAngle, range: -CGFloat.pi...CGFloat.pi) { n in
update { $0.popAngle = n! }
- }),
+ }),
.init(title: "Pop offset ratio", kind: .doubleSlider(current: options.popOffsetRatio.pair, range: -2...2) { n in
update { $0.popOffsetRatio = .by(pair: n!) }
- }),
+ }),
.init(title: "Stack position", kind: .doubleSlider(current: options.stackPosition.pair, range: -1...1) { n in
update { $0.stackPosition = .by(pair: n!) }
- }),
+ }),
.init(title: "Reverse", kind: .toggleSwitch(current: options.reverse) { n in
update { $0.reverse = n }
- }),
+ }),
.init(title: "Blur effect enabled", kind: .toggleSwitch(current: options.blurEffectEnabled) { n in
update { $0.blurEffectEnabled = n }
- }),
+ }),
.init(title: "Max blur radius", kind: .singleSlider(current: options.maxBlurEffectRadius) { n in
update { $0.maxBlurEffectRadius = n! }
- }),
+ }),
.init(title: "Blur effect style", kind: .segmented(options: UIBlurEffect.Style.all.map(\.name), current: options.blurEffectStyle.name) { n in
update { $0.blurEffectStyle = .by(name: n)! }
- })
+ })
]
return [
@@ -343,19 +336,19 @@ class LayoutDesignerViewModel {
let viewModels: [LayoutDesignerOptionViewModel] = [
.init(title: "Piece size ratio", kind: .doubleSlider(current: options.pieceSizeRatio.pair, range: 0.01...1) { n in
update { $0.pieceSizeRatio = .by(pair: n!) }
- }),
+ }),
.init(title: "Container scale ratio", kind: .singleSlider(current: options.containerScaleRatio) { n in
update { $0.containerScaleRatio = n! }
- }),
+ }),
.init(title: "Container translation ratio", kind: .doubleSlider(current: options.containerTranslationRatio.pair, range: 0...2) { n in
update { $0.containerTranslationRatio = .by(pair: n!) }
- }),
+ }),
.init(title: "Container min translation ratio", kind: .doubleSlider(current: options.containerMinTranslationRatio?.pair, range: 0...2, optional: true) { n in
update { $0.containerMinTranslationRatio = n.map { .by(pair: $0) } }
- }),
+ }),
.init(title: "Container max translation ratio", kind: .doubleSlider(current: options.containerMaxTranslationRatio?.pair, range: 0...2, optional: true) { n in
update { $0.containerMaxTranslationRatio = n.map { .by(pair: $0) } }
- })
+ })
]
return [
.init(title: "Options", items: viewModels)
diff --git a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Options/LayoutDesignerOptionsTableView.swift b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Options/LayoutDesignerOptionsTableView.swift
index 59aae06..8f17d70 100644
--- a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Options/LayoutDesignerOptionsTableView.swift
+++ b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Options/LayoutDesignerOptionsTableView.swift
@@ -36,7 +36,7 @@ class LayoutDesignerOptionsTableView: UITableView {
backgroundColor = .clear
separatorStyle = .none
allowsSelection = false
- sectionHeaderHeight = 60
+ sectionHeaderHeight = 40
}
}
@@ -66,9 +66,9 @@ extension LayoutDesignerOptionsTableView: UITableViewDelegate {
let label = UILabel()
label.textColor = .white
label.adjustsFontSizeToFitWidth = true
- label.font = .systemFont(ofSize: 33, weight: .medium)
+ label.font = .systemFont(ofSize: 22, weight: .medium)
label.text = optionViewModels[section].title
- header.fill(with: label, edges: .init(top: 10, left: 24, bottom: -10, right: -24))
+ header.fill(with: label, edges: .init(top: 5, left: 24, bottom: -3, right: -24))
header.backgroundColor = #colorLiteral(red: 0.1607843137, green: 0.2705882353, blue: 0.8431372549, alpha: 1)
return header
}
diff --git a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Options/cell/LayoutDesignerOptionCell.swift b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Options/cell/LayoutDesignerOptionCell.swift
index 7c0e5a6..a4c98da 100644
--- a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Options/cell/LayoutDesignerOptionCell.swift
+++ b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Options/cell/LayoutDesignerOptionCell.swift
@@ -58,15 +58,18 @@ class LayoutDesignerOptionCell: UITableViewCell, NibBased {
nilLabel.textColor = UIColor.white.withAlphaComponent(0.7)
[singleSlider, doubleSlider1, doubleSlider2].forEach {
$0?.tintColor = .white
- $0?.thumbTintColor = .white
- $0?.maximumTrackTintColor = UIColor.white.withAlphaComponent(0.3)
- $0?.minimumTrackTintColor = .white
+ if UIDevice.current.userInterfaceIdiom != .mac {
+ $0?.thumbTintColor = .white
+ $0?.maximumTrackTintColor = UIColor.white.withAlphaComponent(0.3)
+ $0?.minimumTrackTintColor = .white
+ }
$0?.addTarget(self, action: #selector(onSliderChange(slider:)), for: .valueChanged)
}
[singleSliderInput, doubleSliderInput1, doubleSliderInput2].forEach {
$0?.backgroundColor = .white
$0?.textAlignment = .center
$0?.layer.cornerRadius = 8
+ $0?.layer.masksToBounds = true
$0?.addTarget(self, action: #selector(onInputChange(input:)), for: .editingChanged)
$0?.keyboardType = .decimalPad
}
diff --git a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Options/cell/LayoutDesignerOptionCell.xib b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Options/cell/LayoutDesignerOptionCell.xib
index d101327..572d158 100644
--- a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Options/cell/LayoutDesignerOptionCell.xib
+++ b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Options/cell/LayoutDesignerOptionCell.xib
@@ -1,14 +1,14 @@
-
+
-
+
-
+
@@ -22,30 +22,30 @@
-
+
-
+
-
+
@@ -72,7 +72,7 @@
-
+
@@ -88,7 +88,7 @@
-
+
diff --git a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/OptionsCodeGenerator.swift b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/OptionsCodeGenerator.swift
index 29bc357..e5e5419 100644
--- a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/OptionsCodeGenerator.swift
+++ b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/OptionsCodeGenerator.swift
@@ -15,11 +15,49 @@ class OptionsCodeGenerator {
func generateCode(options: T) -> String {
if let options = options as? ScaleTransformViewOptions {
- return generateCode(options: options)
+
+ let code = generateCode(options: options)
+ let layoutOptions = ScaleTransformViewOptions.Layout.allCases
+ .map { (options: ScaleTransformViewOptions.layout($0), layout: $0) }
+ .first { generateCode(options: $0.options) == code }
+ if let layoutOptions = layoutOptions {
+ return """
+ var scaleOptions: ScaleTransformViewOptions {
+ .layout(.\(layoutOptions.layout.rawValue))
+ }
+ """
+ }
+ return code
+
} else if let options = options as? StackTransformViewOptions {
- return generateCode(options: options)
+
+ let code = generateCode(options: options)
+ let layoutOptions = StackTransformViewOptions.Layout.allCases
+ .map { (options: StackTransformViewOptions.layout($0), layout: $0) }
+ .first { generateCode(options: $0.options) == code }
+ if let layoutOptions = layoutOptions {
+ return """
+ var stackOptions: StackTransformViewOptions {
+ .layout(.\(layoutOptions.layout.rawValue))
+ }
+ """
+ }
+ return code
+
} else if let options = options as? SnapshotTransformViewOptions {
- return generateCode(options: options)
+
+ let code = generateCode(options: options)
+ let layoutOptions = SnapshotTransformViewOptions.Layout.allCases
+ .map { (options: SnapshotTransformViewOptions.layout($0), layout: $0) }
+ .first { generateCode(options: $0.options) == code }
+ if let layoutOptions = layoutOptions {
+ return """
+ var snapshotOptions: SnapshotTransformViewOptions {
+ .layout(.\(layoutOptions.layout.rawValue))
+ }
+ """
+ }
+ return code
}
return ""
}
diff --git a/Samples/PagingLayoutSamples/Modules/Main/MainViewController.swift b/Samples/PagingLayoutSamples/Modules/Main/MainViewController.swift
index 9563694..b881919 100644
--- a/Samples/PagingLayoutSamples/Modules/Main/MainViewController.swift
+++ b/Samples/PagingLayoutSamples/Modules/Main/MainViewController.swift
@@ -8,6 +8,7 @@
import Foundation
import UIKit
+import SwiftUI
class MainViewController: UIViewController, NibBased {
@@ -16,50 +17,153 @@ class MainViewController: UIViewController, NibBased {
override var preferredStatusBarStyle: UIStatusBarStyle {
.lightContent
}
-
+
+ @IBOutlet fileprivate weak var frameworkSegmentedControl: UISegmentedControl!
+ @IBOutlet private weak var transformTitleLabel: UILabel!
+ @IBOutlet private var transformSubtitles: [UILabel]!
+ @IBOutlet private weak var swiftUICustomBuildsContainer: UIView!
+ @IBOutlet private weak var uiKitCustomBuildsContainer: UIView!
+
+ private var isSwiftUI: Bool {
+ frameworkSegmentedControl.selectedSegmentIndex == 0
+ }
+
+
+ // MARK: ViewController
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ configureFrameworkSegmentedControl()
+ updateViewsBasedOnSelectedFramework()
+ swiftUICustomBuildsContainer.setNeedsLayout()
+ }
+
// MARK: Event listeners
@IBAction private func stackButtonTouched() {
- navigationController?.pushViewController(
- ShapesViewController.instantiate(viewModel: ShapesViewModel(layouts: .stack)),
- animated: true
- )
+ if isSwiftUI {
+ push(makeViewController(ShapesListView(layoutGroup: .stack),
+ backButtonColor: .gray,
+ statusBarStyle: .darkContent))
+ } else {
+ push(ShapesViewController.instantiate(viewModel: ShapesViewModel(layouts: .stack)))
+ }
}
@IBAction private func scaleButtonTouched() {
- navigationController?.pushViewController(
- ShapesViewController.instantiate(viewModel: ShapesViewModel(layouts: .scale)),
- animated: true
- )
+ if isSwiftUI {
+ push(makeViewController(ShapesListView(layoutGroup: .scale),
+ backButtonColor: .gray,
+ statusBarStyle: .darkContent))
+ } else {
+ push(ShapesViewController.instantiate(viewModel: ShapesViewModel(layouts: .scale)))
+ }
}
@IBAction private func snapshotButtonTouched() {
- navigationController?.pushViewController(
- ShapesViewController.instantiate(viewModel: ShapesViewModel(layouts: .snapshot)),
- animated: true
- )
+ if isSwiftUI {
+ push(makeViewController(ShapesListView(layoutGroup: .snapshot),
+ backButtonColor: .gray,
+ statusBarStyle: .darkContent))
+ } else {
+ push(ShapesViewController.instantiate(viewModel: ShapesViewModel(layouts: .snapshot)))
+ }
}
@IBAction private func fruitsButtonTouched() {
- navigationController?.pushViewController(
- FruitsViewController.instantiate(viewModel: FruitsViewModel()),
- animated: true
- )
+ push(FruitsViewController.instantiate(viewModel: FruitsViewModel()))
}
@IBAction private func galleryButtonTouched() {
- navigationController?.pushViewController(
- GalleryViewController.instantiate(viewModel: GalleryViewModel()),
- animated: true
- )
+ push(GalleryViewController.instantiate(viewModel: GalleryViewModel()))
}
@IBAction private func cardsButtonTouched() {
- navigationController?.pushViewController(
- CardsViewController.instantiate(viewModel: CardsViewModel()),
- animated: true
- )
+ push(CardsViewController.instantiate(viewModel: CardsViewModel()))
+ }
+
+ @IBAction private func devicesButtonTouched() {
+ push(makeViewController(DevicesView(), backButtonColor: .white, statusBarStyle: .lightContent))
+ }
+
+ @IBAction private func transportButtonTouched() {
+ // TODO:
+ }
+
+ @IBAction private func weatherButtonTouched() {
+ push(makeViewController(WeatherTabView(), backButtonColor: .gray, statusBarStyle: .darkContent))
+ }
+
+ @IBAction private func onFrameworkChanged() {
+ updateViewsBasedOnSelectedFramework()
+ }
+
+
+ // MARK: Private functions
+
+ private func configureFrameworkSegmentedControl() {
+ frameworkSegmentedControl.overrideUserInterfaceStyle = .dark
+ }
+
+ private func updateViewsBasedOnSelectedFramework() {
+ transformTitleLabel.text = isSwiftUI ? "PageView" : "Transforms"
+ transformSubtitles.forEach { $0.text = transformTitleLabel.text }
+
+ swiftUICustomBuildsContainer.isHidden = !isSwiftUI
+ uiKitCustomBuildsContainer.isHidden = isSwiftUI
+ }
+
+ private func push(_ viewController: UIViewController) {
+ navigationController?.pushViewController(viewController, animated: true)
+ }
+
+ @objc private func pop() {
+ navigationController?.popViewController(animated: true)
+ }
+
+ private func makeViewController(_ view: T,
+ backButtonColor: UIColor?,
+ statusBarStyle: UIStatusBarStyle?) -> UIViewController {
+ let viewController = MainHostingViewController()
+ viewController.statusBarStyle = statusBarStyle
+ viewController.view.fill(with: UIHostingController(rootView: view).view)
+ if let backButtonColor = backButtonColor {
+ let backButton = UIButton(type: .custom)
+ backButton.setImage(UIImage(systemName: "arrow.left.square.fill",
+ withConfiguration: UIImage.SymbolConfiguration(pointSize: 28, weight: .semibold)),
+ for: .normal)
+ backButton.translatesAutoresizingMaskIntoConstraints = false
+ viewController.view.addSubview(backButton)
+ backButton.leadingAnchor.constraint(equalTo: viewController.view.leadingAnchor, constant: 32).isActive = true
+ backButton.topAnchor.constraint(equalTo: viewController.view.safeAreaLayoutGuide.topAnchor, constant: 19).isActive = true
+ backButton.tintColor = backButtonColor
+ backButton.addTarget(self, action: #selector(pop), for: .touchUpInside)
+ }
+
+ return viewController
+ }
+}
+
+
+class MainHostingViewController: UIViewController {
+
+ fileprivate var statusBarStyle: UIStatusBarStyle?
+
+ override var preferredStatusBarStyle: UIStatusBarStyle {
+ statusBarStyle ?? .lightContent
+ }
+}
+
+
+class MainViewControllerScrollView: UIScrollView {
+ override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
+ if let mainVC = superview?.next as? MainViewController {
+ let point = convert(point, to: mainVC.view)
+ if mainVC.frameworkSegmentedControl.frame.contains(point) {
+ return mainVC.frameworkSegmentedControl.hitTest(point, with: event)
+ }
+ }
+ return super.hitTest(point, with: event)
}
-
}
diff --git a/Samples/PagingLayoutSamples/Modules/Main/MainViewController.xib b/Samples/PagingLayoutSamples/Modules/Main/MainViewController.xib
index 60074f2..cbe3a62 100644
--- a/Samples/PagingLayoutSamples/Modules/Main/MainViewController.xib
+++ b/Samples/PagingLayoutSamples/Modules/Main/MainViewController.xib
@@ -1,15 +1,24 @@
-
-
+
+
-
+
+
+
-
+
+
+
+
+
+
+
+
@@ -17,30 +26,53 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
@@ -67,366 +99,543 @@
-
-
+
+
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
+
+
-
+
+
-
+
+
+
-
-
-
-
+
+
-
-
+
-
+
+
@@ -434,5 +643,9 @@
+
+
+
+
diff --git a/Samples/PagingLayoutSamples/Modules/SwiftUI/DevicesView.swift b/Samples/PagingLayoutSamples/Modules/SwiftUI/DevicesView.swift
new file mode 100644
index 0000000..1961a6c
--- /dev/null
+++ b/Samples/PagingLayoutSamples/Modules/SwiftUI/DevicesView.swift
@@ -0,0 +1,133 @@
+//
+// DevicesView.swift
+// PagingLayoutSamples
+//
+// Created by Amir Khorsandi on 30/01/2021.
+// Copyright © 2021 Amir Khorsandi. All rights reserved.
+//
+
+// Design by Cuberto https://dribbble.com/shots/12580831-Principle-Tutorial-Onboarding-Flow-Animation
+
+import SwiftUI
+import CollectionViewPagingLayout
+
+struct Device: Identifiable {
+ let name: String
+ let iconName: String
+ let color: Color
+
+ var id: String {
+ name
+ }
+}
+
+struct DevicesView: View {
+
+ private let devices: [Device] = [
+ Device(name: "iPad", iconName: "ipad", color: .yellow),
+ Device(name: "Apple Watch", iconName: "applewatch", color: .green),
+ Device(name: "iPhone", iconName: "iphone", color: .orange),
+ Device(name: "AirPods Pro", iconName: "airpodspro", color: .blue),
+ Device(name: "Mac Pro", iconName: "macpro.gen3", color: .red),
+ Device(name: "HomePod", iconName: "homepod", color: .purple)
+ ]
+
+ private let scaleFactor: CGFloat = 130
+ private let circleSize: CGFloat = 80
+
+ @State private var currentDeviceName: String?
+
+ var body: some View {
+ TransformPageView(devices, selection: $currentDeviceName) { device, progress in
+ ZStack {
+ roundedRectangle(device: device, progress: progress)
+ deviceView(device: device, progress: progress)
+ HStack {
+ Image(systemName: "chevron.right")
+ }
+ .foregroundColor(.white)
+ .font(.system(size: 30))
+ .transformEffect(.init(translationX: -300 * (progress - 1), y: 0))
+ .padding(.top, 400)
+ .opacity(1 - Double(abs(progress - 1)))
+ }
+ }
+ .animator(DefaultViewAnimator(0.7, curve: .parametric))
+ .scrollToSelectedPage(false)
+ .onTapPage { name in
+ currentDeviceName = currentDeviceName == devices.last?.name ? devices.first?.name : name
+ }
+ .zPosition(zPosition)
+ .collectionView(\.showsHorizontalScrollIndicator, false)
+ .ignoresSafeArea()
+ }
+
+ private func deviceView(device: Device, progress: CGFloat) -> some View {
+ VStack {
+ Image(systemName: device.iconName)
+ .font(.system(size: 160))
+ Text(device.name)
+ .font(.system(size: 40))
+ .padding(.top, 10)
+ Spacer()
+ .frame(maxHeight: 200)
+ }
+ .frame(maxHeight: .infinity)
+ .foregroundColor(.white)
+ .transformEffect(.init(translationX: 400 * progress, y: 0))
+ }
+
+ private func roundedRectangle(device: Device, progress: CGFloat) -> some View {
+ let scale = getScale(progress)
+ return RoundedRectangle(cornerRadius: circleSize * ((0.2 * scaleFactor) / scale))
+ .fill()
+ .frame(width: circleSize, height: circleSize)
+ .scaleEffect(scale, anchor: scaleAnchor(progress))
+ .transformEffect(.init(translationX: translationX(progress), y: 0))
+ .padding(.top, 400)
+ .foregroundColor(device.color)
+ .opacity((1.25 - max(1, abs(Double(progress)))) / 0.25)
+ }
+
+ private func translationX(_ progress: CGFloat) -> CGFloat {
+ guard progress >= 1 || progress < -0.5 else { return 0 }
+ return -2 * (progress + (progress > 0 ? -1 : 1)) * circleSize
+ }
+
+ private func zPosition(_ progress: CGFloat) -> Int {
+ if progress < -1 { return 3 }
+ if progress < 0 { return 2 }
+ if progress < 0.5 { return 1 }
+ if progress <= 1 { return 4 }
+ if progress < 1.5 { return 2 }
+ return -1
+ }
+
+ private func getScale(_ progress: CGFloat) -> CGFloat {
+ var scale: CGFloat = progress > 1 ? progress - 1 : 1 - progress
+ if progress <= -1 {
+ scale = -progress - 1
+ } else if progress < -0.5 {
+ scale = progress + 1
+ } else if progress <= 0.5 {
+ scale = scaleFactor
+ }
+ return 1 + scale * scaleFactor
+ }
+
+ private func scaleAnchor(_ progress: CGFloat) -> UnitPoint {
+ if progress <= -1 { return .leading }
+ if progress <= -0.5 { return .trailing }
+ if progress < 0.5 { return .center }
+ if progress < 1 { return .leading }
+ return .trailing
+ }
+}
+
+
+struct DevicesView_Previews: PreviewProvider {
+ static var previews: some View {
+ DevicesView()
+ .ignoresSafeArea()
+ }
+}
diff --git a/Samples/PagingLayoutSamples/Modules/SwiftUI/Shapes/ShapesListView.ShapeView.swift b/Samples/PagingLayoutSamples/Modules/SwiftUI/Shapes/ShapesListView.ShapeView.swift
new file mode 100644
index 0000000..4757243
--- /dev/null
+++ b/Samples/PagingLayoutSamples/Modules/SwiftUI/Shapes/ShapesListView.ShapeView.swift
@@ -0,0 +1,50 @@
+//
+// ShapesListView.ShapeView.swift
+// PagingLayoutSamples
+//
+// Created by Amir Khorsandi on 17/04/2021.
+// Copyright © 2021 Amir Khorsandi. All rights reserved.
+//
+
+import SwiftUI
+
+extension ShapesListView {
+ struct ShapeView: View {
+ let shape: Shape
+ let color: LinearGradient
+
+ var body: some View {
+ ZStack {
+ color
+ .border(Color.white, width: 6)
+ VStack {
+ Image(systemName: shape.iconName)
+ .font(.system(size: 42))
+ .padding(.bottom, 10)
+ Text(shape.name)
+ .font(.title2)
+ Image("textPlaceholder")
+ .resizable()
+ .scaledToFit()
+ .frame(maxWidth: 90)
+ .padding(.horizontal, 20)
+ }
+ }
+ .foregroundColor(.white)
+ }
+ }
+}
+
+struct ShapesListView_ShapeView_Previews: PreviewProvider {
+ static var previews: some View {
+ ShapesListView.ShapeView(
+ shape: .init(name: "Hexagon", iconName: "hexagon.fill"),
+ color: LinearGradient(
+ gradient: .init(colors: [.red, .black]),
+ startPoint: .topLeading,
+ endPoint: .bottomLeading
+ )
+ )
+ .previewLayout(.fixed(width: 190, height: 300))
+ }
+}
diff --git a/Samples/PagingLayoutSamples/Modules/SwiftUI/Shapes/ShapesListView.swift b/Samples/PagingLayoutSamples/Modules/SwiftUI/Shapes/ShapesListView.swift
new file mode 100644
index 0000000..4cd41da
--- /dev/null
+++ b/Samples/PagingLayoutSamples/Modules/SwiftUI/Shapes/ShapesListView.swift
@@ -0,0 +1,149 @@
+//
+// ShapesListView.swift
+// PagingLayoutSamples
+//
+// Created by Amir Khorsandi on 17/04/2021.
+// Copyright © 2021 Amir Khorsandi. All rights reserved.
+//
+
+import SwiftUI
+import CollectionViewPagingLayout
+
+struct ShapesListView: View {
+
+ let layoutGroup: LayoutGroup
+
+ let shapes: [Shape] = [
+ .init(name: "Hexagon", iconName: "hexagon.fill"),
+ .init(name: "Rectangle", iconName: "rectangle.fill"),
+ .init(name: "Shield", iconName: "shield.fill"),
+ .init(name: "App", iconName: "app.fill"),
+ .init(name: "Triangle", iconName: "triangle.fill"),
+ .init(name: "Circle", iconName: "circle.fill"),
+ .init(name: "Square", iconName: "square.fill"),
+ .init(name: "Capsule", iconName: "capsule.fill")
+ ]
+
+ var scaleGradient: LinearGradient {
+ LinearGradient(gradient: .init(colors: [Color("pastelRed"), Color("supernova")]),
+ startPoint: .topLeading, endPoint: .bottomTrailing)
+ }
+
+ var stackGradient: LinearGradient {
+ LinearGradient(gradient: .init(colors: [Color("babyBlue"), Color("matisse")]),
+ startPoint: .topLeading, endPoint: .bottomTrailing)
+ }
+
+ var snapshotGradient: LinearGradient {
+ LinearGradient(gradient: .init(colors: [Color("chartreuseYellow"), Color("mayaBlue")]),
+ startPoint: .topLeading, endPoint: .bottomTrailing)
+ }
+
+ var body: some View {
+ VStack {
+ Text("S\(String(layoutGroup.rawValue.dropFirst()))PageView")
+ .font(.title2)
+ .fontWeight(.bold)
+ .padding(.top, 19)
+
+ ScrollView {
+ switch layoutGroup {
+ case .stack:
+ stackLayouts()
+ case .scale:
+ scaleLayouts()
+ case .snapshot:
+ snapshotLayouts()
+ }
+ }
+ .frame(maxWidth: .infinity)
+ }
+ }
+
+ func stackLayouts() -> some View {
+ ForEach(StackTransformViewOptions.Layout.allCases) { layout in
+ VStack(alignment: .leading) {
+ layoutTitle(layout.rawValue)
+ StackPageView(shapes) { shape in
+ ShapeView(shape: shape, color: stackGradient)
+ }
+ .options(.layout(layout))
+ .pagePadding(vertical: .fractionalHeight(0.1),
+ horizontal: .fractionalWidth(0.3))
+ .frame(height: 300)
+ .padding(10)
+ .background(Color("Background"))
+ .cornerRadius(26)
+ }
+ .padding(19)
+ }
+ }
+
+ func scaleLayouts() -> some View {
+ ForEach(ScaleTransformViewOptions.Layout.allCases) { layout in
+ VStack(alignment: .leading) {
+ layoutTitle(layout.rawValue)
+ ScalePageView(shapes) { shape in
+ ShapeView(shape: shape, color: scaleGradient)
+ }
+ .options(.layout(layout))
+ .pagePadding(vertical: .fractionalHeight(0.1),
+ horizontal: .fractionalWidth(0.3))
+ .frame(height: 300)
+ .padding(10)
+ .background(Color("Background"))
+ .cornerRadius(26)
+ }
+ .padding(19)
+ }
+ }
+
+ func snapshotLayouts() -> some View {
+ ForEach(SnapshotTransformViewOptions.Layout.allCases) { layout in
+ VStack(alignment: .leading) {
+ layoutTitle(layout.rawValue)
+ SnapshotPageView(shapes) { shape in
+ ShapeView(shape: shape, color: snapshotGradient)
+ }
+ .options(.layout(layout))
+ .pagePadding(vertical: .fractionalHeight(0.1),
+ horizontal: .fractionalWidth(0.3))
+ .frame(height: 300)
+ .padding(10)
+ .background(Color("Background"))
+ .cornerRadius(26)
+ }
+ .padding(19)
+ }
+ }
+
+ func layoutTitle(_ title: String) -> some View {
+ Text(".\(title)")
+ .fontWeight(.semibold)
+ .foregroundColor(.gray)
+ .padding(.bottom, 8)
+ }
+}
+
+extension StackTransformViewOptions.Layout: Identifiable {
+ public var id: String {
+ rawValue
+ }
+}
+extension ScaleTransformViewOptions.Layout: Identifiable {
+ public var id: String {
+ rawValue
+ }
+}
+extension SnapshotTransformViewOptions.Layout: Identifiable {
+ public var id: String {
+ rawValue
+ }
+}
+
+
+extension ShapesListView {
+ enum LayoutGroup: String {
+ case stack, scale, snapshot
+ }
+}
diff --git a/Samples/PagingLayoutSamples/Modules/SwiftUI/WeatherTabView/WeatherPage.swift b/Samples/PagingLayoutSamples/Modules/SwiftUI/WeatherTabView/WeatherPage.swift
new file mode 100644
index 0000000..91f0ceb
--- /dev/null
+++ b/Samples/PagingLayoutSamples/Modules/SwiftUI/WeatherTabView/WeatherPage.swift
@@ -0,0 +1,141 @@
+//
+// WeatherPage.swift
+// PagingLayoutSamples
+//
+// Created by Amir Khorsandi on 14/04/2021.
+// Copyright © 2021 Amir Khorsandi. All rights reserved.
+//
+
+import Foundation
+
+enum WeatherPage: String, CaseIterable, Identifiable {
+ case sun
+ case lightning
+ case tornado
+ case moon
+ case snow
+
+ var id: String {
+ rawValue
+ }
+
+ var imageName: String {
+ switch self {
+ case .sun:
+ return "sun.max.fill"
+ case .lightning:
+ return "cloud.bolt.rain.fill"
+ case .moon:
+ return "moon.stars.fill"
+ case .tornado:
+ return "tornado"
+ case .snow:
+ return "snow"
+ }
+ }
+
+ var name: String {
+ rawValue.prefix(1).capitalized + rawValue.dropFirst()
+ }
+}
+
+extension WeatherPage {
+ struct Content {
+ let items: [Item]
+
+ enum Item {
+ case text(String)
+ case image(String)
+ }
+ }
+}
+
+// swiftlint:disable line_length
+extension WeatherPage {
+ var content: Content {
+ switch self {
+ case .sun:
+ return .init(items: [
+ .text(
+ """
+ The Sun is the star at the center of the Solar System. It is a nearly perfect sphere of hot plasma, heated to incandescence by nuclear fusion reactions in its core, radiating the energy mainly as visible light and infrared radiation.
+ It is by far the most important source of energy for life on Earth.
+ Its diameter is about 1.39 million kilometres (864,000 miles), or 109 times that of Earth.
+ Its mass is about 330,000 times that of Earth, and accounts for about 99.86% of the total mass of the Solar System.
+ """
+ ),
+ .image("sun1"),
+ .text(
+ """
+ The Sun is a G-type main-sequence star (G2V) based on its spectral class. As such, it is informally and not completely accurately referred to as a yellow dwarf (its light is closer to white than yellow).
+ It formed approximately 4.6 billion years ago from the gravitational collapse of matter within a region of a large molecular cloud.
+ """
+ ),
+ .image("sun2")
+ ])
+ case .lightning:
+ return .init(items: [
+ .text(
+ """
+ Lightning is a naturally occurring electrostatic discharge during which two electrically charged regions in the atmosphere or ground temporarily equalize themselves, causing the instantaneous release of as much as one gigajoule of energy.
+ This discharge may produce a wide range of electromagnetic radiation, from very hot plasma created by the rapid movement of electrons to brilliant flashes of visible light in the form of black-body radiation. Lightning causes thunder, a sound from the shock wave which develops as gases in the vicinity of the discharge experience a sudden increase in pressure.
+ """
+ ),
+ .image("lightning1"),
+ .text(
+ """
+ The three main kinds of lightning are distinguished by where they occur: either inside a single thundercloud, between two different clouds, or between a cloud and the ground.
+ Many other observational variants are recognized, including "heat lightning", which can be seen from a great distance but not heard; dry lightning, which can cause forest fires; and ball lightning, which is rarely observed scientifically.
+ """
+ ),
+ .image("lightning2")
+ ])
+ case .tornado:
+ return .init(items: [
+ .text(
+ """
+ A tornado is a violently rotating column of air that is in contact with both the surface of the Earth and a cumulonimbus cloud or, in rare cases, the base of a cumulus cloud.
+ The windstorm is often referred to as a twister, whirlwind or cyclone, although the word cyclone is used in meteorology to name a weather system with a low-pressure area in the center around which, from an observer looking down toward the surface of the earth, winds blow counterclockwise in the Northern Hemisphere and clockwise in the Southern.
+ """
+ ),
+ .image("tornado1"),
+ .text(
+ """
+ Various types of tornadoes include the multiple vortex tornado, landspout, and waterspout. Waterspouts are characterized by a spiraling funnel-shaped wind current, connecting to a large cumulus or cumulonimbus cloud. They are generally classified as non-supercellular tornadoes that develop over bodies of water, but there is disagreement over whether to classify them as true tornadoes.
+ """
+ ),
+ .image("tornado2")
+ ])
+ case .moon:
+ return .init(items: [
+ .text(
+ """
+ The Moon is Earth's only proper natural satellite. At one-quarter the diameter of Earth (comparable to the width of Australia), it is the largest natural satellite in the Solar System relative to the size of its planet, and the fifth largest satellite in the Solar System overall (larger than any dwarf planet).
+ """
+ ),
+ .image("moon1"),
+ .text(
+ """
+ The Moon's orbit around Earth has a sidereal period of 27.3 days, and a synodic period of 29.5 days. The synodic period drives its lunar phases, which form the basis for the months of a lunar calendar. The Moon is tidally locked to Earth, which means that the length of a full rotation of the Moon on its own axis (a lunar day) is the same as the synodic period, resulting in its same side (the near side) always facing Earth. That said, 59% of the total lunar surface can be seen from Earth through shifts in perspective (its libration).
+ """
+ ),
+ .image("moon2")
+ ])
+ case .snow:
+ return .init(items: [
+ .text(
+ """
+ Snow comprises individual ice crystals that grow while suspended in the atmosphere—usually within clouds—and then fall, accumulating on the ground where they undergo further changes.[2] It consists of frozen crystalline water throughout its life cycle, starting when, under suitable conditions, the ice crystals form in the atmosphere, increase to millimeter size, precipitate and accumulate on surfaces, then metamorphose in place, and ultimately melt, slide or sublimate away.
+ """
+ ),
+ .image("snow1"),
+ .text(
+ """
+ Major snow-prone areas include the polar regions, the northernmost half of the Northern Hemisphere and mountainous regions worldwide with sufficient moisture and cold temperatures. In the Southern Hemisphere, snow is confined primarily to mountainous areas, apart from Antarctica.
+ """
+ ),
+ .image("snow2")
+ ])
+ }
+ }
+}
diff --git a/Samples/PagingLayoutSamples/Modules/SwiftUI/WeatherTabView/WeatherTabView.Overlay.swift b/Samples/PagingLayoutSamples/Modules/SwiftUI/WeatherTabView/WeatherTabView.Overlay.swift
new file mode 100644
index 0000000..124c067
--- /dev/null
+++ b/Samples/PagingLayoutSamples/Modules/SwiftUI/WeatherTabView/WeatherTabView.Overlay.swift
@@ -0,0 +1,44 @@
+//
+// WeatherTabView.Overlay.swift
+// PagingLayoutSamples
+//
+// Created by Amir Khorsandi on 14/04/2021.
+// Copyright © 2021 Amir Khorsandi. All rights reserved.
+//
+
+import SwiftUI
+
+extension WeatherTabView {
+ struct Overlay: View {
+ var body: some View {
+ VStack {
+ LinearGradient(
+ gradient: Gradient(
+ colors: [
+ Color("Background"),
+ Color("Background").opacity(0)
+ ]
+ ),
+ startPoint: .top,
+ endPoint: .bottom
+ )
+ .frame(maxWidth: .infinity, maxHeight: 30)
+ .padding(.top, 50)
+
+ Spacer()
+
+ LinearGradient(
+ gradient: Gradient(
+ colors: [
+ Color("Background").opacity(0),
+ Color("Background")
+ ]
+ ),
+ startPoint: .top,
+ endPoint: .bottom
+ )
+ .frame(maxWidth: .infinity, maxHeight: 40)
+ }
+ }
+ }
+}
diff --git a/Samples/PagingLayoutSamples/Modules/SwiftUI/WeatherTabView/WeatherTabView.PageView.swift b/Samples/PagingLayoutSamples/Modules/SwiftUI/WeatherTabView/WeatherTabView.PageView.swift
new file mode 100644
index 0000000..f7b5f22
--- /dev/null
+++ b/Samples/PagingLayoutSamples/Modules/SwiftUI/WeatherTabView/WeatherTabView.PageView.swift
@@ -0,0 +1,49 @@
+//
+// WeatherTabView.PageView.swift
+// PagingLayoutSamples
+//
+// Created by Amir Khorsandi on 15/04/2021.
+// Copyright © 2021 Amir Khorsandi. All rights reserved.
+//
+
+import SwiftUI
+
+extension WeatherTabView {
+ struct PageView: View {
+ let page: WeatherPage
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 10) {
+ Image("WikipediaLogo")
+ .opacity(0.34)
+ Text(page.name)
+ .font(.system(size: 53, weight: .bold, design: .serif))
+ ForEach(page.content.items) { item in
+ switch item {
+ case .image(let imageName):
+ Image(imageName)
+ .resizable()
+ .scaledToFit()
+ .border(Color.white, width: 11)
+ case .text(let text):
+ Text(text)
+ .font(.system(size: 16, weight: .regular, design: .default))
+ .foregroundColor(.gray)
+ }
+ }
+ }
+ .padding(.bottom, 100)
+ .padding(.top, 20)
+ .padding(.horizontal, 34)
+ }
+ }
+}
+
+extension WeatherPage.Content.Item: Identifiable {
+ var id: String {
+ switch self {
+ case .image(let data), .text(let data):
+ return data.hash.description
+ }
+ }
+}
diff --git a/Samples/PagingLayoutSamples/Modules/SwiftUI/WeatherTabView/WeatherTabView.TabView.swift b/Samples/PagingLayoutSamples/Modules/SwiftUI/WeatherTabView/WeatherTabView.TabView.swift
new file mode 100644
index 0000000..849da3a
--- /dev/null
+++ b/Samples/PagingLayoutSamples/Modules/SwiftUI/WeatherTabView/WeatherTabView.TabView.swift
@@ -0,0 +1,84 @@
+//
+// WeatherTabView.TabView.swift
+// PagingLayoutSamples
+//
+// Created by Amir Khorsandi on 14/04/2021.
+// Copyright © 2021 Amir Khorsandi. All rights reserved.
+//
+
+import SwiftUI
+
+extension WeatherTabView {
+ struct TabView: View {
+ @Binding var selection: WeatherPage.ID?
+
+ var body: some View {
+ VStack {
+ Spacer()
+ ZStack(alignment: .center) {
+ VisualEffectView(style: .systemUltraThinMaterialDark)
+ buttons
+ }
+ .cornerRadius(21)
+ .frame(height: 72)
+ .padding(.horizontal, 23)
+ .padding(.bottom, 20)
+ .animation(.easeInOut)
+ }
+ }
+
+ private var buttons: some View {
+ HStack(spacing: 0) {
+ Spacer(minLength: 0)
+ .frame(maxWidth: isSelected(WeatherPage.allCases.first) ? 12 : .infinity)
+
+ ForEach(WeatherPage.allCases) { page in
+ Button {
+ selection = page.id
+ } label: {
+ HStack {
+ Spacer(minLength: isSelected(page) ? 12 : 0)
+ Image(systemName: page.imageName)
+ .font(.system(size: 25))
+ if isSelected(page) {
+ Text(page.name)
+ .font(.system(size: 18, weight: .light))
+ .lineLimit(1)
+ .fixedSize()
+ }
+ Spacer(minLength: isSelected(page) ? 12 : 0)
+ }
+ .padding(.vertical, 9)
+ .background(
+ isSelected(page) ? Color.black.opacity(0.15) : Color.clear
+ )
+ .cornerRadius(17)
+ }
+ Spacer(minLength: 0)
+ .frame(maxWidth: page == WeatherPage.allCases.last && isSelected(WeatherPage.allCases.last) ? 12 : .infinity)
+ }
+ }
+ .foregroundColor(.white)
+ }
+
+ private func isSelected(_ page: WeatherPage?) -> Bool {
+ page?.id == selection ?? WeatherPage.allCases.first?.id
+ }
+
+ }
+}
+
+struct WeatherTabView_TabView_Previews: PreviewProvider {
+ static var previews: some View {
+ Group {
+ WeatherTabView.TabView(selection: .constant(WeatherPage.sun.id))
+ .previewLayout(.fixed(width: 450, height: 300))
+
+ WeatherTabView.TabView(selection: .constant(WeatherPage.tornado.id))
+ .previewLayout(.fixed(width: 375, height: 300))
+
+ WeatherTabView.TabView(selection: .constant(WeatherPage.lightning.id))
+ .previewLayout(.fixed(width: 320, height: 300))
+ }
+ }
+}
diff --git a/Samples/PagingLayoutSamples/Modules/SwiftUI/WeatherTabView/WeatherTabView.swift b/Samples/PagingLayoutSamples/Modules/SwiftUI/WeatherTabView/WeatherTabView.swift
new file mode 100644
index 0000000..f77bf72
--- /dev/null
+++ b/Samples/PagingLayoutSamples/Modules/SwiftUI/WeatherTabView/WeatherTabView.swift
@@ -0,0 +1,42 @@
+//
+// WeatherTabView.swift
+// PagingLayoutSamples
+//
+// Created by Amir Khorsandi on 13/04/2021.
+// Copyright © 2021 Amir Khorsandi. All rights reserved.
+//
+
+import SwiftUI
+import CollectionViewPagingLayout
+
+struct WeatherTabView: View {
+
+ @State private var currentPage: WeatherPage.ID?
+
+ var body: some View {
+ ZStack {
+ Color("Background").ignoresSafeArea()
+
+ SnapshotPageView(WeatherPage.allCases, selection: $currentPage) { page in
+ ScrollView(showsIndicators: false) {
+ PageView(page: page)
+ }
+ }
+ .animator(DefaultViewAnimator(0.7, curve: .parametric))
+ .options(options)
+ .padding(.top, 50)
+ .overlay(Overlay())
+
+ TabView(selection: $currentPage)
+ }
+ }
+
+ private var options = SnapshotTransformViewOptions(
+ pieceSizeRatio: .init(width: 0.2, height: 1),
+ piecesAlphaRatio: .static(0),
+ piecesTranslationRatio: .columnOddEven(CGPoint(x: 0, y: 0.1), CGPoint(x: 0, y: -0.1)),
+ piecesScaleRatio: .columnOddEven(.init(width: 1, height: 0), .init(width: 0, height: 0)),
+ containerScaleRatio: 0,
+ containerTranslationRatio: .init(x: 1, y: 0)
+ )
+}
diff --git a/Samples/PagingLayoutSamples/Modules/Cards/CardsViewController.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Cards/CardsViewController.swift
similarity index 91%
rename from Samples/PagingLayoutSamples/Modules/Cards/CardsViewController.swift
rename to Samples/PagingLayoutSamples/Modules/UIKit/Cards/CardsViewController.swift
index 1ef0eef..a5ca682 100644
--- a/Samples/PagingLayoutSamples/Modules/Cards/CardsViewController.swift
+++ b/Samples/PagingLayoutSamples/Modules/UIKit/Cards/CardsViewController.swift
@@ -44,10 +44,12 @@ class CardsViewController: UIViewController, NibBased, ViewModelBased {
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
- layout.invalidateLayout()
+ layout.invalidateLayoutInBatchUpdate()
if !didScrollCollectionViewToMiddle {
- layout.setCurrentPage(Constants.infiniteNumberOfItems / 2, animated: false)
didScrollCollectionViewToMiddle = true
+ collectionView?.performBatchUpdates({ [weak self] in
+ self?.layout.setCurrentPage(Constants.infiniteNumberOfItems / 2, animated: false)
+ })
}
}
@@ -81,6 +83,7 @@ class CardsViewController: UIViewController, NibBased, ViewModelBased {
collectionView.dataSource = self
layout.numberOfVisibleItems = 7
layout.scrollDirection = .vertical
+ layout.transparentAttributeWhenCellNotLoaded = true
collectionView.collectionViewLayout = layout
collectionView.showsVerticalScrollIndicator = false
collectionView.clipsToBounds = false
diff --git a/Samples/PagingLayoutSamples/Modules/Cards/CardsViewController.xib b/Samples/PagingLayoutSamples/Modules/UIKit/Cards/CardsViewController.xib
similarity index 100%
rename from Samples/PagingLayoutSamples/Modules/Cards/CardsViewController.xib
rename to Samples/PagingLayoutSamples/Modules/UIKit/Cards/CardsViewController.xib
diff --git a/Samples/PagingLayoutSamples/Modules/Cards/CardsViewModel.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Cards/CardsViewModel.swift
similarity index 100%
rename from Samples/PagingLayoutSamples/Modules/Cards/CardsViewModel.swift
rename to Samples/PagingLayoutSamples/Modules/UIKit/Cards/CardsViewModel.swift
diff --git a/Samples/PagingLayoutSamples/Modules/Cards/Cell/CardCellViewModel.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Cards/Cell/CardCellViewModel.swift
similarity index 100%
rename from Samples/PagingLayoutSamples/Modules/Cards/Cell/CardCellViewModel.swift
rename to Samples/PagingLayoutSamples/Modules/UIKit/Cards/Cell/CardCellViewModel.swift
diff --git a/Samples/PagingLayoutSamples/Modules/Cards/Cell/CardCollectionViewCell.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Cards/Cell/CardCollectionViewCell.swift
similarity index 100%
rename from Samples/PagingLayoutSamples/Modules/Cards/Cell/CardCollectionViewCell.swift
rename to Samples/PagingLayoutSamples/Modules/UIKit/Cards/Cell/CardCollectionViewCell.swift
diff --git a/Samples/PagingLayoutSamples/Modules/Cards/Cell/CardCollectionViewCell.xib b/Samples/PagingLayoutSamples/Modules/UIKit/Cards/Cell/CardCollectionViewCell.xib
similarity index 100%
rename from Samples/PagingLayoutSamples/Modules/Cards/Cell/CardCollectionViewCell.xib
rename to Samples/PagingLayoutSamples/Modules/UIKit/Cards/Cell/CardCollectionViewCell.xib
diff --git a/Samples/PagingLayoutSamples/Modules/Fruits/Cell/FruitCellViewModel.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Fruits/Cell/FruitCellViewModel.swift
similarity index 100%
rename from Samples/PagingLayoutSamples/Modules/Fruits/Cell/FruitCellViewModel.swift
rename to Samples/PagingLayoutSamples/Modules/UIKit/Fruits/Cell/FruitCellViewModel.swift
diff --git a/Samples/PagingLayoutSamples/Modules/Fruits/Cell/FruitsCollectionViewCell.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Fruits/Cell/FruitsCollectionViewCell.swift
similarity index 100%
rename from Samples/PagingLayoutSamples/Modules/Fruits/Cell/FruitsCollectionViewCell.swift
rename to Samples/PagingLayoutSamples/Modules/UIKit/Fruits/Cell/FruitsCollectionViewCell.swift
diff --git a/Samples/PagingLayoutSamples/Modules/Fruits/Cell/FruitsCollectionViewCell.xib b/Samples/PagingLayoutSamples/Modules/UIKit/Fruits/Cell/FruitsCollectionViewCell.xib
similarity index 100%
rename from Samples/PagingLayoutSamples/Modules/Fruits/Cell/FruitsCollectionViewCell.xib
rename to Samples/PagingLayoutSamples/Modules/UIKit/Fruits/Cell/FruitsCollectionViewCell.xib
diff --git a/Samples/PagingLayoutSamples/Modules/Fruits/FruitsViewController.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Fruits/FruitsViewController.swift
similarity index 93%
rename from Samples/PagingLayoutSamples/Modules/Fruits/FruitsViewController.swift
rename to Samples/PagingLayoutSamples/Modules/UIKit/Fruits/FruitsViewController.swift
index 11cadff..e5b0ef9 100644
--- a/Samples/PagingLayoutSamples/Modules/Fruits/FruitsViewController.swift
+++ b/Samples/PagingLayoutSamples/Modules/UIKit/Fruits/FruitsViewController.swift
@@ -27,11 +27,6 @@ class FruitsViewController: UIViewController, NibBased, ViewModelBased {
configureViews()
}
- override func viewDidLayoutSubviews() {
- super.viewDidLayoutSubviews()
- collectionView.collectionViewLayout.invalidateLayout()
- }
-
// MARK: Event listener
@IBAction private func onBackTouched() {
diff --git a/Samples/PagingLayoutSamples/Modules/Fruits/FruitsViewController.xib b/Samples/PagingLayoutSamples/Modules/UIKit/Fruits/FruitsViewController.xib
similarity index 100%
rename from Samples/PagingLayoutSamples/Modules/Fruits/FruitsViewController.xib
rename to Samples/PagingLayoutSamples/Modules/UIKit/Fruits/FruitsViewController.xib
diff --git a/Samples/PagingLayoutSamples/Modules/Fruits/FruitsViewModel.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Fruits/FruitsViewModel.swift
similarity index 100%
rename from Samples/PagingLayoutSamples/Modules/Fruits/FruitsViewModel.swift
rename to Samples/PagingLayoutSamples/Modules/UIKit/Fruits/FruitsViewModel.swift
diff --git a/Samples/PagingLayoutSamples/Modules/Gallery/Cell/PhotoCellViewModel.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Gallery/Cell/PhotoCellViewModel.swift
similarity index 100%
rename from Samples/PagingLayoutSamples/Modules/Gallery/Cell/PhotoCellViewModel.swift
rename to Samples/PagingLayoutSamples/Modules/UIKit/Gallery/Cell/PhotoCellViewModel.swift
diff --git a/Samples/PagingLayoutSamples/Modules/Gallery/Cell/PhotoCollectionViewCell.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Gallery/Cell/PhotoCollectionViewCell.swift
similarity index 100%
rename from Samples/PagingLayoutSamples/Modules/Gallery/Cell/PhotoCollectionViewCell.swift
rename to Samples/PagingLayoutSamples/Modules/UIKit/Gallery/Cell/PhotoCollectionViewCell.swift
diff --git a/Samples/PagingLayoutSamples/Modules/Gallery/Cell/PhotoCollectionViewCell.xib b/Samples/PagingLayoutSamples/Modules/UIKit/Gallery/Cell/PhotoCollectionViewCell.xib
similarity index 100%
rename from Samples/PagingLayoutSamples/Modules/Gallery/Cell/PhotoCollectionViewCell.xib
rename to Samples/PagingLayoutSamples/Modules/UIKit/Gallery/Cell/PhotoCollectionViewCell.xib
diff --git a/Samples/PagingLayoutSamples/Modules/Gallery/GalleryViewController.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Gallery/GalleryViewController.swift
similarity index 93%
rename from Samples/PagingLayoutSamples/Modules/Gallery/GalleryViewController.swift
rename to Samples/PagingLayoutSamples/Modules/UIKit/Gallery/GalleryViewController.swift
index 0106769..5544c23 100644
--- a/Samples/PagingLayoutSamples/Modules/Gallery/GalleryViewController.swift
+++ b/Samples/PagingLayoutSamples/Modules/UIKit/Gallery/GalleryViewController.swift
@@ -33,11 +33,6 @@ class GalleryViewController: UIViewController, NibBased, ViewModelBased {
configureViews()
}
- override func viewDidLayoutSubviews() {
- super.viewDidLayoutSubviews()
- collectionView.collectionViewLayout.invalidateLayout()
- }
-
// MARK: Event listener
diff --git a/Samples/PagingLayoutSamples/Modules/Gallery/GalleryViewController.xib b/Samples/PagingLayoutSamples/Modules/UIKit/Gallery/GalleryViewController.xib
similarity index 100%
rename from Samples/PagingLayoutSamples/Modules/Gallery/GalleryViewController.xib
rename to Samples/PagingLayoutSamples/Modules/UIKit/Gallery/GalleryViewController.xib
diff --git a/Samples/PagingLayoutSamples/Modules/Gallery/GalleryViewModel.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Gallery/GalleryViewModel.swift
similarity index 100%
rename from Samples/PagingLayoutSamples/Modules/Gallery/GalleryViewModel.swift
rename to Samples/PagingLayoutSamples/Modules/UIKit/Gallery/GalleryViewModel.swift
diff --git a/Samples/PagingLayoutSamples/Modules/Shapes/Card/ShapeCardView.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/Card/ShapeCardView.swift
similarity index 100%
rename from Samples/PagingLayoutSamples/Modules/Shapes/Card/ShapeCardView.swift
rename to Samples/PagingLayoutSamples/Modules/UIKit/Shapes/Card/ShapeCardView.swift
diff --git a/Samples/PagingLayoutSamples/Modules/Shapes/Card/ShapeCardView.xib b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/Card/ShapeCardView.xib
similarity index 100%
rename from Samples/PagingLayoutSamples/Modules/Shapes/Card/ShapeCardView.xib
rename to Samples/PagingLayoutSamples/Modules/UIKit/Shapes/Card/ShapeCardView.xib
diff --git a/Samples/PagingLayoutSamples/Modules/Shapes/Card/ShapeCardViewModel.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/Card/ShapeCardViewModel.swift
similarity index 100%
rename from Samples/PagingLayoutSamples/Modules/Shapes/Card/ShapeCardViewModel.swift
rename to Samples/PagingLayoutSamples/Modules/UIKit/Shapes/Card/ShapeCardViewModel.swift
diff --git a/Samples/PagingLayoutSamples/Modules/Shapes/LayoutTypeCell/LayoutTypeCellViewModel.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/LayoutTypeCell/LayoutTypeCellViewModel.swift
similarity index 100%
rename from Samples/PagingLayoutSamples/Modules/Shapes/LayoutTypeCell/LayoutTypeCellViewModel.swift
rename to Samples/PagingLayoutSamples/Modules/UIKit/Shapes/LayoutTypeCell/LayoutTypeCellViewModel.swift
diff --git a/Samples/PagingLayoutSamples/Modules/Shapes/LayoutTypeCell/LayoutTypeCollectionViewCell.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/LayoutTypeCell/LayoutTypeCollectionViewCell.swift
similarity index 100%
rename from Samples/PagingLayoutSamples/Modules/Shapes/LayoutTypeCell/LayoutTypeCollectionViewCell.swift
rename to Samples/PagingLayoutSamples/Modules/UIKit/Shapes/LayoutTypeCell/LayoutTypeCollectionViewCell.swift
diff --git a/Samples/PagingLayoutSamples/Modules/Shapes/LayoutTypeCell/LayoutTypeCollectionViewCell.xib b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/LayoutTypeCell/LayoutTypeCollectionViewCell.xib
similarity index 100%
rename from Samples/PagingLayoutSamples/Modules/Shapes/LayoutTypeCell/LayoutTypeCollectionViewCell.xib
rename to Samples/PagingLayoutSamples/Modules/UIKit/Shapes/LayoutTypeCell/LayoutTypeCollectionViewCell.xib
diff --git a/Samples/PagingLayoutSamples/Modules/Shapes/ShapeCell/BaseShapeCollectionViewCell.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapeCell/BaseShapeCollectionViewCell.swift
similarity index 100%
rename from Samples/PagingLayoutSamples/Modules/Shapes/ShapeCell/BaseShapeCollectionViewCell.swift
rename to Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapeCell/BaseShapeCollectionViewCell.swift
diff --git a/Samples/PagingLayoutSamples/Modules/Shapes/ShapeCell/ShapeCollectionViewCells.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapeCell/ShapeCollectionViewCells.swift
similarity index 100%
rename from Samples/PagingLayoutSamples/Modules/Shapes/ShapeCell/ShapeCollectionViewCells.swift
rename to Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapeCell/ShapeCollectionViewCells.swift
diff --git a/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapeLayout+ScaleOptions.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapeLayout+ScaleOptions.swift
new file mode 100644
index 0000000..ff17a37
--- /dev/null
+++ b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapeLayout+ScaleOptions.swift
@@ -0,0 +1,35 @@
+//
+// ShapeLayout+ScaleOptions.swift
+// PagingLayoutSamples
+//
+// Created by Amir on 27/06/2020.
+// Copyright © 2020 Amir Khorsandi. All rights reserved.
+//
+
+import Foundation
+import CollectionViewPagingLayout
+
+extension ShapeLayout {
+ var scaleOptions: ScaleTransformViewOptions? {
+ switch self {
+ case .scaleBlur:
+ return .layout(.blur)
+ case .scaleLinear:
+ return .layout(.linear)
+ case .scaleEaseIn:
+ return .layout(.easeIn)
+ case .scaleEaseOut:
+ return .layout(.easeOut)
+ case .scaleRotary:
+ return .layout(.rotary)
+ case .scaleCylinder:
+ return .layout(.cylinder)
+ case .scaleInvertedCylinder:
+ return .layout(.invertedCylinder)
+ case .scaleCoverFlow:
+ return .layout(.coverFlow)
+ default:
+ return nil
+ }
+ }
+}
diff --git a/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapeLayout+SnapshotOptions.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapeLayout+SnapshotOptions.swift
new file mode 100644
index 0000000..23a7e8a
--- /dev/null
+++ b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapeLayout+SnapshotOptions.swift
@@ -0,0 +1,36 @@
+//
+// ShapeLayout+SnapshotOptions.swift
+// PagingLayoutSamples
+//
+// Created by Amir on 27/06/2020.
+// Copyright © 2020 Amir Khorsandi. All rights reserved.
+//
+
+import Foundation
+import CollectionViewPagingLayout
+
+extension ShapeLayout {
+ var snapshotOptions: SnapshotTransformViewOptions? {
+ switch self {
+ case .snapshotGrid:
+ return .layout(.grid)
+ case .snapshotSpace:
+ return .layout(.space)
+ case .snapshotChess:
+ return .layout(.chess)
+ case .snapshotTiles:
+ return .layout(.tiles)
+ case .snapshotLines:
+ return .layout(.lines)
+ case .snapshotBars:
+ return .layout(.bars)
+ case .snapshotPuzzle:
+ return .layout(.puzzle)
+ case .snapshotFade:
+ return .layout(.fade)
+ default:
+ return nil
+ }
+
+ }
+}
diff --git a/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapeLayout+StackOptions.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapeLayout+StackOptions.swift
new file mode 100644
index 0000000..9fb5cec
--- /dev/null
+++ b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapeLayout+StackOptions.swift
@@ -0,0 +1,31 @@
+//
+// ShapeLayout+StackOptions.swift
+// PagingLayoutSamples
+//
+// Created by Amir on 27/06/2020.
+// Copyright © 2020 Amir Khorsandi. All rights reserved.
+//
+
+import Foundation
+import CollectionViewPagingLayout
+
+extension ShapeLayout {
+ var stackOptions: StackTransformViewOptions? {
+ switch self {
+ case .stackTransparent:
+ return .layout(.transparent)
+ case .stackPerspective:
+ return .layout(.perspective)
+ case .stackRotary:
+ return .layout(.rotary)
+ case .stackVortex:
+ return .layout(.vortex)
+ case .stackReverse:
+ return .layout(.reverse)
+ case .stackBlur:
+ return .layout(.blur)
+ default:
+ return nil
+ }
+ }
+}
diff --git a/Samples/PagingLayoutSamples/Modules/Shapes/ShapesViewController.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapesViewController.swift
similarity index 89%
rename from Samples/PagingLayoutSamples/Modules/Shapes/ShapesViewController.swift
rename to Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapesViewController.swift
index c68511a..8a07842 100644
--- a/Samples/PagingLayoutSamples/Modules/Shapes/ShapesViewController.swift
+++ b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapesViewController.swift
@@ -20,7 +20,7 @@ class ShapesViewController: UIViewController, NibBased, ViewModelBased {
private struct Constants {
- static let infiniteNumberOfItems = 100_000
+ static let infiniteNumberOfItems = 10_000
}
@@ -54,15 +54,17 @@ class ShapesViewController: UIViewController, NibBased, ViewModelBased {
super.viewDidLayoutSubviews()
if !didScrollCollectionViewToMiddle {
- getPagingLayout(layoutTypeCollectionView)?.setCurrentPage(
- Constants.infiniteNumberOfItems / 2,
- animated: false
- )
didScrollCollectionViewToMiddle = true
+ collectionView?.performBatchUpdates({ [weak self] in
+ self?.getPagingLayout(layoutTypeCollectionView)?.setCurrentPage(
+ Constants.infiniteNumberOfItems / 2,
+ animated: false
+ )
+ })
}
updateSelectedLayout()
- invalidateLayouts()
+ getPagingLayout(layoutTypeCollectionView)?.invalidateLayoutInBatchUpdate()
}
@@ -111,13 +113,12 @@ class ShapesViewController: UIViewController, NibBased, ViewModelBased {
collectionView.isPagingEnabled = true
collectionView.dataSource = self
let layout = CollectionViewPagingLayout()
- layout.numberOfVisibleItems = 10
collectionView.collectionViewLayout = layout
- layout.configureTapOnCollectionView(goToSelectedPage: true)
layout.delegate = self
collectionView.showsHorizontalScrollIndicator = false
collectionView.clipsToBounds = false
collectionView.backgroundColor = .clear
+ collectionView.delegate = self
}
private func configureLayoutTypeCollectionView() {
@@ -127,7 +128,6 @@ class ShapesViewController: UIViewController, NibBased, ViewModelBased {
let layout = CollectionViewPagingLayout()
layout.numberOfVisibleItems = 10
layoutTypeCollectionView.collectionViewLayout = layout
- layout.configureTapOnCollectionView(goToSelectedPage: true)
layoutTypeCollectionView.showsHorizontalScrollIndicator = false
layoutTypeCollectionView.clipsToBounds = false
layoutTypeCollectionView.backgroundColor = .clear
@@ -135,9 +135,7 @@ class ShapesViewController: UIViewController, NibBased, ViewModelBased {
}
private func updateSelectedLayout() {
- guard let layout = layoutTypeCollectionView?.collectionViewLayout as? CollectionViewPagingLayout else {
- return
- }
+ guard let layout = getPagingLayout(layoutTypeCollectionView) else { return }
let index = layout.currentPage % viewModel.layoutTypeViewModels.count
self.viewModel.selectedLayout = self.viewModel.layoutTypeViewModels[index]
delegate?.shapesViewController(self, onSelectedLayoutChange: viewModel.selectedLayout.layout)
@@ -167,8 +165,8 @@ class ShapesViewController: UIViewController, NibBased, ViewModelBased {
})
}
- private func getPagingLayout(_ collectionView: UICollectionView) -> CollectionViewPagingLayout? {
- collectionView.collectionViewLayout as? CollectionViewPagingLayout
+ private func getPagingLayout(_ collectionView: UICollectionView?) -> CollectionViewPagingLayout? {
+ collectionView?.collectionViewLayout as? CollectionViewPagingLayout
}
}
@@ -242,6 +240,14 @@ extension ShapesViewController: UICollectionViewDelegate {
}
updateSelectedLayout()
}
+
+ func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+ (collectionView.collectionViewLayout as? CollectionViewPagingLayout)?.setCurrentPage(indexPath.row) { [weak self] in
+ DispatchQueue.main.async {
+ self?.updateSelectedLayout()
+ }
+ }
+ }
}
diff --git a/Samples/PagingLayoutSamples/Modules/Shapes/ShapesViewController.xib b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapesViewController.xib
similarity index 100%
rename from Samples/PagingLayoutSamples/Modules/Shapes/ShapesViewController.xib
rename to Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapesViewController.xib
diff --git a/Samples/PagingLayoutSamples/Modules/Shapes/ShapesViewModel.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapesViewModel.swift
similarity index 100%
rename from Samples/PagingLayoutSamples/Modules/Shapes/ShapesViewModel.swift
rename to Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapesViewModel.swift
diff --git a/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout.xcodeproj_sample/project.pbxproj_sample b/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout.xcodeproj_sample/project.pbxproj_sample
index 088a211..3aad101 100644
--- a/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout.xcodeproj_sample/project.pbxproj_sample
+++ b/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout.xcodeproj_sample/project.pbxproj_sample
@@ -339,7 +339,7 @@
repositoryURL = "https://github.com/amirdew/CollectionViewPagingLayout";
requirement = {
kind = upToNextMajorVersion;
- minimumVersion = 0.3.0;
+ minimumVersion = 1.0.0;
};
};
/* End XCRemoteSwiftPackageReference section */
diff --git a/Samples/PagingLayoutSamples/Utilities/VisualEffectView.swift b/Samples/PagingLayoutSamples/Utilities/VisualEffectView.swift
new file mode 100644
index 0000000..f0cfa54
--- /dev/null
+++ b/Samples/PagingLayoutSamples/Utilities/VisualEffectView.swift
@@ -0,0 +1,22 @@
+//
+// VisualEffectView.swift
+// PagingLayoutSamples
+//
+// Created by Amir Khorsandi on 14/04/2021.
+// Copyright © 2021 Amir Khorsandi. All rights reserved.
+//
+
+import SwiftUI
+import UIKit
+
+struct VisualEffectView: UIViewRepresentable {
+ var style: UIBlurEffect.Style = .systemUltraThinMaterial
+
+ func makeUIView(context: Context) -> UIVisualEffectView {
+ let view = UIVisualEffectView(effect: UIBlurEffect(style: style))
+ return view
+ }
+ func updateUIView(_ uiView: UIVisualEffectView, context: Context) {
+ uiView.effect = UIBlurEffect(style: style)
+ }
+}
diff --git a/Samples/Podfile b/Samples/Podfile
index bb8f143..164b4d3 100644
--- a/Samples/Podfile
+++ b/Samples/Podfile
@@ -1,4 +1,4 @@
-platform :ios, '13.0'
+platform :ios, '14.0'
target 'PagingLayoutSamples' do
use_frameworks!
diff --git a/Samples/Podfile.lock b/Samples/Podfile.lock
index 58b4d74..6bb5023 100644
--- a/Samples/Podfile.lock
+++ b/Samples/Podfile.lock
@@ -1,7 +1,7 @@
PODS:
- - CollectionViewPagingLayout (0.3.0)
+ - CollectionViewPagingLayout (1.1.0)
- Splash (0.13.0)
- - SwiftLint (0.39.2)
+ - SwiftLint (0.47.0)
DEPENDENCIES:
- CollectionViewPagingLayout (from `./../`)
@@ -20,14 +20,14 @@ EXTERNAL SOURCES:
CHECKOUT OPTIONS:
Splash:
- :commit: 3ed14d500c85663d35136855e7ce4ef064288db2
+ :commit: 919ad99dfdc16b82665c44c3b6494f4e5f22bcec
:git: https://github.com/amirdew/Splash.git
SPEC CHECKSUMS:
- CollectionViewPagingLayout: 7c2d6f975d467356e2ddfd72aef8534f1ebd946d
+ CollectionViewPagingLayout: 2f8d50683bae57ca88f47fa4ab0195c9079582cf
Splash: 5ec9a07c4bbf047ddd659bfc11d35331d31004f7
- SwiftLint: 22ccbbe3b8008684be5955693bab135e0ed6a447
+ SwiftLint: d41cc46a2ae58ac6d9f26954bc89f1d72e71fdef
-PODFILE CHECKSUM: ba360cc7c61c7efa5897648fc2987642be2e493e
+PODFILE CHECKSUM: a829b1accd80b68e4033b32927c85146318f7597
-COCOAPODS: 1.9.3
+COCOAPODS: 1.11.3