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/.gitignore b/.gitignore index d33fd61..d49a9bc 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ build/ DerivedData/ +## SPM +.swiftpm + ## Various settings *.pbxuser !default.pbxuser diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000..9221a3b --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,67 @@ +disabled_rules: + - file_length + - type_body_length + - function_body_length + - trailing_whitespace + - empty_enum_arguments + - function_parameter_count + - identifier_name + - type_name + - nesting + - large_tuple + +opt_in_rules: + - private_action + - private_outlet + - prefixed_toplevel_constant + - overridden_super_call + - file_header + - weak_delegate + - collection_alignment + - empty_count + - empty_string + - fatal_error_message + - function_default_parameter_at_end + - last_where + - legacy_random + - multiline_function_chains + - multiline_literal_brackets + - multiline_parameters + - override_in_extension + - sorted_first_last + - toggle_bool + - unused_optional_binding + - vertical_parameter_alignment_on_call + - yoda_condition + +excluded: + - Package.swift + - Pods + - PagingLayoutSamples/SampleProject.bundle/SampleProject + - Samples/Pods + - Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject + - CollectionViewPagingLayoutTests + +# adjusting rules + +vertical_whitespace: + max_empty_lines: 2 + +line_length: + warning: 180 + error: 200 + +file_header: + required_pattern: | + \/\/ + \/\/ .*?\.swift + \/\/ .* + \/\/ + \/\/ (Created by .*? on .*?) + \/\/ Copyright © \d{4} Amir Khorsandi. All rights reserved\. + \/\/ + +reporter: "xcode" + +cyclomatic_complexity: + ignores_case_statements: true diff --git a/.swiftlint_autocorrect.yml b/.swiftlint_autocorrect.yml new file mode 100644 index 0000000..612c563 --- /dev/null +++ b/.swiftlint_autocorrect.yml @@ -0,0 +1,32 @@ +whitelist_rules: + - let_var_whitespace + - return_arrow_whitespace + - opening_brace + - colon + - mark + - trailing_newline + - statement_position + - trailing_semicolon + - control_statement + - comma + - vertical_whitespace + - closure_end_indentation + - closure_spacing + - empty_parameters + - joined_default_parameter + - operator_usage_whitespace + - unneeded_parentheses_in_closure_argument + - redundant_optional_initialization + - implicit_return + - literal_expression_end_indentation + - nimble_operator + - number_separator + - unused_import + +excluded: +- Pods + +# adjusting rules + +vertical_whitespace: + max_empty_lines: 2 diff --git a/CollectionViewPagingLayout.podspec b/CollectionViewPagingLayout.podspec index 77e043c..f081bb6 100644 --- a/CollectionViewPagingLayout.podspec +++ b/CollectionViewPagingLayout.podspec @@ -1,21 +1,23 @@ - Pod::Spec.new do |s| - s.name = "CollectionViewPagingLayout" - s.version = "0.0.5" - s.summary = "Simple layout for making pagings with UICollectionView." - - s.description = <<-DESC - a custom UICollectionViewLayout for making a paging effect 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 = "CollectionViewPagingLayout/Lib" + 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 9458169..10010f4 100644 --- a/CollectionViewPagingLayout.xcodeproj/project.pbxproj +++ b/CollectionViewPagingLayout.xcodeproj/project.pbxproj @@ -7,87 +7,108 @@ objects = { /* Begin PBXBuildFile section */ - 2925CDDF23D4D21F00243F5F /* Card.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2925CDDE23D4D21F00243F5F /* Card.swift */; }; - AB1BBA9B23CA5179004E5C3B /* CardCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB1BBA9823CA5178004E5C3B /* CardCellViewModel.swift */; }; - AB1BBA9C23CA5179004E5C3B /* CardCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB1BBA9923CA5178004E5C3B /* CardCollectionViewCell.swift */; }; - AB1BBA9D23CA5179004E5C3B /* CardCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = AB1BBA9A23CA5179004E5C3B /* CardCollectionViewCell.xib */; }; - AB1BBA9E23CA7BD9004E5C3B /* CardsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB1BBA8D23C935D3004E5C3B /* CardsViewController.swift */; }; - AB1BBA9F23CA7BE3004E5C3B /* CardsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = AB1BBA8C23C935D3004E5C3B /* CardsViewController.xib */; }; - AB1BBAA023CA7BE6004E5C3B /* CardsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB1BBA8E23C935D3004E5C3B /* CardsViewModel.swift */; }; - AB1E03AF23B25CE70087F904 /* PageControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB1E03AE23B25CE70087F904 /* PageControlView.swift */; }; - AB500A3323B104E20056BE37 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB500A3223B104E20056BE37 /* AppDelegate.swift */; }; - AB500A3C23B104E60056BE37 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AB500A3B23B104E60056BE37 /* Assets.xcassets */; }; - AB500A3F23B104E60056BE37 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AB500A3D23B104E60056BE37 /* LaunchScreen.storyboard */; }; - AB500A4723B1061D0056BE37 /* CollectionViewPagingLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB500A4623B1061D0056BE37 /* CollectionViewPagingLayout.swift */; }; - AB500A4A23B13BBD0056BE37 /* FruitsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB500A4923B13BBD0056BE37 /* FruitsViewController.swift */; }; - AB500A4C23B13BC90056BE37 /* FruitsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = AB500A4B23B13BC90056BE37 /* FruitsViewController.xib */; }; - AB500A4E23B13C5D0056BE37 /* NibBased.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB500A4D23B13C5C0056BE37 /* NibBased.swift */; }; - AB500A5023B151780056BE37 /* FruitsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB500A4F23B151780056BE37 /* FruitsViewModel.swift */; }; - AB500A5523B152500056BE37 /* ViewModelBased.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB500A5423B152500056BE37 /* ViewModelBased.swift */; }; - AB500A5823B154210056BE37 /* Fruit.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB500A5723B154210056BE37 /* Fruit.swift */; }; - AB500A5B23B154640056BE37 /* FruitsCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB500A5A23B154640056BE37 /* FruitsCollectionViewCell.swift */; }; - AB500A5D23B1547C0056BE37 /* FruitCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB500A5C23B1547C0056BE37 /* FruitCellViewModel.swift */; }; - AB500A5F23B154950056BE37 /* FruitsCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = AB500A5E23B154950056BE37 /* FruitsCollectionViewCell.xib */; }; - AB7C1E0323B42BB8006441DE /* UIView+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB7C1E0223B42BB8006441DE /* UIView+Utilities.swift */; }; - AB7C1E0623B4E2B2006441DE /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB7C1E0523B4E2B2006441DE /* MainViewController.swift */; }; - AB7C1E0823B4E2C0006441DE /* MainViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = AB7C1E0723B4E2C0006441DE /* MainViewController.xib */; }; - AB7C1E0B23B4F224006441DE /* TransformableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB7C1E0A23B4F224006441DE /* TransformableView.swift */; }; - ABA1A72C23B42240006A46A3 /* PriceTagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABA1A72B23B42240006A46A3 /* PriceTagView.swift */; }; - ABA1A72E23B42247006A46A3 /* PriceTagView.xib in Resources */ = {isa = PBXBuildFile; fileRef = ABA1A72D23B42247006A46A3 /* PriceTagView.xib */; }; - ABA1A73123B422B2006A46A3 /* QuantityControllerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABA1A73023B422B2006A46A3 /* QuantityControllerView.swift */; }; - ABA1A73323B422B9006A46A3 /* QuantityControllerView.xib in Resources */ = {isa = PBXBuildFile; fileRef = ABA1A73223B422B9006A46A3 /* QuantityControllerView.xib */; }; - ABC242C323B6822200DBD4D6 /* GalleryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC242C223B6822200DBD4D6 /* GalleryViewController.swift */; }; - ABC242C523B6822A00DBD4D6 /* GalleryViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = ABC242C423B6822A00DBD4D6 /* GalleryViewController.xib */; }; - ABC242C723B6823600DBD4D6 /* GalleryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC242C623B6823600DBD4D6 /* GalleryViewModel.swift */; }; - ABC242CA23B682DD00DBD4D6 /* PhotoCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC242C923B682DD00DBD4D6 /* PhotoCellViewModel.swift */; }; - ABC242CC23B6831400DBD4D6 /* Photo.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC242CB23B6831400DBD4D6 /* Photo.swift */; }; - ABC242CE23B6860700DBD4D6 /* PhotoCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC242CD23B6860700DBD4D6 /* PhotoCollectionViewCell.swift */; }; - ABC242D023B6861000DBD4D6 /* PhotoCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = ABC242CF23B6861000DBD4D6 /* PhotoCollectionViewCell.xib */; }; + 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 */; }; + 298DBC262441C98E00341D8E /* SnapshotTransformView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298DBC052441C98E00341D8E /* SnapshotTransformView.swift */; }; + 298DBC272441C98E00341D8E /* SnapshotTransformViewOptions.PiecePosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298DBC062441C98E00341D8E /* SnapshotTransformViewOptions.PiecePosition.swift */; }; + 298DBC282441C98E00341D8E /* SnapshotContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298DBC072441C98E00341D8E /* SnapshotContainerView.swift */; }; + 298DBC292441C98E00341D8E /* SnapshotTransformViewOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298DBC082441C98E00341D8E /* SnapshotTransformViewOptions.swift */; }; + 298DBC2A2441C98E00341D8E /* TransformCurve.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298DBC092441C98E00341D8E /* TransformCurve.swift */; }; + 298DBC2B2441C98E00341D8E /* ScaleTransformViewOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298DBC0B2441C98E00341D8E /* ScaleTransformViewOptions.swift */; }; + 298DBC2C2441C98E00341D8E /* ScaleTransformView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298DBC0C2441C98E00341D8E /* ScaleTransformView.swift */; }; + 298DBC2D2441C98E00341D8E /* ScaleTransformViewOptions.Rotation3dOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298DBC0D2441C98E00341D8E /* ScaleTransformViewOptions.Rotation3dOptions.swift */; }; + 298DBC2E2441C98E00341D8E /* ScaleTransformViewOptions.Translation3dOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298DBC0E2441C98E00341D8E /* ScaleTransformViewOptions.Translation3dOptions.swift */; }; + 298DBC2F2441C98E00341D8E /* CGFloat+Range.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298DBC102441C98E00341D8E /* CGFloat+Range.swift */; }; + 298DBC302441C98E00341D8E /* Multipliable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298DBC112441C98E00341D8E /* Multipliable.swift */; }; + 298DBC312441C98E00341D8E /* CGFloat+Interpolate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298DBC122441C98E00341D8E /* CGFloat+Interpolate.swift */; }; + 298DBC322441C98E00341D8E /* UIView+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298DBC132441C98E00341D8E /* UIView+Utilities.swift */; }; + 298DBC332441C98E00341D8E /* CollectionViewPagingLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298DBC142441C98E00341D8E /* CollectionViewPagingLayout.swift */; }; + 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 */ - 2925CDDE23D4D21F00243F5F /* Card.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Card.swift; sourceTree = ""; }; - AB1BBA8C23C935D3004E5C3B /* CardsViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CardsViewController.xib; sourceTree = ""; }; - AB1BBA8D23C935D3004E5C3B /* CardsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardsViewController.swift; sourceTree = ""; }; - AB1BBA8E23C935D3004E5C3B /* CardsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardsViewModel.swift; sourceTree = ""; }; - AB1BBA9823CA5178004E5C3B /* CardCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardCellViewModel.swift; sourceTree = ""; }; - AB1BBA9923CA5178004E5C3B /* CardCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardCollectionViewCell.swift; sourceTree = ""; }; - AB1BBA9A23CA5179004E5C3B /* CardCollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CardCollectionViewCell.xib; sourceTree = ""; }; - AB1E03AE23B25CE70087F904 /* PageControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageControlView.swift; sourceTree = ""; }; - AB500A2F23B104E20056BE37 /* CollectionViewPagingLayout.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CollectionViewPagingLayout.app; sourceTree = BUILT_PRODUCTS_DIR; }; - AB500A3223B104E20056BE37 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - AB500A3B23B104E60056BE37 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - AB500A3E23B104E60056BE37 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - AB500A4023B104E60056BE37 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - AB500A4623B1061D0056BE37 /* CollectionViewPagingLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewPagingLayout.swift; sourceTree = ""; }; - AB500A4923B13BBD0056BE37 /* FruitsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FruitsViewController.swift; sourceTree = ""; }; - AB500A4B23B13BC90056BE37 /* FruitsViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FruitsViewController.xib; sourceTree = ""; }; - AB500A4D23B13C5C0056BE37 /* NibBased.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NibBased.swift; sourceTree = ""; }; - AB500A4F23B151780056BE37 /* FruitsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FruitsViewModel.swift; sourceTree = ""; }; - AB500A5423B152500056BE37 /* ViewModelBased.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelBased.swift; sourceTree = ""; }; - AB500A5723B154210056BE37 /* Fruit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fruit.swift; sourceTree = ""; }; - AB500A5A23B154640056BE37 /* FruitsCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FruitsCollectionViewCell.swift; sourceTree = ""; }; - AB500A5C23B1547C0056BE37 /* FruitCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FruitCellViewModel.swift; sourceTree = ""; }; - AB500A5E23B154950056BE37 /* FruitsCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FruitsCollectionViewCell.xib; sourceTree = ""; }; - AB7C1E0223B42BB8006441DE /* UIView+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Utilities.swift"; sourceTree = ""; }; - AB7C1E0523B4E2B2006441DE /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; - AB7C1E0723B4E2C0006441DE /* MainViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainViewController.xib; sourceTree = ""; }; - AB7C1E0A23B4F224006441DE /* TransformableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransformableView.swift; sourceTree = ""; }; - ABA1A72B23B42240006A46A3 /* PriceTagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PriceTagView.swift; sourceTree = ""; }; - ABA1A72D23B42247006A46A3 /* PriceTagView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PriceTagView.xib; sourceTree = ""; }; - ABA1A73023B422B2006A46A3 /* QuantityControllerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuantityControllerView.swift; sourceTree = ""; }; - ABA1A73223B422B9006A46A3 /* QuantityControllerView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = QuantityControllerView.xib; sourceTree = ""; }; - ABC242C223B6822200DBD4D6 /* GalleryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryViewController.swift; sourceTree = ""; }; - ABC242C423B6822A00DBD4D6 /* GalleryViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = GalleryViewController.xib; sourceTree = ""; }; - ABC242C623B6823600DBD4D6 /* GalleryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryViewModel.swift; sourceTree = ""; }; - ABC242C923B682DD00DBD4D6 /* PhotoCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoCellViewModel.swift; sourceTree = ""; }; - ABC242CB23B6831400DBD4D6 /* Photo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Photo.swift; sourceTree = ""; }; - ABC242CD23B6860700DBD4D6 /* PhotoCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoCollectionViewCell.swift; sourceTree = ""; }; - ABC242CF23B6861000DBD4D6 /* PhotoCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PhotoCollectionViewCell.xib; sourceTree = ""; }; + 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 = ""; }; + 298DBC022441C98E00341D8E /* BlurEffectView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlurEffectView.swift; sourceTree = ""; }; + 298DBC042441C98E00341D8E /* SnapshotTransformViewOptions.PiecesValue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnapshotTransformViewOptions.PiecesValue.swift; sourceTree = ""; }; + 298DBC052441C98E00341D8E /* SnapshotTransformView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnapshotTransformView.swift; sourceTree = ""; }; + 298DBC062441C98E00341D8E /* SnapshotTransformViewOptions.PiecePosition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnapshotTransformViewOptions.PiecePosition.swift; sourceTree = ""; }; + 298DBC072441C98E00341D8E /* SnapshotContainerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnapshotContainerView.swift; sourceTree = ""; }; + 298DBC082441C98E00341D8E /* SnapshotTransformViewOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnapshotTransformViewOptions.swift; sourceTree = ""; }; + 298DBC092441C98E00341D8E /* TransformCurve.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformCurve.swift; sourceTree = ""; }; + 298DBC0B2441C98E00341D8E /* ScaleTransformViewOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScaleTransformViewOptions.swift; sourceTree = ""; }; + 298DBC0C2441C98E00341D8E /* ScaleTransformView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScaleTransformView.swift; sourceTree = ""; }; + 298DBC0D2441C98E00341D8E /* ScaleTransformViewOptions.Rotation3dOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScaleTransformViewOptions.Rotation3dOptions.swift; sourceTree = ""; }; + 298DBC0E2441C98E00341D8E /* ScaleTransformViewOptions.Translation3dOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScaleTransformViewOptions.Translation3dOptions.swift; sourceTree = ""; }; + 298DBC102441C98E00341D8E /* CGFloat+Range.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGFloat+Range.swift"; sourceTree = ""; }; + 298DBC112441C98E00341D8E /* Multipliable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Multipliable.swift; sourceTree = ""; }; + 298DBC122441C98E00341D8E /* CGFloat+Interpolate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGFloat+Interpolate.swift"; sourceTree = ""; }; + 298DBC132441C98E00341D8E /* UIView+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Utilities.swift"; sourceTree = ""; }; + 298DBC142441C98E00341D8E /* CollectionViewPagingLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewPagingLayout.swift; sourceTree = ""; }; + 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 */ - AB500A2C23B104E20056BE37 /* Frameworks */ = { + 291FDEC0262327FD00AD1C14 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 291FDEC8262327FD00AD1C14 /* CollectionViewPagingLayout.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 298DBBF32441C94900341D8E /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( @@ -97,188 +118,165 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - AB1BBA8B23C935BF004E5C3B /* Cards */ = { + 291EC0DA2610B32500C65A34 /* SwiftUI */ = { isa = PBXGroup; children = ( - AB1BBA9123CA5143004E5C3B /* Cell */, - AB1BBA8D23C935D3004E5C3B /* CardsViewController.swift */, - AB1BBA8C23C935D3004E5C3B /* CardsViewController.xib */, - AB1BBA8E23C935D3004E5C3B /* CardsViewModel.swift */, + 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 = Cards; + path = SwiftUI; sourceTree = ""; }; - AB1BBA9123CA5143004E5C3B /* Cell */ = { + 291FDEC4262327FD00AD1C14 /* CollectionViewPagingLayoutTests */ = { isa = PBXGroup; children = ( - AB1BBA9823CA5178004E5C3B /* CardCellViewModel.swift */, - AB1BBA9923CA5178004E5C3B /* CardCollectionViewCell.swift */, - AB1BBA9A23CA5179004E5C3B /* CardCollectionViewCell.xib */, + 291FDEC5262327FD00AD1C14 /* CollectionViewPagingLayoutTests.swift */, + 291FDEC7262327FD00AD1C14 /* Info.plist */, ); - path = Cell; + path = CollectionViewPagingLayoutTests; sourceTree = ""; }; - AB1E03AD23B25CD30087F904 /* CustomViews */ = { + 298DBBEC2441C94900341D8E = { isa = PBXGroup; children = ( - ABA1A72F23B4225C006A46A3 /* QuantityControllerView */, - ABA1A72A23B4222E006A46A3 /* PriceTagView */, - AB1E03AE23B25CE70087F904 /* PageControlView.swift */, + 298DBBF82441C94900341D8E /* CollectionViewPagingLayout */, + 291FDEC4262327FD00AD1C14 /* CollectionViewPagingLayoutTests */, + 298DBBF72441C94900341D8E /* Products */, ); - path = CustomViews; sourceTree = ""; }; - AB500A2623B104E20056BE37 = { + 298DBBF72441C94900341D8E /* Products */ = { isa = PBXGroup; children = ( - AB500A3123B104E20056BE37 /* CollectionViewPagingLayout */, - AB500A3023B104E20056BE37 /* Products */, - ); - sourceTree = ""; - }; - AB500A3023B104E20056BE37 /* Products */ = { - isa = PBXGroup; - children = ( - AB500A2F23B104E20056BE37 /* CollectionViewPagingLayout.app */, + 298DBBF62441C94900341D8E /* CollectionViewPagingLayout.framework */, + 291FDEC3262327FD00AD1C14 /* CollectionViewPagingLayoutTests.xctest */, ); name = Products; sourceTree = ""; }; - AB500A3123B104E20056BE37 /* CollectionViewPagingLayout */ = { + 298DBBF82441C94900341D8E /* CollectionViewPagingLayout */ = { isa = PBXGroup; children = ( - AB7C1E0923B4F118006441DE /* Lib */, - AB1E03AD23B25CD30087F904 /* CustomViews */, - AB500A5223B152170056BE37 /* Modules */, - AB500A5123B152130056BE37 /* Models */, - AB500A5323B152400056BE37 /* Utilities */, - AB500A3223B104E20056BE37 /* AppDelegate.swift */, - AB500A3B23B104E60056BE37 /* Assets.xcassets */, - AB500A3D23B104E60056BE37 /* LaunchScreen.storyboard */, - AB500A4023B104E60056BE37 /* Info.plist */, + 298DBC012441C98E00341D8E /* Lib */, + 298DBBF92441C94900341D8E /* CollectionViewPagingLayout.h */, + 298DBBFA2441C94900341D8E /* Info.plist */, ); path = CollectionViewPagingLayout; sourceTree = ""; }; - AB500A4823B13B9D0056BE37 /* Fruits */ = { + 298DBC012441C98E00341D8E /* Lib */ = { isa = PBXGroup; children = ( - AB500A5923B1544A0056BE37 /* Cell */, - AB500A4923B13BBD0056BE37 /* FruitsViewController.swift */, - AB500A4B23B13BC90056BE37 /* FruitsViewController.xib */, - AB500A4F23B151780056BE37 /* FruitsViewModel.swift */, + 298DBC022441C98E00341D8E /* BlurEffectView.swift */, + 298DBC142441C98E00341D8E /* CollectionViewPagingLayout.swift */, + 298DBC232441C98E00341D8E /* TransformableView.swift */, + 298DBC092441C98E00341D8E /* TransformCurve.swift */, + 29DF4685261DF863007E0FA4 /* CollectionViewPagingLayout.ZPositionHandler.swift */, + 29DF4684261DF863007E0FA4 /* ViewAnimator.swift */, + 298DBC0A2441C98E00341D8E /* Scale */, + 298DBC032441C98E00341D8E /* Snapshot */, + 298DBC192441C98E00341D8E /* Stack */, + 291EC0DA2610B32500C65A34 /* SwiftUI */, + 298DBC0F2441C98E00341D8E /* Utilities */, ); - path = Fruits; - sourceTree = ""; + path = Lib; + sourceTree = SOURCE_ROOT; }; - AB500A5123B152130056BE37 /* Models */ = { + 298DBC032441C98E00341D8E /* Snapshot */ = { isa = PBXGroup; children = ( - AB500A5723B154210056BE37 /* Fruit.swift */, - ABC242CB23B6831400DBD4D6 /* Photo.swift */, - 2925CDDE23D4D21F00243F5F /* Card.swift */, + 298DBC042441C98E00341D8E /* SnapshotTransformViewOptions.PiecesValue.swift */, + 298DBC052441C98E00341D8E /* SnapshotTransformView.swift */, + 298DBC062441C98E00341D8E /* SnapshotTransformViewOptions.PiecePosition.swift */, + 298DBC072441C98E00341D8E /* SnapshotContainerView.swift */, + 298DBC082441C98E00341D8E /* SnapshotTransformViewOptions.swift */, + 29CD47C526235E6F00376CC8 /* SnapshotTransformViewOptions+Layout.swift */, ); - path = Models; + path = Snapshot; sourceTree = ""; }; - AB500A5223B152170056BE37 /* Modules */ = { + 298DBC0A2441C98E00341D8E /* Scale */ = { isa = PBXGroup; children = ( - AB7C1E0423B4E292006441DE /* Main */, - AB500A4823B13B9D0056BE37 /* Fruits */, - AB1BBA8B23C935BF004E5C3B /* Cards */, - ABC242C123B681F800DBD4D6 /* Gallery */, + 298DBC0B2441C98E00341D8E /* ScaleTransformViewOptions.swift */, + 298DBC0C2441C98E00341D8E /* ScaleTransformView.swift */, + 298DBC0D2441C98E00341D8E /* ScaleTransformViewOptions.Rotation3dOptions.swift */, + 298DBC0E2441C98E00341D8E /* ScaleTransformViewOptions.Translation3dOptions.swift */, + 29CD47CD26235E8A00376CC8 /* ScaleTransformViewOptions+Layout.swift */, ); - path = Modules; + path = Scale; sourceTree = ""; }; - AB500A5323B152400056BE37 /* Utilities */ = { + 298DBC0F2441C98E00341D8E /* Utilities */ = { isa = PBXGroup; children = ( - AB500A4D23B13C5C0056BE37 /* NibBased.swift */, - AB500A5423B152500056BE37 /* ViewModelBased.swift */, - AB7C1E0223B42BB8006441DE /* UIView+Utilities.swift */, + 298DBC102441C98E00341D8E /* CGFloat+Range.swift */, + 298DBC112441C98E00341D8E /* Multipliable.swift */, + 298DBC122441C98E00341D8E /* CGFloat+Interpolate.swift */, + 298DBC132441C98E00341D8E /* UIView+Utilities.swift */, ); path = Utilities; sourceTree = ""; }; - AB500A5923B1544A0056BE37 /* Cell */ = { + 298DBC192441C98E00341D8E /* Stack */ = { isa = PBXGroup; children = ( - AB500A5A23B154640056BE37 /* FruitsCollectionViewCell.swift */, - AB500A5E23B154950056BE37 /* FruitsCollectionViewCell.xib */, - AB500A5C23B1547C0056BE37 /* FruitCellViewModel.swift */, + 298DBC1A2441C98E00341D8E /* StackTransformView.swift */, + 298DBC1B2441C98E00341D8E /* StackTransformViewOptions.swift */, + 29CD47C926235E7800376CC8 /* StackTransformViewOptions+Layout.swift */, ); - path = Cell; + path = Stack; sourceTree = ""; }; - AB7C1E0423B4E292006441DE /* Main */ = { - isa = PBXGroup; - children = ( - AB7C1E0523B4E2B2006441DE /* MainViewController.swift */, - AB7C1E0723B4E2C0006441DE /* MainViewController.xib */, - ); - path = Main; - sourceTree = ""; - }; - AB7C1E0923B4F118006441DE /* Lib */ = { - isa = PBXGroup; - children = ( - AB500A4623B1061D0056BE37 /* CollectionViewPagingLayout.swift */, - AB7C1E0A23B4F224006441DE /* TransformableView.swift */, - ); - path = Lib; - sourceTree = ""; - }; - ABA1A72A23B4222E006A46A3 /* PriceTagView */ = { - isa = PBXGroup; - children = ( - ABA1A72B23B42240006A46A3 /* PriceTagView.swift */, - ABA1A72D23B42247006A46A3 /* PriceTagView.xib */, +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 298DBBF12441C94900341D8E /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 298DBBFB2441C94900341D8E /* CollectionViewPagingLayout.h in Headers */, ); - path = PriceTagView; - sourceTree = ""; + runOnlyForDeploymentPostprocessing = 0; }; - ABA1A72F23B4225C006A46A3 /* QuantityControllerView */ = { - isa = PBXGroup; - children = ( - ABA1A73023B422B2006A46A3 /* QuantityControllerView.swift */, - ABA1A73223B422B9006A46A3 /* QuantityControllerView.xib */, +/* 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 */, ); - path = QuantityControllerView; - sourceTree = ""; - }; - ABC242C123B681F800DBD4D6 /* Gallery */ = { - isa = PBXGroup; - children = ( - ABC242C823B682D400DBD4D6 /* Cell */, - ABC242C223B6822200DBD4D6 /* GalleryViewController.swift */, - ABC242C423B6822A00DBD4D6 /* GalleryViewController.xib */, - ABC242C623B6823600DBD4D6 /* GalleryViewModel.swift */, + buildRules = ( ); - path = Gallery; - sourceTree = ""; - }; - ABC242C823B682D400DBD4D6 /* Cell */ = { - isa = PBXGroup; - children = ( - ABC242C923B682DD00DBD4D6 /* PhotoCellViewModel.swift */, - ABC242CD23B6860700DBD4D6 /* PhotoCollectionViewCell.swift */, - ABC242CF23B6861000DBD4D6 /* PhotoCollectionViewCell.xib */, + dependencies = ( + 291FDECA262327FD00AD1C14 /* PBXTargetDependency */, ); - path = Cell; - sourceTree = ""; + name = CollectionViewPagingLayoutTests; + productName = CollectionViewPagingLayoutTests; + productReference = 291FDEC3262327FD00AD1C14 /* CollectionViewPagingLayoutTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - AB500A2E23B104E20056BE37 /* CollectionViewPagingLayout */ = { + 298DBBF52441C94900341D8E /* CollectionViewPagingLayout */ = { isa = PBXNativeTarget; - buildConfigurationList = AB500A4323B104E60056BE37 /* Build configuration list for PBXNativeTarget "CollectionViewPagingLayout" */; + buildConfigurationList = 298DBBFE2441C94900341D8E /* Build configuration list for PBXNativeTarget "CollectionViewPagingLayout" */; buildPhases = ( - AB500A2B23B104E20056BE37 /* Sources */, - AB500A2C23B104E20056BE37 /* Frameworks */, - AB500A2D23B104E20056BE37 /* Resources */, + 2967003224420B4100A1F508 /* Swiftlint */, + 298DBBF12441C94900341D8E /* Headers */, + 298DBBF22441C94900341D8E /* Sources */, + 298DBBF32441C94900341D8E /* Frameworks */, + 298DBBF42441C94900341D8E /* Resources */, ); buildRules = ( ); @@ -286,25 +284,28 @@ ); name = CollectionViewPagingLayout; productName = CollectionViewPagingLayout; - productReference = AB500A2F23B104E20056BE37 /* CollectionViewPagingLayout.app */; - productType = "com.apple.product-type.application"; + productReference = 298DBBF62441C94900341D8E /* CollectionViewPagingLayout.framework */; + productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ - AB500A2723B104E20056BE37 /* Project object */ = { + 298DBBED2441C94900341D8E /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1120; - LastUpgradeCheck = 1120; - ORGANIZATIONNAME = "Amir Khorsandi"; + LastSwiftUpdateCheck = 1240; + LastUpgradeCheck = 1330; + ORGANIZATIONNAME = Amir; TargetAttributes = { - AB500A2E23B104E20056BE37 = { - CreatedOnToolsVersion = 11.2.1; + 291FDEC2262327FD00AD1C14 = { + CreatedOnToolsVersion = 12.4; + }; + 298DBBF52441C94900341D8E = { + CreatedOnToolsVersion = 11.3.1; }; }; }; - buildConfigurationList = AB500A2A23B104E20056BE37 /* Build configuration list for PBXProject "CollectionViewPagingLayout" */; + buildConfigurationList = 298DBBF02441C94900341D8E /* Build configuration list for PBXProject "CollectionViewPagingLayout" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; @@ -312,85 +313,155 @@ en, Base, ); - mainGroup = AB500A2623B104E20056BE37; - productRefGroup = AB500A3023B104E20056BE37 /* Products */; + mainGroup = 298DBBEC2441C94900341D8E; + productRefGroup = 298DBBF72441C94900341D8E /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( - AB500A2E23B104E20056BE37 /* CollectionViewPagingLayout */, + 298DBBF52441C94900341D8E /* CollectionViewPagingLayout */, + 291FDEC2262327FD00AD1C14 /* CollectionViewPagingLayoutTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - AB500A2D23B104E20056BE37 /* Resources */ = { + 291FDEC1262327FD00AD1C14 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 298DBBF42441C94900341D8E /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - AB500A3F23B104E60056BE37 /* LaunchScreen.storyboard in Resources */, - ABC242C523B6822A00DBD4D6 /* GalleryViewController.xib in Resources */, - AB500A5F23B154950056BE37 /* FruitsCollectionViewCell.xib in Resources */, - AB1BBA9F23CA7BE3004E5C3B /* CardsViewController.xib in Resources */, - ABC242D023B6861000DBD4D6 /* PhotoCollectionViewCell.xib in Resources */, - AB500A4C23B13BC90056BE37 /* FruitsViewController.xib in Resources */, - AB500A3C23B104E60056BE37 /* Assets.xcassets in Resources */, - AB1BBA9D23CA5179004E5C3B /* CardCollectionViewCell.xib in Resources */, - AB7C1E0823B4E2C0006441DE /* MainViewController.xib in Resources */, - ABA1A72E23B42247006A46A3 /* PriceTagView.xib in Resources */, - ABA1A73323B422B9006A46A3 /* QuantityControllerView.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 2967003224420B4100A1F508 /* Swiftlint */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = Swiftlint; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if which swiftlint >/dev/null; then\n swiftlint autocorrect --config \"${SRCROOT}/.swiftlint_autocorrect.yml\";\n swiftlint --config \"${SRCROOT}/.swiftlint.yml\";\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ - AB500A2B23B104E20056BE37 /* Sources */ = { + 291FDEBF262327FD00AD1C14 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - AB500A4723B1061D0056BE37 /* CollectionViewPagingLayout.swift in Sources */, - ABC242CE23B6860700DBD4D6 /* PhotoCollectionViewCell.swift in Sources */, - 2925CDDF23D4D21F00243F5F /* Card.swift in Sources */, - ABA1A72C23B42240006A46A3 /* PriceTagView.swift in Sources */, - AB1E03AF23B25CE70087F904 /* PageControlView.swift in Sources */, - AB1BBA9B23CA5179004E5C3B /* CardCellViewModel.swift in Sources */, - AB500A5023B151780056BE37 /* FruitsViewModel.swift in Sources */, - AB500A4A23B13BBD0056BE37 /* FruitsViewController.swift in Sources */, - ABC242C323B6822200DBD4D6 /* GalleryViewController.swift in Sources */, - AB1BBAA023CA7BE6004E5C3B /* CardsViewModel.swift in Sources */, - AB7C1E0323B42BB8006441DE /* UIView+Utilities.swift in Sources */, - AB500A4E23B13C5D0056BE37 /* NibBased.swift in Sources */, - ABC242C723B6823600DBD4D6 /* GalleryViewModel.swift in Sources */, - AB1BBA9E23CA7BD9004E5C3B /* CardsViewController.swift in Sources */, - ABC242CA23B682DD00DBD4D6 /* PhotoCellViewModel.swift in Sources */, - AB7C1E0623B4E2B2006441DE /* MainViewController.swift in Sources */, - AB500A5D23B1547C0056BE37 /* FruitCellViewModel.swift in Sources */, - AB1BBA9C23CA5179004E5C3B /* CardCollectionViewCell.swift in Sources */, - ABC242CC23B6831400DBD4D6 /* Photo.swift in Sources */, - AB500A5823B154210056BE37 /* Fruit.swift in Sources */, - AB500A5B23B154640056BE37 /* FruitsCollectionViewCell.swift in Sources */, - ABA1A73123B422B2006A46A3 /* QuantityControllerView.swift in Sources */, - AB7C1E0B23B4F224006441DE /* TransformableView.swift in Sources */, - AB500A5523B152500056BE37 /* ViewModelBased.swift in Sources */, - AB500A3323B104E20056BE37 /* AppDelegate.swift in Sources */, + 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 PBXVariantGroup section */ - AB500A3D23B104E60056BE37 /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - AB500A3E23B104E60056BE37 /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; +/* Begin PBXTargetDependency section */ + 291FDECA262327FD00AD1C14 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 298DBBF52441C94900341D8E /* CollectionViewPagingLayout */; + targetProxy = 291FDEC9262327FD00AD1C14 /* PBXContainerItemProxy */; }; -/* End PBXVariantGroup section */ +/* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - AB500A4123B104E60056BE37 /* Debug */ = { + 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 = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -416,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; @@ -423,6 +495,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -440,17 +513,19 @@ 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; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; }; name = Debug; }; - AB500A4223B104E60056BE37 /* Release */ = { + 298DBBFD2441C94900341D8E /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -476,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; @@ -483,6 +559,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -494,74 +571,103 @@ 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; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; }; name = Release; }; - AB500A4423B104E60056BE37 /* Debug */ = { + 298DBBFF2441C94900341D8E /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 4J5W7CJ2ZV; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + 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", ); - PRODUCT_BUNDLE_IDENTIFIER = app.amir.CollectionViewPagingLayout; - PRODUCT_NAME = "$(TARGET_NAME)"; + 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; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; - AB500A4523B104E60056BE37 /* Release */ = { + 298DBC002441C94900341D8E /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 4J5W7CJ2ZV; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + 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", ); - PRODUCT_BUNDLE_IDENTIFIER = app.amir.CollectionViewPagingLayout; - PRODUCT_NAME = "$(TARGET_NAME)"; + 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; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - AB500A2A23B104E20056BE37 /* Build configuration list for PBXProject "CollectionViewPagingLayout" */ = { + 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 = ( - AB500A4123B104E60056BE37 /* Debug */, - AB500A4223B104E60056BE37 /* Release */, + 298DBBFC2441C94900341D8E /* Debug */, + 298DBBFD2441C94900341D8E /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - AB500A4323B104E60056BE37 /* Build configuration list for PBXNativeTarget "CollectionViewPagingLayout" */ = { + 298DBBFE2441C94900341D8E /* Build configuration list for PBXNativeTarget "CollectionViewPagingLayout" */ = { isa = XCConfigurationList; buildConfigurations = ( - AB500A4423B104E60056BE37 /* Debug */, - AB500A4523B104E60056BE37 /* Release */, + 298DBBFF2441C94900341D8E /* Debug */, + 298DBC002441C94900341D8E /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; - rootObject = AB500A2723B104E20056BE37 /* Project object */; + rootObject = 298DBBED2441C94900341D8E /* Project object */; } diff --git a/CollectionViewPagingLayout.xcodeproj/xcshareddata/xcschemes/CollectionViewPagingLayout.xcscheme b/CollectionViewPagingLayout.xcodeproj/xcshareddata/xcschemes/CollectionViewPagingLayout.xcscheme new file mode 100644 index 0000000..250ff93 --- /dev/null +++ b/CollectionViewPagingLayout.xcodeproj/xcshareddata/xcschemes/CollectionViewPagingLayout.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CollectionViewPagingLayout.xcodeproj/xcshareddata/xcschemes/CollectionViewPagingLayoutTests.xcscheme b/CollectionViewPagingLayout.xcodeproj/xcshareddata/xcschemes/CollectionViewPagingLayoutTests.xcscheme new file mode 100644 index 0000000..eff24f9 --- /dev/null +++ b/CollectionViewPagingLayout.xcodeproj/xcshareddata/xcschemes/CollectionViewPagingLayoutTests.xcscheme @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/CollectionViewPagingLayout/AppDelegate.swift b/CollectionViewPagingLayout/AppDelegate.swift deleted file mode 100644 index c439336..0000000 --- a/CollectionViewPagingLayout/AppDelegate.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// AppDelegate.swift -// CollectionViewPagingLayout -// -// Created by Amir Khorsandi on 12/23/19. -// Copyright © 2019 Amir Khorsandi. All rights reserved. -// - -import UIKit - -@UIApplicationMain -class AppDelegate: UIResponder, UIApplicationDelegate { - - - var window: UIWindow? - var navigationController: UINavigationController! - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. - - window = UIWindow(frame: UIScreen.main.bounds) - navigationController = UINavigationController() - navigationController.isNavigationBarHidden = true - navigationController.setViewControllers([MainViewController.instantiate()], animated: false) - window!.rootViewController = navigationController - window!.makeKeyAndVisible() - return true - } - -} - diff --git a/CollectionViewPagingLayout/Assets.xcassets/AppIcon.appiconset/Contents.json b/CollectionViewPagingLayout/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index d8db8d6..0000000 --- a/CollectionViewPagingLayout/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "size" : "20x20", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "20x20", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "3x" - }, - { - "idiom" : "ipad", - "size" : "20x20", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "20x20", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "83.5x83.5", - "scale" : "2x" - }, - { - "idiom" : "ios-marketing", - "size" : "1024x1024", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/CollectionViewPagingLayout/CollectionViewPagingLayout.h b/CollectionViewPagingLayout/CollectionViewPagingLayout.h new file mode 100644 index 0000000..fee307a --- /dev/null +++ b/CollectionViewPagingLayout/CollectionViewPagingLayout.h @@ -0,0 +1,15 @@ +// +// CollectionViewPagingLayout.h +// CollectionViewPagingLayout +// +// Created by Amir on 11/04/2020. +// Copyright © 2020 Amir. All rights reserved. +// + +#import + +//! Project version number for CollectionViewPagingLayout. +FOUNDATION_EXPORT double CollectionViewPagingLayoutVersionNumber; + +//! Project version string for CollectionViewPagingLayout. +FOUNDATION_EXPORT const unsigned char CollectionViewPagingLayoutVersionString[]; diff --git a/CollectionViewPagingLayout/Info.plist b/CollectionViewPagingLayout/Info.plist index e18d494..c0701c6 100644 --- a/CollectionViewPagingLayout/Info.plist +++ b/CollectionViewPagingLayout/Info.plist @@ -15,29 +15,8 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.0 + $(MARKETING_VERSION) CFBundleVersion - 1 - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - - UIUserInterfaceStyle - Light - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - + $(CURRENT_PROJECT_VERSION) diff --git a/CollectionViewPagingLayout/Lib/CollectionViewPagingLayout.swift b/CollectionViewPagingLayout/Lib/CollectionViewPagingLayout.swift deleted file mode 100644 index fa9b371..0000000 --- a/CollectionViewPagingLayout/Lib/CollectionViewPagingLayout.swift +++ /dev/null @@ -1,144 +0,0 @@ -// -// CollectionViewPagingLayout.swift -// CollectionViewPagingLayout -// -// Created by Amir Khorsandi on 12/23/19. -// Copyright © 2019 Amir Khorsandi. All rights reserved. -// - -import UIKit - -public protocol CollectionViewPagingLayoutDelegate: class { - func onCurrentPageChanged(layout: CollectionViewPagingLayout, currentPage: Int) -} - -public class CollectionViewPagingLayout: UICollectionViewLayout { - - // MARK: Properties - - public var numberOfVisibleItems: Int? - - public var scrollDirection: UICollectionView.ScrollDirection = .horizontal - - weak var delegate: CollectionViewPagingLayoutDelegate? - - override public var collectionViewContentSize: CGSize { - var safeAreaLeftRight: CGFloat = 0 - var safeAreaTopBottom: CGFloat = 0 - if #available(iOS 11, *) { - safeAreaLeftRight = (collectionView?.safeAreaInsets.left ?? 0) + (collectionView?.safeAreaInsets.right ?? 0) - safeAreaTopBottom = (collectionView?.safeAreaInsets.top ?? 0) + (collectionView?.safeAreaInsets.bottom ?? 0) - } - if scrollDirection == .horizontal { - return CGSize(width: CGFloat(numberOfItems) * visibleRect.width, height: visibleRect.height - safeAreaTopBottom) - } else { - return CGSize(width: visibleRect.width - safeAreaLeftRight, height: CGFloat(numberOfItems) * visibleRect.height) - } - } - - private(set) var currentPage: Int - - private var visibleRect: CGRect { - guard let collectionView = collectionView else { - return .zero - } - return CGRect(origin: collectionView.contentOffset, size: collectionView.bounds.size) - } - - private var numberOfItems: Int { - collectionView?.numberOfItems(inSection: 0) ?? 0 - } - - - // MARK: Life cycle - - public override init() { - currentPage = 0 - super.init() - } - - required init?(coder aDecoder: NSCoder) { - fatalError("not available") - } - - - // MARK: Public functions - - public func setCurrentPage(_ page: Int, animated: Bool = true) { - var offset = (scrollDirection == .horizontal ? visibleRect.width : visibleRect.height) * CGFloat(page) - offset = max(0, offset) - offset = scrollDirection == .horizontal ? min(collectionViewContentSize.width - visibleRect.width, offset) : min(collectionViewContentSize.height - visibleRect.height, offset) - collectionView?.setContentOffset(scrollDirection == .horizontal ? .init(x: offset, y: 0) : .init(x: 0, y: offset), - animated: animated) - } - - public func goToNextPage(animated: Bool = true) { - setCurrentPage(currentPage + 1, animated: animated) - } - - public func goToPrevPage(animated: Bool = true) { - setCurrentPage(currentPage - 1, animated: animated) - } - - - // MARK: UICollectionViewFlowLayout - - override public func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { - return true - } - - override public func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { - var attributesArray: [UICollectionViewLayoutAttributes] = [] - var numberOfAttributes = min(numberOfItems, numberOfVisibleItems ?? numberOfItems) - numberOfAttributes = max(numberOfAttributes, 3) - if numberOfAttributes % 2 == 0 { - numberOfAttributes += 1 - } - - let currentIndex = Int(round(scrollDirection == .horizontal ? (visibleRect.minX / visibleRect.width) : (visibleRect.minY / visibleRect.height))) - let startIndex = max(0, currentIndex - (numberOfAttributes - 1)/2) - - for index in startIndex..= CGFloat(numberOfVisibleItems) - 1 { - attributes.isHidden = true - } else { - let cell = collectionView?.cellForItem(at: attributes.indexPath) - if cell == nil || cell is TransformableView { - attributes.frame = visibleRect - (cell as? TransformableView)?.transform(progress: progress) - zIndex = (cell as? TransformableView)?.zPosition(progress: progress) ?? zIndex - } else { - attributes.frame = .init(origin: .init(x: CGFloat(index) * visibleRect.width, y: 0), size: visibleRect.size) - } - } - attributes.zIndex = zIndex - attributesArray.append(attributes) - } - return attributesArray - } - - override public func invalidateLayout() { - super.invalidateLayout() - updateCurrentPageIfNeeded() - } - - - // MARK: Private functions - - private func updateCurrentPageIfNeeded() { - var currentPage: Int = 0 - if let collectionView = collectionView { - let pageSize = scrollDirection == .horizontal ? collectionView.frame.width : collectionView.frame.height - let contentOffset = scrollDirection == .horizontal ? (collectionView.contentOffset.x + collectionView.contentInset.left) : (collectionView.contentOffset.y + collectionView.contentInset.top) - currentPage = Int(round(contentOffset / pageSize)) - } - if currentPage != self.currentPage { - self.currentPage = currentPage - self.delegate?.onCurrentPageChanged(layout: self, currentPage: currentPage) - } - } - -} diff --git a/CollectionViewPagingLayout/Lib/TransformableView.swift b/CollectionViewPagingLayout/Lib/TransformableView.swift deleted file mode 100644 index d70e1e2..0000000 --- a/CollectionViewPagingLayout/Lib/TransformableView.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// TransformableView.swift -// CollectionViewPagingLayout -// -// Created by Amir Khorsandi on 12/26/19. -// Copyright © 2019 Amir Khorsandi. All rights reserved. -// - -import Foundation -import UIKit - -public protocol TransformableView { - - /// Sends a float value based on the position of the view (cell) - /// if the view is in the center of CollectionView it sends 0 - /// - /// - Parameter progress: the interpolated progress for the cell view - func transform(progress: CGFloat) - - /// Optional function for providing the Z index(position) of cell view - /// As defined as an extension the default value of zIndex is Int(-abs(round(progress))) - /// - /// - Parameter progress: the interpolated progress for the cell view - /// - Returns: the z index(position) - func zPosition(progress: CGFloat) -> Int -} - - -extension TransformableView { - - func zPosition(progress: CGFloat) -> Int { - Int(-abs(round(progress))) - } -} diff --git a/CollectionViewPagingLayout/Modules/Main/MainViewController.swift b/CollectionViewPagingLayout/Modules/Main/MainViewController.swift deleted file mode 100644 index d1cd568..0000000 --- a/CollectionViewPagingLayout/Modules/Main/MainViewController.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// MainViewController.swift -// CollectionViewPagingLayout -// -// Created by Amir Khorsandi on 12/26/19. -// Copyright © 2019 Amir Khorsandi. All rights reserved. -// - -import Foundation -import UIKit - -class MainViewController: UIViewController, NibBased { - - - // MARK: Event listeners - - @IBAction private func fruitsButtonTouched() { - navigationController?.pushViewController( - FruitsViewController.instantiate(viewModel: FruitsViewModel()), - animated: true - ) - } - - @IBAction private func galleryButtonTouched() { - navigationController?.pushViewController( - GalleryViewController.instantiate(viewModel: GalleryViewModel()), - animated: true - ) - } - - @IBAction private func cardsButtonTouched() { - navigationController?.pushViewController( - CardsViewController.instantiate(viewModel: CardsViewModel()), - animated: true - ) - } - -} diff --git a/CollectionViewPagingLayout/Modules/Main/MainViewController.xib b/CollectionViewPagingLayout/Modules/Main/MainViewController.xib deleted file mode 100644 index d3e382a..0000000 --- a/CollectionViewPagingLayout/Modules/Main/MainViewController.xib +++ /dev/null @@ -1,118 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/CollectionViewPagingLayout/Utilities/UIView+Utilities.swift b/CollectionViewPagingLayout/Utilities/UIView+Utilities.swift deleted file mode 100644 index 9ed50f0..0000000 --- a/CollectionViewPagingLayout/Utilities/UIView+Utilities.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// UIView+Utilities.swift -// CollectionViewPagingLayout -// -// Created by Amir Khorsandi on 12/26/19. -// Copyright © 2019 Amir Khorsandi. All rights reserved. -// - -import UIKit - -extension UIView { - - func fill(with view: UIView) { - addSubview(view) - translatesAutoresizingMaskIntoConstraints = false - view.translatesAutoresizingMaskIntoConstraints = false - addConstraints([ - NSLayoutConstraint(item: view, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: 0), - NSLayoutConstraint(item: view, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: 0), - NSLayoutConstraint(item: view, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 0), - NSLayoutConstraint(item: view, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0) - ]) - } -} 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 new file mode 100644 index 0000000..c7ac78d --- /dev/null +++ b/Lib/BlurEffectView.swift @@ -0,0 +1,67 @@ +// +// BlurEffectView.swift +// CollectionViewPagingLayout +// +// Created by Amir on 23/02/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit + +public class BlurEffectView: UIVisualEffectView { + + // MARK: Parameters + + private var animator: UIViewPropertyAnimator? + private var radius: CGFloat? + + + // MARK: Lifecycle + + deinit { + animator?.stopAnimation(true) + } + + public override func removeFromSuperview() { + super.removeFromSuperview() + animator?.stopAnimation(true) + } + + public override func layoutSubviews() { + super.layoutSubviews() + if animator?.state != .active { + if let radius = radius, effect != nil { + setBlurRadius(radius: radius) + } else { + removeFromSuperview() + } + } + } + + + // MARK: Public functions + + /// Create blur effect with custom radius + /// + /// - Parameters: + /// - effect: optional blur effect, eg UIBlurEffect(style: .dark), default value is current effect + /// - radius: blur intensity between 0.0 and 1.0 + public func setBlurRadius(effect: UIBlurEffect? = nil, radius: CGFloat = 1.0) { + guard let effect = (effect ?? self.effect) as? UIBlurEffect else { + return + } + if animator == nil || + self.effect == .none || + animator?.state != .active || + animator?.fractionComplete == 1 || + animator?.fractionComplete == 0 { + + animator?.stopAnimation(true) + self.effect = nil + animator = UIViewPropertyAnimator(duration: 1, curve: .linear) { [unowned self] in self.effect = effect } + } + self.radius = radius + animator?.fractionComplete = radius + } + +} 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 new file mode 100644 index 0000000..f9f1b86 --- /dev/null +++ b/Lib/CollectionViewPagingLayout.swift @@ -0,0 +1,340 @@ +// +// CollectionViewPagingLayout.swift +// CollectionViewPagingLayout +// +// Created by Amir Khorsandi on 12/23/19. +// Copyright © 2019 Amir Khorsandi. All rights reserved. +// + +import UIKit + +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) +} + + +public extension CollectionViewPagingLayoutDelegate { + func onCurrentPageChanged(layout: CollectionViewPagingLayout, currentPage: Int) {} +} + + +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) + } + } + + private var currentScrollOffset: CGFloat { + let visibleRect = self.visibleRect + return scrollDirection == .horizontal ? (visibleRect.minX / max(visibleRect.width, 1)) : (visibleRect.minY / max(visibleRect.height, 1)) + } + + private var visibleRect: CGRect { + collectionView.map { CGRect(origin: $0.contentOffset, size: $0.bounds.size) } ?? .zero + } + + private var numberOfItems: Int { + guard let numberOfSections = collectionView?.numberOfSections, numberOfSections > 0 else { + return 0 + } + return (0.. Void)? = nil) { + safelySetCurrentPage(page, animated: animated, animator: animator, completion: completion) + } + + public func setCurrentPage(_ page: Int, + animated: Bool = true, + completion: (() -> Void)? = nil) { + safelySetCurrentPage(page, animated: animated, animator: defaultAnimator, completion: completion) + } + + public func goToNextPage(animated: Bool = true, + animator: ViewAnimator? = nil, + completion: (() -> Void)? = nil) { + setCurrentPage(currentPage + 1, animated: animated, animator: animator, completion: completion) + } + + 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() + }) + } + } + + + // MARK: UICollectionViewLayout + + override public func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { + if newBounds.size != visibleRect.size { + currentPageCache = currentPage + } + return true + } + + override public func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { + let currentScrollOffset = self.currentScrollOffset + let numberOfItems = self.numberOfItems + let attributesCount = numberOfVisibleItems ?? numberOfItems + let visibleRangeMid = attributesCount / 2 + let currentPageIndex = Int(round(currentScrollOffset)) + var initialStartIndex = currentPageIndex - visibleRangeMid + var initialEndIndex = currentPageIndex + visibleRangeMid + if attributesCount % 2 != 0 { + if currentPageIndex < visibleRangeMid { + initialStartIndex -= 1 + } else { + initialEndIndex += 1 + } + } + let startIndexOutOfBounds = max(0, -initialStartIndex) + let endIndexOutOfBounds = max(0, initialEndIndex - numberOfItems) + let startIndex = max(0, initialStartIndex - endIndexOutOfBounds) + let endIndex = min(numberOfItems, initialEndIndex + startIndexOutOfBounds) + + var attributesArray: [(page: Int, attributes: UICollectionViewLayoutAttributes)] = [] + var section = 0 + var numberOfItemsInSection = collectionView?.numberOfItems(inSection: section) ?? 0 + var numberOfItemsInPrevSections = 0 + for index in startIndex..= numberOfItemsInSection { + numberOfItemsInPrevSections += numberOfItemsInSection + section += 1 + numberOfItemsInSection = collectionView?.numberOfItems(inSection: section) ?? 0 + item = index - numberOfItemsInPrevSections + } + + let cellAttributes = UICollectionViewLayoutAttributes(forCellWith: IndexPath(item: item, section: section)) + let pageIndex = CGFloat(index) + let progress = pageIndex - currentScrollOffset + var zIndex = Int(-abs(round(progress))) + + let cell = collectionView?.cellForItem(at: cellAttributes.indexPath) + + if let cell = cell as? TransformableView { + cell.transform(progress: progress) + zIndex = cell.zPosition(progress: progress) + } + + 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) + } + + // 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 + } + attributesArray.append((page: Int(pageIndex), attributes: cellAttributes)) + } + attributesCache = attributesArray + addBoundsObserverIfNeeded() + return attributesArray.map(\.attributes) + } + + override public func invalidateLayout() { + super.invalidateLayout() + if let page = currentPageCache { + setCurrentPage(page, animated: false) + currentPageCache = nil + } else { + updateCurrentPageIfNeeded() + } + } + + + // MARK: Private functions + + private func updateCurrentPageIfNeeded() { + var currentPage: Int = 0 + if let collectionView = collectionView { + let contentOffset = collectionView.contentOffset + let pageSize = scrollDirection == .horizontal ? collectionView.frame.width : collectionView.frame.height + let offset = scrollDirection == .horizontal ? + (contentOffset.x + collectionView.contentInset.left) : + (contentOffset.y + collectionView.contentInset.top) + if pageSize > 0 { + currentPage = Int(round(offset / pageSize)) + } + } + if currentPage != self.currentPage, !isAnimating { + self.currentPage = currentPage + } + } + + private func getContentSize() -> CGSize { + var safeAreaLeftRight: CGFloat = 0 + var safeAreaTopBottom: CGFloat = 0 + if #available(iOS 11, *) { + safeAreaLeftRight = (collectionView?.safeAreaInsets.left ?? 0) + (collectionView?.safeAreaInsets.right ?? 0) + safeAreaTopBottom = (collectionView?.safeAreaInsets.top ?? 0) + (collectionView?.safeAreaInsets.bottom ?? 0) + } + if scrollDirection == .horizontal { + return CGSize(width: CGFloat(numberOfItems) * visibleRect.width, height: visibleRect.height - safeAreaTopBottom) + } else { + return CGSize(width: visibleRect.width - safeAreaLeftRight, height: CGFloat(numberOfItems) * visibleRect.height) + } + } + + 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 = Double(pageSize) * Double(page) + offset = max(0, offset) + offset = min(offset, Double(maxPossibleOffset)) + let contentOffset: CGPoint = scrollDirection == .horizontal ? CGPoint(x: offset, y: 0) : CGPoint(x: 0, y: offset) + + 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) + } + + // this is necessary when we want to set the current page without animation + if !animated, page != currentPage { + invalidateLayoutInBatchUpdate() + } + } + + 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?() + } + } + } +} + + +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 new file mode 100644 index 0000000..9d10c22 --- /dev/null +++ b/Lib/Scale/ScaleTransformView.swift @@ -0,0 +1,180 @@ +// +// ScaleTransformView.swift +// CollectionViewPagingLayout +// +// Created by Amir on 15/02/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit + +/// A protocol for adding scale transformation to `TransformableView` +public protocol ScaleTransformView: TransformableView { + + /// Options for controlling scale effect, see `ScaleTransformViewOptions.swift` + var scaleOptions: ScaleTransformViewOptions { get } + + /// The view to apply scale effect on + var scalableView: UIView { get } + + /// The view to apply blur effect on + var scaleBlurViewHost: UIView { get } + + /// the main function for applying transforms + func applyScaleTransform(progress: CGFloat) +} + + +public extension ScaleTransformView { + + /// The default value is the super view of `scalableView` + var scaleBlurViewHost: UIView { + scalableView.superview ?? scalableView + } +} + + +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 + } +} + + +public extension ScaleTransformView { + + // MARK: Properties + + var scaleOptions: ScaleTransformViewOptions { + .init() + } + + + // MARK: TransformableView + + func transform(progress: CGFloat) { + applyScaleTransform(progress: progress) + } + + + // MARK: Public functions + + func applyScaleTransform(progress: CGFloat) { + applyStyle(progress: progress) + applyScaleAndTranslation(progress: progress) + applyCATransform3D(progress: progress) + + if #available(iOS 10, *) { + applyBlurEffect(progress: progress) + } + + } + + + // MARK: Private functions + + private func applyStyle(progress: CGFloat) { + guard scaleOptions.shadowEnabled else { + return + } + 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, widthProgressValue), + height: max(scaleOptions.shadowOffsetMin.height, heightProgressValue) + ) + layer.shadowOffset = offset + layer.shadowRadius = max(scaleOptions.shadowRadiusMin, progressMultiplier * scaleOptions.shadowRadiusMax) + layer.shadowOpacity = max(scaleOptions.shadowOpacityMin, (1 - abs(Float(progress))) * scaleOptions.shadowOpacityMax) + } + + private func applyScaleAndTranslation(progress: CGFloat) { + var transform = CGAffineTransform.identity + var xAdjustment: CGFloat = 0 + var yAdjustment: CGFloat = 0 + let scaleProgress = scaleOptions.scaleCurve.computeFromLinear(progress: abs(progress)) + var scale = 1 - scaleProgress * scaleOptions.scaleRatio + scale = max(scale, scaleOptions.minScale) + scale = min(scale, scaleOptions.maxScale) + + if scaleOptions.keepHorizontalSpacingEqual { + xAdjustment = ((1 - scale) * scalableView.bounds.width) / 2 + if progress > 0 { + xAdjustment *= -1 + } + } + + if scaleOptions.keepVerticalSpacingEqual { + yAdjustment = ((1 - scale) * scalableView.bounds.height) / 2 + } + + let translateProgress = scaleOptions.translationCurve.computeFromLinear(progress: abs(progress)) + var translateX = scalableView.bounds.width * scaleOptions.translationRatio.x * (translateProgress * (progress < 0 ? -1 : 1)) - xAdjustment + var translateY = scalableView.bounds.height * scaleOptions.translationRatio.y * abs(translateProgress) - yAdjustment + if let min = scaleOptions.minTranslationRatio { + translateX = max(translateX, scalableView.bounds.width * min.x) + translateY = max(translateY, scalableView.bounds.height * min.y) + } + if let max = scaleOptions.maxTranslationRatio { + translateX = min(translateX, scalableView.bounds.width * max.x) + translateY = min(translateY, scalableView.bounds.height * max.y) + } + transform = transform + .translatedBy(x: translateX, y: translateY) + .scaledBy(x: scale, y: scale) + scalableView.transform = transform + } + + private func applyCATransform3D(progress: CGFloat) { + var transform = CATransform3DMakeAffineTransform(scalableView.transform) + + if let options = self.scaleOptions.rotation3d { + var angle = options.angle * progress + angle = max(angle, options.minAngle) + angle = min(angle, options.maxAngle) + transform.m34 = options.m34 + transform = CATransform3DRotate(transform, angle, options.x, options.y, options.z) + scalableView.layer.isDoubleSided = options.isDoubleSided + } + + if let options = self.scaleOptions.translation3d { + var x = options.translateRatios.0 * progress * scalableView.bounds.width + var y = options.translateRatios.1 * abs(progress) * scalableView.bounds.height + var z = options.translateRatios.2 * abs(progress) * scalableView.bounds.width + x = max(x, options.minTranslateRatios.0 * scalableView.bounds.width) + x = min(x, options.maxTranslateRatios.0 * scalableView.bounds.width) + y = max(y, options.minTranslateRatios.1 * scalableView.bounds.height) + y = min(y, options.maxTranslateRatios.1 * scalableView.bounds.height) + z = max(z, options.minTranslateRatios.2 * scalableView.bounds.width) + z = min(z, options.maxTranslateRatios.2 * scalableView.bounds.width) + + transform = CATransform3DTranslate(transform, x, y, z) + } + scalableView.layer.transform = transform + } + + private func applyBlurEffect(progress: CGFloat) { + guard scaleOptions.blurEffectRadiusRatio > 0, scaleOptions.blurEffectEnabled else { + scaleBlurViewHost.subviews.first(where: { $0 is BlurEffectView })?.removeFromSuperview() + return + } + let blurView: BlurEffectView + if let view = scaleBlurViewHost.subviews.first(where: { $0 is BlurEffectView }) as? BlurEffectView { + blurView = view + } else { + blurView = BlurEffectView(effect: UIBlurEffect(style: scaleOptions.blurEffectStyle)) + 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/Lib/Scale/ScaleTransformViewOptions+Layout.swift b/Lib/Scale/ScaleTransformViewOptions+Layout.swift new file mode 100644 index 0000000..6625331 --- /dev/null +++ b/Lib/Scale/ScaleTransformViewOptions+Layout.swift @@ -0,0 +1,141 @@ +// +// ScaleTransformViewOptions+Layout.swift +// CollectionViewPagingLayout +// +// Created by Amir on 28/03/2021. +// Copyright © 2021 Amir Khorsandi. All rights reserved. +// + +import UIKit +import Foundation + +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), + maxTranslationRatio: CGPoint(x: 2, y: 0), + blurEffectEnabled: true, + blurEffectRadiusRatio: 0.2 + ) + case .linear: + return Self( + minScale: 0.6, + scaleRatio: 0.4, + translationRatio: CGPoint(x: 0.66, y: 0.2), + maxTranslationRatio: CGPoint(x: 2, y: 0), + keepVerticalSpacingEqual: true, + keepHorizontalSpacingEqual: true, + scaleCurve: .linear, + translationCurve: .linear + ) + case .easeIn: + return Self( + minScale: 0.6, + scaleRatio: 0.4, + translationRatio: CGPoint(x: 0.66, y: 0.2), + keepVerticalSpacingEqual: true, + keepHorizontalSpacingEqual: true, + scaleCurve: .easeIn, + translationCurve: .linear + ) + case .easeOut: + return Self( + minScale: 0.6, + scaleRatio: 0.4, + translationRatio: CGPoint(x: 0.66, y: 0.2), + keepVerticalSpacingEqual: true, + keepHorizontalSpacingEqual: true, + scaleCurve: .linear, + translationCurve: .easeIn + ) + case .rotary: + return Self( + minScale: 0, + scaleRatio: 0.4, + translationRatio: CGPoint(x: 0.1, y: 0.1), + minTranslationRatio: CGPoint(x: -1, y: 0), + maxTranslationRatio: CGPoint(x: 1, y: 1), + rotation3d: ScaleTransformViewOptions.Rotation3dOptions(angle: .pi / 15, minAngle: -.pi / 3, maxAngle: .pi / 3, x: 0, y: 0, z: 1, m34: -0.004), + translation3d: .init(translateRatios: (0.9, 0.1, 0), + minTranslateRatios: (-3, -0.8, -0.3), + maxTranslateRatios: (3, 0.8, -0.3)) + ) + case .cylinder: + return Self( + minScale: 0.55, + maxScale: 0.55, + scaleRatio: 0, + translationRatio: .zero, + minTranslationRatio: .zero, + maxTranslationRatio: .zero, + shadowEnabled: false, + 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 .invertedCylinder: + return Self( + minScale: 1.2, + maxScale: 1.2, + scaleRatio: 0, + translationRatio: .zero, + minTranslationRatio: .zero, + maxTranslationRatio: .zero, + shadowEnabled: false, + rotation3d: .init(angle: .pi / 3, minAngle: -.pi, maxAngle: .pi, x: 0, y: -1, z: 0, m34: -0.002), + translation3d: .init(translateRatios: (0.1, 0, 0), + minTranslateRatios: (-0.05, 0, 0.86), + maxTranslateRatios: (0.05, 0, -0.86)) + ) + 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, + translationRatio: .zero, + minTranslationRatio: .zero, + maxTranslationRatio: .zero, + shadowEnabled: true, + rotation3d: rotation3d, + translation3d: translation3d + ) + } + } +} diff --git a/Lib/Scale/ScaleTransformViewOptions.Rotation3dOptions.swift b/Lib/Scale/ScaleTransformViewOptions.Rotation3dOptions.swift new file mode 100644 index 0000000..06b937d --- /dev/null +++ b/Lib/Scale/ScaleTransformViewOptions.Rotation3dOptions.swift @@ -0,0 +1,52 @@ +// +// ScaleTransformViewOptions.Rotation3dOptions.swift +// CollectionViewPagingLayout +// +// Created by Amir on 27/03/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit + +public extension ScaleTransformViewOptions { + + struct Rotation3dOptions { + + // MARK: Properties + + /// The angle for rotate side views + public var angle: CGFloat + + /// The minimum angle for rotation + public var minAngle: CGFloat + + /// The maximum angle for rotation + public var maxAngle: CGFloat + + public var x: CGFloat + + public var y: CGFloat + + public var z: CGFloat + + /// `CATransform3D.m34`, read more: https://stackoverflow.com/questions/3881446/meaning-of-m34-of-catransform3d + public var m34: CGFloat + + /// `CALayer.isDoubleSided`, read more: https://developer.apple.com/documentation/quartzcore/calayer/1410924-isdoublesided + public var isDoubleSided: Bool = false + + + // MARK: Lifecycle + + public init(angle: CGFloat, minAngle: CGFloat, maxAngle: CGFloat, x: CGFloat, y: CGFloat, z: CGFloat, m34: CGFloat) { + self.angle = angle + self.minAngle = minAngle + self.maxAngle = maxAngle + self.x = x + self.y = y + self.z = z + self.m34 = m34 + } + } + +} diff --git a/Lib/Scale/ScaleTransformViewOptions.Translation3dOptions.swift b/Lib/Scale/ScaleTransformViewOptions.Translation3dOptions.swift new file mode 100644 index 0000000..127eb3b --- /dev/null +++ b/Lib/Scale/ScaleTransformViewOptions.Translation3dOptions.swift @@ -0,0 +1,43 @@ +// +// ScaleTransformViewOptions.Translation3dOptions.swift +// CollectionViewPagingLayout +// +// Created by Amir on 27/03/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit + +public extension ScaleTransformViewOptions { + + struct Translation3dOptions { + + // MARK: Properties + + /// The translates(x,y,z) ratios + /// (translateX = progress * translates.x * targetView.width) + /// (translateY = progress * translates.y * targetView.height) + /// (translateZ = progress * translates.z * targetView.width) + public var translateRatios: (CGFloat, CGFloat, CGFloat) + + /// The minimum translate ratios + public var minTranslateRatios: (CGFloat, CGFloat, CGFloat) + + /// The maximum translate ratios + public var maxTranslateRatios: (CGFloat, CGFloat, CGFloat) + + + // MARK: Lifecycle + + public init( + translateRatios: (CGFloat, CGFloat, CGFloat), + minTranslateRatios: (CGFloat, CGFloat, CGFloat), + maxTranslateRatios: (CGFloat, CGFloat, CGFloat) + ) { + self.translateRatios = translateRatios + self.minTranslateRatios = minTranslateRatios + self.maxTranslateRatios = maxTranslateRatios + } + } + +} diff --git a/Lib/Scale/ScaleTransformViewOptions.swift b/Lib/Scale/ScaleTransformViewOptions.swift new file mode 100644 index 0000000..be2d9bf --- /dev/null +++ b/Lib/Scale/ScaleTransformViewOptions.swift @@ -0,0 +1,146 @@ +// +// ScaleTransformViewOptions.swift +// CollectionViewPagingLayout +// +// Created by Amir on 19/02/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import Foundation +import UIKit + +public struct ScaleTransformViewOptions { + + // MARK: Properties + + /// The minimum scale factor for side views + public var minScale: CGFloat + + /// The maximum scale factor for side views + public var maxScale: CGFloat + + /// Ratio for computing scale for each item + /// Scale = 1 - progress * `scaleRatio` + public var scaleRatio: CGFloat + + /// Ratio for the amount of translate for side views, calculates by `scalableView` size + /// for instance, if translationRatio.x = 0.5 and scalableView.width = 100 then + /// translateX = 50 for the right view and translateX = -50 for the left view + public var translationRatio: CGPoint + + /// The minimum amount of translate for side views, calculates like `translationRatio` + public var minTranslationRatio: CGPoint? + + /// The maximum amount of translate for side views, calculates like `translationRatio` + public var maxTranslationRatio: CGPoint? + + /// Since it applies scale on views the spacing between views wouldn't be equal + /// by default, if this flag is enabled it will keep spacing between items equally + public var keepVerticalSpacingEqual: Bool + + /// Like `keepHorizontalSpacingEqual` but for horizontal spacing + public var keepHorizontalSpacingEqual: Bool + + /// The curve function for scaling + public var scaleCurve: TransformCurve + + /// The curve function for translating + public var translationCurve: TransformCurve + + /// Set this flag to `false` if you don't want any shadow for `scalableView` + public var shadowEnabled: Bool + + /// The shadow color that be applied on `scalableView` + public var shadowColor: UIColor + + /// The shadow opacity that be applied on `scalableView` + public var shadowOpacity: Float + + /// The shadow radius that be applied on `scalableView` when scale is minimum + public var shadowRadiusMin: CGFloat + + /// The shadow radius that be applied on `scalableView` when scale is maximum + public var shadowRadiusMax: CGFloat + + /// The shadow offset that be applied on `scalableView` when scale is minimum + public var shadowOffsetMin: CGSize + + /// The shadow offset that be applied on `scalableView` when scale is maximum + public var shadowOffsetMax: CGSize + + /// The shadow opacity that be applied on `scalableView` when scale is minimum + public var shadowOpacityMin: Float + + /// The shadow opacity that be applied on `scalableView` when scale is maximum + public var shadowOpacityMax: Float + + /// Enabling the blur effect for side views + public var blurEffectEnabled: Bool + + /// The Ratio for computing blur radius, radius = progress * `burEffectRadiusRatio` + public var blurEffectRadiusRatio: CGFloat + + /// Blur effect style in case you enable it + public var blurEffectStyle: UIBlurEffect.Style + + /// 3D rotation based on CATransform3DMakeRotation(angle:CGFloat, x: CGFloat, y: CGFloat, z: CGFloat) + public var rotation3d: Rotation3dOptions? + + /// 3D translation based on CATransform3DMakeTranslation(tx: CGFloat, ty: CGFloat, tz: CGFloat) + public var translation3d: Translation3dOptions? + + + // MARK: Lifecycle + + public init( + minScale: CGFloat = 0.75, + maxScale: CGFloat = 1, + scaleRatio: CGFloat = 0.25, + translationRatio: CGPoint = .init(x: 0.93, y: 0.36), + minTranslationRatio: CGPoint? = .init(x: -5, y: -5), + maxTranslationRatio: CGPoint? = .init(x: 5, y: 5), + keepVerticalSpacingEqual: Bool = true, + keepHorizontalSpacingEqual: Bool = true, + scaleCurve: TransformCurve = .linear, + translationCurve: TransformCurve = .linear, + shadowEnabled: Bool = true, + shadowColor: UIColor = .black, + shadowOpacity: Float = 0.6, + shadowRadiusMin: CGFloat = 2, + shadowRadiusMax: CGFloat = 13, + shadowOffsetMin: CGSize = .init(width: 0, height: 2), + shadowOffsetMax: CGSize = .init(width: 0, height: 6), + shadowOpacityMin: Float = 0.1, + shadowOpacityMax: Float = 0.1, + blurEffectEnabled: Bool = false, + blurEffectRadiusRatio: CGFloat = 0.4, + blurEffectStyle: UIBlurEffect.Style = .light, + rotation3d: Rotation3dOptions? = nil, + translation3d: Translation3dOptions? = nil + ) { + self.minScale = minScale + self.maxScale = maxScale + self.scaleRatio = scaleRatio + self.translationRatio = translationRatio + self.minTranslationRatio = minTranslationRatio + self.maxTranslationRatio = maxTranslationRatio + self.keepVerticalSpacingEqual = keepVerticalSpacingEqual + self.keepHorizontalSpacingEqual = keepHorizontalSpacingEqual + self.scaleCurve = scaleCurve + self.translationCurve = translationCurve + self.shadowEnabled = shadowEnabled + self.shadowColor = shadowColor + self.shadowOpacity = shadowOpacity + self.shadowRadiusMin = shadowRadiusMin + self.shadowRadiusMax = shadowRadiusMax + self.shadowOffsetMin = shadowOffsetMin + self.shadowOffsetMax = shadowOffsetMax + self.shadowOpacityMin = shadowOpacityMin + self.shadowOpacityMax = shadowOpacityMax + self.blurEffectEnabled = blurEffectEnabled + self.blurEffectRadiusRatio = blurEffectRadiusRatio + self.blurEffectStyle = blurEffectStyle + self.rotation3d = rotation3d + self.translation3d = translation3d + } +} diff --git a/Lib/Snapshot/SnapshotContainerView.swift b/Lib/Snapshot/SnapshotContainerView.swift new file mode 100644 index 0000000..378461f --- /dev/null +++ b/Lib/Snapshot/SnapshotContainerView.swift @@ -0,0 +1,75 @@ +// +// SnapshotContainerView.swift +// CollectionViewPagingLayout +// +// Created by Amir on 07/03/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit + +public class SnapshotContainerView: UIView { + + // MARK: Properties + + public let snapshots: [UIView] + public let identifier: String + public let snapshotSize: CGSize + public let pieceSizeRatio: CGSize + + private weak var targetView: UIView? + + + // MARK: Lifecycle + + public init?(targetView: UIView, pieceSizeRatio: CGSize, identifier: String) { + var snapshots: [UIView] = [] + self.pieceSizeRatio = pieceSizeRatio + guard pieceSizeRatio.width > 0, pieceSizeRatio.height > 0 else { + return nil + } + var x: CGFloat = 0 + var y: CGFloat = 0 + var width = pieceSizeRatio.width * targetView.frame.width + var height = pieceSizeRatio.height * targetView.frame.height + if width > targetView.frame.width { + width = targetView.frame.width + } + if height > targetView.frame.height { + height = targetView.frame.height + } + while true { + if y >= targetView.frame.height { + break + } + + let frame = CGRect(x: x, y: y, width: min(width, targetView.frame.width - x), height: min(height, targetView.frame.height - y)) + if let view = targetView.resizableSnapshotView(from: frame, afterScreenUpdates: true, withCapInsets: .zero) { + view.frame = frame + snapshots.append(view) + } + x += width + if x >= targetView.frame.width { + x = 0 + y += height + } + + } + if snapshots.isEmpty { + return nil + } + self.targetView = targetView + self.identifier = identifier + self.snapshots = snapshots + snapshotSize = targetView.bounds.size + super.init(frame: targetView.frame) + snapshots.forEach { + self.addSubview($0) + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) is not supported") + } + +} diff --git a/Lib/Snapshot/SnapshotTransformView.swift b/Lib/Snapshot/SnapshotTransformView.swift new file mode 100644 index 0000000..8491414 --- /dev/null +++ b/Lib/Snapshot/SnapshotTransformView.swift @@ -0,0 +1,225 @@ +// +// SnapshotTransformView.swift +// CollectionViewPagingLayout +// +// Created by Amir on 07/03/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit + +public protocol SnapshotTransformView: TransformableView { + + /// Options for controlling the effects, see `SnapshotTransformViewOptions.swift` + var snapshotOptions: SnapshotTransformViewOptions { get } + + /// The view to apply the effect on + var targetView: UIView { get } + + /// A unique identifier for the snapshot, a new snapshot won't be made if + /// there is a cashed snapshot with the same identifier + 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 +} + + +public extension SnapshotTransformView where Self: UICollectionViewCell { + + /// Default `targetView` for `UICollectionViewCell` is the first subview of + /// `contentView` or the content view itself in case of no subviews + var targetView: UIView { + contentView.subviews.first ?? contentView + } + + /// Default `identifier` for `UICollectionViewCell` is it's index + /// if you have the same content with different indexes (like an infinite list) + /// you should override this and provide a content-based identifier + var snapshotIdentifier: String { + var collectionView: UICollectionView? + var superview = self.superview + while superview != nil { + if let view = superview as? UICollectionView { + collectionView = view + break + } + superview = superview?.superview + } + 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 + } +} + + +public extension SnapshotTransformView { + + // MARK: Properties + + var snapshotOptions: SnapshotTransformViewOptions { + .init() + } + + // MARK: TransformableView + + func transform(progress: CGFloat) { + guard let snapshot = getSnapshot() else { + return + } + applySnapshotTransform(snapshot: snapshot, progress: progress) + } + + + // MARK: Public functions + + func getSnapshot() -> SnapshotContainerView? { + findSnapshot() ?? makeSnapshot() + } + + func applySnapshotTransform(snapshot: SnapshotContainerView, progress: CGFloat) { + if progress == 0 { + targetView.transform = .identity + snapshot.alpha = 0 + } else { + snapshot.alpha = 1 + // hide the original view, we apply transform on the snapshot + targetView.transform = CGAffineTransform.identity.translatedBy(x: 0, y: -2 * UIScreen.main.bounds.height) + } + snapshot.transform(progress: progress, options: snapshotOptions) + } + + + // 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? { + 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? { + 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) + return view + } + +} + + +private extension SnapshotContainerView { + + func transform(progress: CGFloat, options: SnapshotTransformViewOptions) { + let scale = max(1 - abs(progress) * options.containerScaleRatio, 0) + var translateX = progress * frame.width * options.containerTranslationRatio.x + var translateY = progress * frame.height * options.containerTranslationRatio.y + if let min = options.containerMinTranslationRatio { + translateX = max(translateX, frame.width * min.x) + translateY = max(translateX, frame.width * min.y) + } + if let max = options.containerMaxTranslationRatio { + translateX = min(translateX, frame.width * max.x) + translateY = min(translateY, frame.height * max.y) + } + + transform = CGAffineTransform.identity + .translatedBy(x: translateX, + y: translateY) + .scaledBy(x: scale, y: scale) + var sizeRatioRow = options.pieceSizeRatio.height + if abs(sizeRatioRow) < 0.01 { + sizeRatioRow = 0.01 + } + var sizeRatioColumn = options.pieceSizeRatio.width + if abs(sizeRatioColumn) < 0.01 { + sizeRatioColumn = 0.01 + } + let rowCount = Int(1.0 / sizeRatioRow) + let columnCount = Int(1.0 / sizeRatioColumn) + + snapshots.enumerated().forEach { index, view in + let position = SnapshotTransformViewOptions.PiecePosition( + index: index, + row: Int(index / columnCount), + column: Int(index % columnCount), + rowCount: rowCount, + columnCount: columnCount + ) + + let pieceScale = abs(progress) * options.piecesScaleRatio.getRatio(position: position) + let pieceTransform = options.piecesTranslationRatio.getRatio(position: position) * abs(progress) + let minPieceTransform = options.minPiecesTranslationRatio?.getRatio(position: position) + let maxPieceTransform = options.maxPiecesTranslationRatio?.getRatio(position: position) + var translateX = pieceTransform.x * view.frame.width + var translateY = pieceTransform.y * view.frame.height + + if let min = minPieceTransform { + translateX = max(translateX, view.frame.width * min.x) + translateY = max(translateY, view.frame.height * min.y) + } + if let max = maxPieceTransform { + translateX = min(translateX, view.frame.width * max.x) + translateY = min(translateY, view.frame.height * max.y) + } + + view.transform = CGAffineTransform.identity + .translatedBy(x: translateX, y: translateY) + .scaledBy(x: max(0, 1 - pieceScale.width), y: max(0, 1 - pieceScale.height)) + view.alpha = 1 - options.piecesAlphaRatio.getRatio(position: position) * abs(progress) + view.layer.cornerRadius = options.piecesCornerRadiusRatio.getRatio(position: position) * abs(progress) * min(view.frame.height, view.frame.width) + view.layer.masksToBounds = true + } + } +} diff --git a/Lib/Snapshot/SnapshotTransformViewOptions+Layout.swift b/Lib/Snapshot/SnapshotTransformViewOptions+Layout.swift new file mode 100644 index 0000000..1c2b8d9 --- /dev/null +++ b/Lib/Snapshot/SnapshotTransformViewOptions+Layout.swift @@ -0,0 +1,108 @@ +// +// SnapshotTransformViewOptions+Layout.swift +// CollectionViewPagingLayout +// +// Created by Amir on 28/03/2021. +// Copyright © 2021 Amir Khorsandi. All rights reserved. +// + +import UIKit +import Foundation + +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), + piecesTranslationRatio: .aggregated([.rowBasedMirror(CGPoint(x: 0, y: -1.8)), .columnBasedMirror(CGPoint(x: -1.8, y: 0))], +), + piecesScaleRatio: .static(.init(width: 0.8, height: 0.8)), + containerScaleRatio: 0.1, + containerTranslationRatio: .init(x: 0.7, y: 0) + ) + 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)], +), + piecesTranslationRatio: .aggregated([.rowBasedMirror(CGPoint(x: 1, y: -1)), .columnBasedMirror(CGPoint(x: -1, y: 1))], *), + piecesScaleRatio: .static(.init(width: 0.5, height: 0.5)), + containerScaleRatio: 0.1, + containerTranslationRatio: .init(x: 0.7, y: 0) + ) + case .chess: + return Self( + pieceSizeRatio: .init(width: 1.0 / 5.0, height: 1.0 / 10.0), + piecesCornerRadiusRatio: .static(0.5), + piecesAlphaRatio: .columnBasedMirror(0.4), + piecesTranslationRatio: .columnBasedMirror(CGPoint(x: -1, y: 1)), + piecesScaleRatio: .static(.init(width: 0.5, height: 0.5)), + containerScaleRatio: 0.1, + containerTranslationRatio: .init(x: 0.7, y: 0) + ) + case .tiles: + return Self( + pieceSizeRatio: .init(width: 1, height: 1.0 / 10.0), + piecesCornerRadiusRatio: .static(0), + piecesAlphaRatio: .static(0.4), + piecesTranslationRatio: .rowOddEven(CGPoint(x: -0.4, y: 0), CGPoint(x: 0.4, y: 0)), + piecesScaleRatio: .static(.init(width: 0, height: 0.1)), + containerScaleRatio: 0.1, + containerTranslationRatio: .init(x: 1, y: 0) + ) + case .lines: + return Self( + pieceSizeRatio: .init(width: 1, height: 1.0 / 16.0), + piecesCornerRadiusRatio: .static(0), + piecesAlphaRatio: .static(0.4), + piecesTranslationRatio: .rowOddEven(CGPoint(x: -0.15, y: 0), CGPoint(x: 0.15, y: 0)), + piecesScaleRatio: .static(.init(width: 0.6, height: 0.96)), + containerScaleRatio: 0.1, + containerTranslationRatio: .init(x: 0.8, y: 0) + ) + case .bars: + return Self( + pieceSizeRatio: .init(width: 1.0 / 10.0, height: 1), + piecesCornerRadiusRatio: .static(1.2), + piecesAlphaRatio: .static(0.4), + piecesTranslationRatio: .columnOddEven(CGPoint(x: 0, y: -0.1), CGPoint(x: 0, y: 0.1)), + piecesScaleRatio: .static(.init(width: 0.2, height: 0.6)), + containerScaleRatio: 0.1, + containerTranslationRatio: .init(x: 1, y: 0) + ) + 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)], +), + piecesTranslationRatio: .rowOddEven(CGPoint(x: -0.15, y: 0), CGPoint(x: 0.15, y: 0)), + piecesScaleRatio: .columnOddEven(.init(width: 0.1, height: 0.4), .init(width: 0.4, height: 0.1)), + containerScaleRatio: 0.2, + containerTranslationRatio: .init(x: 1, y: 0) + ) + case .fade: + return Self( + pieceSizeRatio: .init(width: 1, height: 1.0 / 10.0), + piecesCornerRadiusRatio: .static(0.1), + piecesAlphaRatio: .rowBased(0.1), + piecesTranslationRatio: .rowBasedMirror(CGPoint(x: 0, y: 0.1)), + piecesScaleRatio: .rowBasedMirror(.init(width: 0.05, height: 0.1)), + containerScaleRatio: 0.7, + containerTranslationRatio: .init(x: 1.9, y: 0) + ) + } + } +} diff --git a/Lib/Snapshot/SnapshotTransformViewOptions.PiecePosition.swift b/Lib/Snapshot/SnapshotTransformViewOptions.PiecePosition.swift new file mode 100644 index 0000000..0333704 --- /dev/null +++ b/Lib/Snapshot/SnapshotTransformViewOptions.PiecePosition.swift @@ -0,0 +1,35 @@ +// +// SnapshotTransformViewOptions.PiecePosition.swift +// CollectionViewPagingLayout +// +// Created by Amir on 27/03/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit + +public extension SnapshotTransformViewOptions { + + struct PiecePosition { + + // MARK: Properties + + let index: Int + let row: Int + let column: Int + let rowCount: Int + let columnCount: Int + + + // MARK: Lifecycle + + internal init(index: Int, row: Int, column: Int, rowCount: Int, columnCount: Int) { + self.index = index + self.row = row + self.column = column + self.rowCount = rowCount + self.columnCount = columnCount + } + } + +} diff --git a/Lib/Snapshot/SnapshotTransformViewOptions.PiecesValue.swift b/Lib/Snapshot/SnapshotTransformViewOptions.PiecesValue.swift new file mode 100644 index 0000000..4310611 --- /dev/null +++ b/Lib/Snapshot/SnapshotTransformViewOptions.PiecesValue.swift @@ -0,0 +1,101 @@ +// +// SnapshotTransformViewOptions.PiecesValue.swift +// CollectionViewPagingLayout +// +// Created by Amir on 27/03/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit + +public extension SnapshotTransformViewOptions { + + enum PiecesValue { + + // MARK: Cases + + case columnBased(Type, reversed: Bool = false) + case rowBased(Type, reversed: Bool = false) + case columnOddEven(Type, Type, increasing: Bool = false) + case rowOddEven(Type, Type, increasing: Bool = false) + case columnBasedMirror(Type, reversed: Bool = false) + case rowBasedMirror(Type, reversed: Bool = false) + case indexBasedCustom([Type]) + case rowBasedCustom([Type]) + case columnBasedCustom([Type]) + case `static`(Type) + case aggregated([PiecesValue], (Type, Type) -> Type) + + + // MARK: Public functions + + func getRatio(position: PiecePosition) -> Type { + switch self { + case .columnBased(let ratio, let reversed): + return getRowColumnBased(ratio: ratio, count: position.columnCount, current: position.column, reversed: reversed) + case .rowBased(let ratio, let reversed): + return getRowColumnBased(ratio: ratio, count: position.rowCount, current: position.row, reversed: reversed) + case .columnOddEven(let oddRatio, let evenRatio, let increasing): + return (position.column % 2 == 0 ? evenRatio : oddRatio) * (increasing ? CGFloat(position.column) : 1) + case .rowOddEven(let oddRatio, let evenRatio, let increasing): + return (position.row % 2 == 0 ? evenRatio : oddRatio) * (increasing ? CGFloat(position.row) : 1) + case .indexBasedCustom(let ratios): + return ratios[position.index % ratios.count] + case .rowBasedCustom(let ratios): + return ratios[position.row % ratios.count] + case .columnBasedCustom(let ratios): + return ratios[position.column % ratios.count] + case .static(let ratio): + return ratio + case .columnBasedMirror(let ratio, let reversed): + return getRowColumnBasedMirror(ratio: ratio, count: position.columnCount, current: position.column, reversed: reversed) + case .rowBasedMirror(let ratio, let reversed): + return getRowColumnBasedMirror(ratio: ratio, count: position.rowCount, current: position.row, reversed: reversed) + case .aggregated(let values, let nextPartialResult): + guard !values.isEmpty else { + fatalError("aggregate array is empty") + } + let result = values.map { + $0.getRatio(position: position) + } + return result.dropFirst().reduce(result.first!, nextPartialResult) + } + } + + + // MARK: Private functions + + private func getRowColumnBased(ratio: Type, count: Int, current: Int, reversed: Bool) -> Type { + if reversed { + return ratio * CGFloat(count - current - 1) + } else { + return ratio * CGFloat(current) + } + } + + private func getRowColumnBasedMirror(ratio: Type, count: Int, current: Int, reversed: Bool) -> Type { + let middle = Int(count / 2) + if count % 2 == 1, current == middle { + return ratio * 0 + } + var index = current + if index >= middle { + index -= middle + if count % 2 == 0 { + index += 1 + } + } else { + index = middle - index + } + if reversed { + index = middle - index + } + var floatIndex = CGFloat(index) + if count % 2 == 0 { + floatIndex -= 0.5 + } + return ratio * floatIndex * (current >= middle ? 1 : -1) + } + + } +} diff --git a/Lib/Snapshot/SnapshotTransformViewOptions.swift b/Lib/Snapshot/SnapshotTransformViewOptions.swift new file mode 100644 index 0000000..265418d --- /dev/null +++ b/Lib/Snapshot/SnapshotTransformViewOptions.swift @@ -0,0 +1,84 @@ +// +// SnapshotTransformViewOptions.swift +// CollectionViewPagingLayout +// +// Created by Amir on 15/03/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit + +public struct SnapshotTransformViewOptions { + + // MARK: Properties + + /// Ratio for computing the size of each piece in the snapshot + /// width = view.width * `pieceSizeRatio.width` + public var pieceSizeRatio: CGSize + + /// Ratio for computing the size of each piece in the snapshot + public var piecesCornerRadiusRatio: PiecesValue + + /// Ratio for computing the opacity of each piece in the snapshot + /// 0 means no opacity change 1 means full opacity change + public var piecesAlphaRatio: PiecesValue + + /// Ratio for the amount of translate for each piece in the snapshot, calculates by each piece size + /// for instance, if piecesTranslationRatio.x = 0.5 and pieceView.width = 100 then + /// translateX = 50 for the pieceView + public var piecesTranslationRatio: PiecesValue + + /// Ratio for the minimum amount of translate for each piece, calculates like `piecesTranslationRatio` + public var minPiecesTranslationRatio: PiecesValue? + + /// Ratio for the maximum amount of translate for each piece, calculates like `piecesTranslationRatio` + public var maxPiecesTranslationRatio: PiecesValue? + + /// Ratio for computing scale of each piece in the snapshot + /// Scale = 1 - abs(progress) * `piecesScaleRatio` + public var piecesScaleRatio: PiecesValue + + /// Ratio for computing scale for the snapshot container + /// Scale = 1 - abs(progress) * `scaleRatio` + public var containerScaleRatio: CGFloat + + /// Ratio for the amount of translate for container view, calculates by `targetView` size + /// for instance, if containerTranslationRatio.x = 0.5 and targetView.width = 100 then + /// translateX = 50 for the right view and translateX = -50 for the left view + public var containerTranslationRatio: CGPoint + + /// The minimum amount of translate for container views, calculates like `containerTranslationRatio` + public var containerMinTranslationRatio: CGPoint? + + /// The maximum amount of translate for container views, calculates like `containerTranslationRatio` + public var containerMaxTranslationRatio: CGPoint? + + + // MARK: Lifecycle + + public init( + pieceSizeRatio: CGSize = .init(width: 1, height: 1.0 / 8.0), + piecesCornerRadiusRatio: PiecesValue = .static(0), + piecesAlphaRatio: PiecesValue = .static(0), + piecesTranslationRatio: PiecesValue = .rowOddEven(.init(x: 0, y: -1), .init(x: -1, y: -1)), + minPiecesTranslationRatio: PiecesValue? = nil, + maxPiecesTranslationRatio: PiecesValue? = nil, + piecesScaleRatio: PiecesValue = .static(.init(width: 0, height: 1)), + containerScaleRatio: CGFloat = 0.25, + containerTranslationRatio: CGPoint = .init(x: 1, y: 0), + containerMinTranslationRatio: CGPoint? = nil, + containerMaxTranslationRatio: CGPoint? = nil + ) { + self.pieceSizeRatio = pieceSizeRatio + self.piecesCornerRadiusRatio = piecesCornerRadiusRatio + self.piecesAlphaRatio = piecesAlphaRatio + self.piecesTranslationRatio = piecesTranslationRatio + self.minPiecesTranslationRatio = minPiecesTranslationRatio + self.maxPiecesTranslationRatio = maxPiecesTranslationRatio + self.piecesScaleRatio = piecesScaleRatio + self.containerScaleRatio = containerScaleRatio + self.containerTranslationRatio = containerTranslationRatio + self.containerMinTranslationRatio = containerMinTranslationRatio + self.containerMaxTranslationRatio = containerMaxTranslationRatio + } +} diff --git a/Lib/Stack/StackTransformView.swift b/Lib/Stack/StackTransformView.swift new file mode 100644 index 0000000..aa8e4e8 --- /dev/null +++ b/Lib/Stack/StackTransformView.swift @@ -0,0 +1,207 @@ +// +// StackTransformView.swift +// CollectionViewPagingLayout +// +// Created by Amir on 21/02/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit + +/// A protocol for adding stack transformation effect to `TransformableView` +public protocol StackTransformView: TransformableView { + + /// Options for controlling stack effects, see `StackTransformViewOptions.swift` + var stackOptions: StackTransformViewOptions { get } + + /// The view to apply scale effect on + var cardView: UIView { get } + + /// The view to apply blur effect on + var stackBlurViewHost: UIView { get } + + /// the main function for applying transforms + func applyStackTransform(progress: CGFloat) +} + + +public extension StackTransformView { + + /// The default value is the super view of `cardView` + var stackBlurViewHost: UIView { + cardView.superview ?? cardView + } + +} + + +public extension StackTransformView where Self: UICollectionViewCell { + + /// Default `cardView` for `UICollectionViewCell` is the first subview of + /// `contentView` or the content view itself in case of no subviews + var cardView: UIView { + contentView.subviews.first ?? contentView + } +} + + +public extension StackTransformView { + + // MARK: Properties + + var stackOptions: StackTransformViewOptions { + .init() + } + + + // MARK: TransformableView + + func transform(progress: CGFloat) { + applyStackTransform(progress: progress) + } + + func zPosition(progress: CGFloat) -> Int { + var zPosition = -Int(round(progress)) + if stackOptions.reverse { + zPosition *= -1 + } + return zPosition + } + + + // MARK: Public functions + + func applyStackTransform(progress: CGFloat) { + var progress = progress + if stackOptions.reverse { + progress *= -1 + } + applyStyle(progress: progress) + applyScale(progress: progress) + applyAlpha(progress: progress) + applyRotation(progress: progress) + if #available(iOS 10, *) { + applyBlurEffect(progress: progress) + } + } + + + // MARK: Private functions + + private func applyStyle(progress: CGFloat) { + guard stackOptions.shadowEnabled else { + return + } + let layer = cardView.layer + layer.shadowColor = stackOptions.shadowColor.cgColor + layer.shadowOffset = stackOptions.shadowOffset + layer.shadowRadius = stackOptions.shadowRadius + layer.shadowOpacity = stackOptions.shadowOpacity + } + + private func applyScale(progress: CGFloat) { + var transform = CGAffineTransform.identity + var xAdjustment: CGFloat = 0 + var yAdjustment: CGFloat = 0 + + var scale = 1 - progress * stackOptions.scaleFactor + if let minScale = stackOptions.minScale { + scale = max(minScale, scale) + } + if let maxScale = stackOptions.maxScale { + scale = min(maxScale, scale) + } + + let stackProgress = progress.interpolate(in: .init(0, CGFloat(stackOptions.maxStackSize))) + let perspectiveProgress = TransformCurve.easeOut.computeFromLinear(progress: stackProgress) * stackOptions.perspectiveRatio + + + var xSpacing = cardView.bounds.width * stackOptions.spacingFactor + if let max = stackOptions.maxSpacing { + xSpacing = min(xSpacing, cardView.bounds.width * max) + } + let translateX = xSpacing * -max(progress, 0) * -stackOptions.stackPosition.x + + var ySpacing = cardView.bounds.height * stackOptions.spacingFactor + if let max = stackOptions.maxSpacing { + ySpacing = min(ySpacing, cardView.bounds.height * max) + } + let translateY = ySpacing * -max(progress, 0) * -stackOptions.stackPosition.y + + yAdjustment = ((scale - 1) * cardView.bounds.height) / 2 // make y equal for all cards + yAdjustment += perspectiveProgress * cardView.bounds.height + yAdjustment *= -stackOptions.stackPosition.y + + xAdjustment = ((scale - 1) * cardView.bounds.width) / 2 // make x equal for all cards + xAdjustment += perspectiveProgress * cardView.bounds.width + xAdjustment *= -stackOptions.stackPosition.x + + + if progress < 0 { + xAdjustment -= cardView.bounds.width * stackOptions.popOffsetRatio.width * progress + yAdjustment -= cardView.bounds.height * stackOptions.popOffsetRatio.height * progress + } + + transform = transform + .translatedBy(x: translateX + xAdjustment, y: translateY + yAdjustment) + .scaledBy(x: scale, y: scale) + cardView.transform = transform + } + + private func applyAlpha(progress: CGFloat) { + cardView.alpha = 1 + + let floatStackSize = CGFloat(stackOptions.maxStackSize) + if progress >= floatStackSize - 1 { + let targetCard = floatStackSize - 1 + cardView.alpha = 1 - progress.interpolate( + in: .init(targetCard, targetCard + stackOptions.bottomStackAlphaSpeedFactor) + ) + } else if progress < 0 { + cardView.alpha = progress.interpolate(in: .init(-1, -1 + stackOptions.topStackAlphaSpeedFactor)) + } + + if cardView.alpha > 0, progress >= 0 { + cardView.alpha -= progress * stackOptions.alphaFactor + } + + } + + private func applyRotation(progress: CGFloat) { + var angle: CGFloat = 0 + if progress <= 0 { + angle = -abs(progress).interpolate(out: .init(0, abs(stackOptions.popAngle))) + if stackOptions.popAngle < 0 { + angle *= -1 + } + } else { + let floatAmount = abs(progress - CGFloat(Int(progress))) + angle = -floatAmount * stackOptions.stackRotateAngel * 2 + stackOptions.stackRotateAngel + if Int(progress) % 2 == 0 { + angle *= -1 + } + if progress < 1 { + angle += (1 - progress).interpolate(out: .init(0, stackOptions.stackRotateAngel)) + } + } + + cardView.transform = cardView.transform.rotated(by: angle) + } + + private func applyBlurEffect(progress: CGFloat) { + guard stackOptions.maxBlurEffectRadius > 0, stackOptions.blurEffectEnabled else { + stackBlurViewHost.subviews.first(where: { $0 is BlurEffectView })?.removeFromSuperview() + return + } + let blurView: BlurEffectView + if let view = stackBlurViewHost.subviews.first(where: { $0 is BlurEffectView }) as? BlurEffectView { + blurView = view + } else { + blurView = BlurEffectView() + 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/Lib/Stack/StackTransformViewOptions+Layout.swift b/Lib/Stack/StackTransformViewOptions+Layout.swift new file mode 100644 index 0000000..35a7490 --- /dev/null +++ b/Lib/Stack/StackTransformViewOptions+Layout.swift @@ -0,0 +1,106 @@ +// +// StackTransformViewOptions+Layout.swift +// CollectionViewPagingLayout +// +// Created by Amir on 28/03/2021. +// Copyright © 2021 Amir Khorsandi. All rights reserved. +// + +import UIKit +import Foundation + +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, + alphaFactor: 0.2, + bottomStackAlphaSpeedFactor: 10, + topStackAlphaSpeedFactor: 0.1, + popAngle: .pi / 10, + popOffsetRatio: .init(width: -1.45, height: 0.3) + ) + case .perspective: + return Self( + scaleFactor: 0.1, + minScale: 0.2, + maxStackSize: 6, + spacingFactor: 0.08, + alphaFactor: 0.0, + perspectiveRatio: 0.3, + shadowRadius: 5, + popAngle: .pi / 10, + popOffsetRatio: .init(width: -1.45, height: 0.3), + stackPosition: CGPoint(x: 1, y: 0) + ) + case .rotary: + return Self( + scaleFactor: -0.03, + minScale: 0.2, + maxStackSize: 3, + spacingFactor: 0.01, + alphaFactor: 0.1, + shadowRadius: 8, + stackRotateAngel: .pi / 16, + popAngle: .pi / 4, + popOffsetRatio: .init(width: -1.45, height: 0.4), + stackPosition: CGPoint(x: 0, y: 1) + ) + case .vortex: + return Self( + scaleFactor: -0.15, + minScale: 0.2, + maxScale: nil, + maxStackSize: 4, + spacingFactor: 0, + alphaFactor: 0.4, + topStackAlphaSpeedFactor: 1, + perspectiveRatio: -0.3, + shadowEnabled: false, + popAngle: .pi, + popOffsetRatio: .zero, + stackPosition: CGPoint(x: 0, y: 1) + ) + case .reverse: + return Self( + scaleFactor: 0.1, + maxScale: nil, + maxStackSize: 4, + spacingFactor: 0.08, + shadowRadius: 8, + popAngle: -.pi / 4, + popOffsetRatio: .init(width: 1.45, height: 0.4), + stackPosition: CGPoint(x: -1, y: -0.2), + reverse: true + ) + case .blur: + return Self( + scaleFactor: 0.1, + maxScale: nil, + maxStackSize: 7, + spacingFactor: 0.06, + topStackAlphaSpeedFactor: 0.1, + perspectiveRatio: 0.04, + shadowRadius: 8, + popAngle: -.pi / 4, + popOffsetRatio: .init(width: 1.45, height: 0.4), + stackPosition: CGPoint(x: -1, y: 0), + reverse: true, + blurEffectEnabled: true, + maxBlurEffectRadius: 0.08 + ) + } + } +} diff --git a/Lib/Stack/StackTransformViewOptions.swift b/Lib/Stack/StackTransformViewOptions.swift new file mode 100644 index 0000000..fd91606 --- /dev/null +++ b/Lib/Stack/StackTransformViewOptions.swift @@ -0,0 +1,152 @@ +// +// StackTransformViewOptions.swift +// CollectionViewPagingLayout +// +// Created by Amir on 21/02/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit + +public struct StackTransformViewOptions { + + // MARK: Properties + + /// The scale factor for computing scale of each card, the scale for the top card is 1 + /// and the scale for the card just below top card is (1 - scaleFactor) and so on + public var scaleFactor: CGFloat + + /// The minumum scale for each card, see `scaleFactor` + public var minScale: CGFloat? + + /// The maximum scale for each card, see `scaleFactor` + public var maxScale: CGFloat? + + /// The maximum number of visible cards in the stack + public var maxStackSize: Int + + /// the spacing factor for card positions, computing like this: + /// cardY = cardHeight * spacingFactor * progress + public var spacingFactor: CGFloat + + /// the maxium spacing for the card position, see `spacingFactor` + public var maxSpacing: CGFloat? + + /// the alpha factor for the card alpha, the alpha for the topest card will be 1 + /// and the alpha for the card just below the top card is (1 - alphaFactor) and so on + /// alpha = 1 - progress * alphaFactor + public var alphaFactor: CGFloat + + /// the most bottom card of the stack will be fading out with this speed + /// between 0...1, if you want it to be slow set it to 1 + public var bottomStackAlphaSpeedFactor: CGFloat + + /// line `bottomStackAlphaSpeedFactor` but for the toppest card + public var topStackAlphaSpeedFactor: CGFloat + + /// if you want to have a prespective view on you stack you can set this value + /// to greater that zero, that means spacing factor will be multiplied to this + /// value for each card + public var perspectiveRatio: CGFloat + + /// If you want to have shadow blew each card set this value to true + public var shadowEnabled: Bool + + /// the shadow color, will be applied if `shadowEnabled` set to true + public var shadowColor: UIColor + + /// the shadow opacity, will be applied if `shadowEnabled` set to true + public var shadowOpacity: Float + + /// the shadow offset, will be applied if `shadowEnabled` set to true + public var shadowOffset: CGSize + + /// the shadow radius, will be applied if `shadowEnabled` set to true + public var shadowRadius: CGFloat + + /// the angle for rotating the stack cards, it works like even-odd + /// the topest card angle would be zero, the next rotates `stackRotateAngel` + /// and the next open rotates -`stackRotateAngel` and so on + public var stackRotateAngel: CGFloat + + /// Maximum angle for poping the topest card, starts from zero up to this angle + public var popAngle: CGFloat + + /// Offset for poping the topest card, you can set width or height ratio + /// cardX -= cardWidth * popOffsetRatio.width * progress + public var popOffsetRatio: CGSize + + /// A point for adjusting the position of card in the stack + /// for instance if you want to set the stack position to the top you + /// should set this value to x:0, y:-1, for bottom x:0, y:1, + /// and set x for left and right, you can combine them too + public var stackPosition: CGPoint + + /// if you want to drop cards from stack by scrolling left to right or top to bottom + /// you can use this flag, you also need to manually set the conect offset of the collection view + /// to the content size since you want to scroll from the end of collection view + /// https://stackoverflow.com/questions/13958543/uicollectionview-scroll-right-to-left-reverse + public var reverse: Bool + + /// Enable this flag if you want to have blur effect, the farest card will be more blury + public var blurEffectEnabled: Bool + + /// Maxium radius for blur effect, will be applied if `blurEffectEnabled` set to true + public var maxBlurEffectRadius: CGFloat + + /// Blur style for blur effect, will be applied if `blurEffectEnabled` set to true + public var blurEffectStyle: UIBlurEffect.Style + + + // MARK: Lifecycle + + public init( + scaleFactor: CGFloat = 0.15, + minScale: CGFloat? = 0, + maxScale: CGFloat? = 1, + maxStackSize: Int = 3, + spacingFactor: CGFloat = 0.03, + maxSpacing: CGFloat? = nil, + alphaFactor: CGFloat = 0.0, + bottomStackAlphaSpeedFactor: CGFloat = 0.9, + topStackAlphaSpeedFactor: CGFloat = 0.3, + perspectiveRatio: CGFloat = 0, + shadowEnabled: Bool = true, + shadowColor: UIColor = .black, + shadowOpacity: Float = 0.1, + shadowOffset: CGSize = .zero, + shadowRadius: CGFloat = 10, + stackRotateAngel: CGFloat = 0, + popAngle: CGFloat = .pi / 7, + popOffsetRatio: CGSize = .init(width: -1.3, height: 0.3), + stackPosition: CGPoint = CGPoint(x: 0, y: -1), + reverse: Bool = false, + blurEffectEnabled: Bool = false, + maxBlurEffectRadius: CGFloat = 0.0, + blurEffectStyle: UIBlurEffect.Style = .light + ) { + self.scaleFactor = scaleFactor + self.minScale = minScale + self.maxScale = maxScale + self.maxStackSize = maxStackSize + self.spacingFactor = spacingFactor + self.maxSpacing = maxSpacing + self.alphaFactor = alphaFactor + self.bottomStackAlphaSpeedFactor = bottomStackAlphaSpeedFactor + self.topStackAlphaSpeedFactor = topStackAlphaSpeedFactor + self.perspectiveRatio = perspectiveRatio + self.shadowEnabled = shadowEnabled + self.shadowColor = shadowColor + self.shadowOpacity = shadowOpacity + self.shadowOffset = shadowOffset + self.shadowRadius = shadowRadius + self.stackRotateAngel = stackRotateAngel + self.popAngle = popAngle + self.popOffsetRatio = popOffsetRatio + self.stackPosition = stackPosition + self.reverse = reverse + self.blurEffectEnabled = blurEffectEnabled + self.maxBlurEffectRadius = maxBlurEffectRadius + self.blurEffectStyle = blurEffectStyle + } +} 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/TransformCurve.swift b/Lib/TransformCurve.swift new file mode 100644 index 0000000..7b4c2c4 --- /dev/null +++ b/Lib/TransformCurve.swift @@ -0,0 +1,42 @@ +// +// TransformCurve.swift +// CollectionViewPagingLayout +// +// Created by Amir Khorsandi on 2/16/20. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit + +/// Curve function type for transforming +public enum TransformCurve { + case linear + case easeIn + case easeOut +} + + +public extension TransformCurve { + + /// Converting linear progress to curve progress + /// input and output are between 0 and 1 + /// + /// - Parameter progress: the current linear progress + /// - Returns: Curved progress based on self + func computeFromLinear(progress: CGFloat) -> CGFloat { + switch self { + case .linear: + return progress + case .easeIn, .easeOut: + let logValue = progress * 9 + let value: CGFloat + if self == .easeOut { + value = 1 - log10(1 + (9 - logValue)) + } else { + value = log10(1 + logValue) + } + return value + } + } + +} diff --git a/Lib/TransformableView.swift b/Lib/TransformableView.swift new file mode 100644 index 0000000..f1217d1 --- /dev/null +++ b/Lib/TransformableView.swift @@ -0,0 +1,67 @@ +// +// TransformableView.swift +// CollectionViewPagingLayout +// +// Created by Amir Khorsandi on 12/26/19. +// Copyright © 2019 Amir Khorsandi. All rights reserved. +// + +import Foundation +import UIKit + +public protocol TransformableView { + + /// 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) + + /// Optional function for providing the Z index(position) of the cell view + /// As defined as an extension the default value of zIndex is Int(-abs(round(progress))) + /// + /// - Parameter progress: the interpolated progress for the cell view + /// - Returns: the z index(position) + func zPosition(progress: CGFloat) -> Int +} + + +public extension TransformableView { + + /// Defining the default value of zIndex + func zPosition(progress: CGFloat) -> Int { + Int(-abs(round(progress))) + } +} + + +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 + } +} + + +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/Utilities/CGFloat+Interpolate.swift b/Lib/Utilities/CGFloat+Interpolate.swift new file mode 100644 index 0000000..208ce26 --- /dev/null +++ b/Lib/Utilities/CGFloat+Interpolate.swift @@ -0,0 +1,24 @@ +// +// CGFloat+Interpolate.swift +// CollectionViewPagingLayout +// +// Created by Amir Khorsandi on 2/22/20. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit + +extension CGFloat { + + public func interpolate(in inputRange: Range = .init(0, 1), out outputRange: Range = .init(0, 1)) -> CGFloat { + var current = self + if current > inputRange.upper { + current = inputRange.upper + } + if current < inputRange.lower { + current = inputRange.lower + } + let progress = abs(current - inputRange.lower) / abs(inputRange.upper - inputRange.lower) + return outputRange.lower + progress * abs(outputRange.upper - outputRange.lower) + } +} diff --git a/Lib/Utilities/CGFloat+Range.swift b/Lib/Utilities/CGFloat+Range.swift new file mode 100644 index 0000000..00840a6 --- /dev/null +++ b/Lib/Utilities/CGFloat+Range.swift @@ -0,0 +1,24 @@ +// +// CGFloat+Range.swift +// CollectionViewPagingLayout +// +// Created by Amir Khorsandi on 2/22/20. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit + +extension CGFloat { + + public struct Range { + + let lower: CGFloat + let upper: CGFloat + + public init(_ lower: CGFloat, _ upper: CGFloat) { + self.lower = lower + self.upper = upper + } + } + +} diff --git a/Lib/Utilities/Multipliable.swift b/Lib/Utilities/Multipliable.swift new file mode 100644 index 0000000..f1e6da3 --- /dev/null +++ b/Lib/Utilities/Multipliable.swift @@ -0,0 +1,79 @@ +// +// Multipliable.swift +// CollectionViewPagingLayout +// +// Created by Amir on 15/03/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit + +public protocol MultipliableToCGFloat { + static func * (rhs: Self, lhs: CGFloat) -> Self + static func * (rhs: CGFloat, lhs: Self) -> Self +} + +public protocol SummableToCGFloat { + static func + (rhs: Self, lhs: CGFloat) -> Self + static func + (rhs: CGFloat, lhs: Self) -> Self +} + +public protocol MultipliableToSelf { + static func * (rhs: Self, lhs: Self) -> Self +} + +public protocol SummableToSelf { + static func + (rhs: Self, lhs: Self) -> Self +} + +extension CGFloat: MultipliableToCGFloat, MultipliableToSelf, SummableToCGFloat, SummableToSelf {} + + +extension CGPoint: MultipliableToCGFloat, MultipliableToSelf { + public static func * (rhs: CGFloat, lhs: CGPoint) -> CGPoint { + lhs * rhs + } + public static func * (rhs: CGPoint, lhs: CGFloat) -> CGPoint { + CGPoint(x: rhs.x * lhs, y: rhs.y * lhs) + } + public static func * (rhs: CGPoint, lhs: CGPoint) -> CGPoint { + CGPoint(x: rhs.x * lhs.x, y: rhs.y * lhs.y) + } +} + +extension CGPoint: SummableToCGFloat, SummableToSelf { + public static func + (rhs: CGFloat, lhs: CGPoint) -> CGPoint { + lhs + rhs + } + public static func + (rhs: CGPoint, lhs: CGFloat) -> CGPoint { + CGPoint(x: rhs.x + lhs, y: rhs.y + lhs) + } + public static func + (rhs: CGPoint, lhs: CGPoint) -> CGPoint { + CGPoint(x: rhs.x + lhs.x, y: rhs.y + lhs.y) + } +} + + +extension CGSize: MultipliableToCGFloat, MultipliableToSelf { + public static func * (rhs: CGFloat, lhs: CGSize) -> CGSize { + lhs * rhs + } + public static func * (rhs: CGSize, lhs: CGFloat) -> CGSize { + CGSize(width: rhs.width * lhs, height: rhs.height * lhs) + } + public static func * (rhs: CGSize, lhs: CGSize) -> CGSize { + CGSize(width: rhs.width * lhs.width, height: rhs.height * lhs.height) + } +} + +extension CGSize: SummableToCGFloat, SummableToSelf { + public static func + (rhs: CGFloat, lhs: CGSize) -> CGSize { + lhs + rhs + } + public static func + (rhs: CGSize, lhs: CGFloat) -> CGSize { + CGSize(width: rhs.width + lhs, height: rhs.height + lhs) + } + public static func + (rhs: CGSize, lhs: CGSize) -> CGSize { + CGSize(width: rhs.width + lhs.width, height: rhs.height + lhs.height) + } +} diff --git a/Lib/Utilities/UIView+Utilities.swift b/Lib/Utilities/UIView+Utilities.swift new file mode 100644 index 0000000..ed8db9f --- /dev/null +++ b/Lib/Utilities/UIView+Utilities.swift @@ -0,0 +1,42 @@ +// +// UIView+Utilities.swift +// CollectionViewPagingLayout +// +// Created by Amir Khorsandi on 12/26/19. +// Copyright © 2019 Amir Khorsandi. All rights reserved. +// + +import UIKit + +extension UIView { + + @discardableResult + public func fill(with view: UIView, edges: UIEdgeInsets = .zero) -> [NSLayoutConstraint] { + view.translatesAutoresizingMaskIntoConstraints = false + addSubview(view) + let constraints = [ + NSLayoutConstraint(item: view, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: edges.left), + NSLayoutConstraint(item: view, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: edges.top), + NSLayoutConstraint(item: view, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: edges.right), + NSLayoutConstraint(item: view, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: edges.bottom) + ] + addConstraints(constraints) + return constraints + } + + public func center(to view: UIView) { + view.translatesAutoresizingMaskIntoConstraints = false + superview?.addConstraints([ + NSLayoutConstraint(item: view, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0), + NSLayoutConstraint(item: view, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0) + ]) + } + + public func equalSize(to view: UIView) { + view.translatesAutoresizingMaskIntoConstraints = false + superview?.addConstraints([ + NSLayoutConstraint(item: view, attribute: .width, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 1, constant: 0), + NSLayoutConstraint(item: view, attribute: .height, relatedBy: .equal, toItem: self, attribute: .height, multiplier: 1, constant: 0) + ]) + } +} 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 new file mode 100644 index 0000000..ec040a6 --- /dev/null +++ b/Package.swift @@ -0,0 +1,20 @@ +// swift-tools-version:5.1 + +import PackageDescription + +let package = Package( + name: "CollectionViewPagingLayout", + platforms: [ + .iOS(.v13) + ], + products: [ + .library( + name: "CollectionViewPagingLayout", + targets: ["CollectionViewPagingLayout"]) + ], + targets: [ + .target( + name: "CollectionViewPagingLayout", + path: "Lib") + ] +) diff --git a/PrivacyPolicy.md b/PrivacyPolicy.md new file mode 100644 index 0000000..6afdebc --- /dev/null +++ b/PrivacyPolicy.md @@ -0,0 +1,5 @@ +## Privacy Policy +- 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 4591af6..81ef52c 100644 --- a/README.md +++ b/README.md @@ -1,84 +1,159 @@ -# CollectionViewPagingLayout +# CollectionViewPagingLayout - PagingView for SwiftUI -[![Version](https://img.shields.io/cocoapods/v/CollectionViewPagingLayout.svg?style=flat)](http://cocoapods.org/pods/CollectionViewPagingLayout) [![License](https://img.shields.io/cocoapods/l/CollectionViewPagingLayout.svg?style=flat)](http://cocoapods.org/pods/CollectionViewPagingLayout) -[![Platform](https://img.shields.io/cocoapods/p/CollectionViewPagingLayout.svg?style=flat)](http://cocoapods.org/pods/CollectionViewPagingLayout) +![platforms](https://img.shields.io/badge/platforms-iOS-333333.svg) +[![pod](https://img.shields.io/cocoapods/v/CollectionViewPagingLayout.svg)](https://cocoapods.org/pods/CollectionViewPagingLayout) +[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) +[![Swift Package Manager compatible](https://img.shields.io/badge/Swift%20Package%20Manager-compatible-brightgreen.svg)](https://github.com/apple/swift-package-manager) -## Preview +## Previews + +### Layout Designer +[](https://apps.apple.com/nl/app/layout-designer/id1507238011?l=en&mt=12) + + + +#### Custom implementations, UIKit: `TransformableView`, SwiftUI: `TransformPageView` +Click on image to see the code + +

+ +[](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` + + +

+ +[](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)

- + +### 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 -A custom `UICollectionViewLayout` that gives you the ability to apply transforms easily on the cells -by conforming your cell class to `TransformableView` protocol you will get a `progress` value and you can use it to change the cell view. -You can see the usage by looking into the example source code. -More examples will be added. -## Add to your project +**`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) -#### Cocoapods -CollectionViewPagingLayout is available through [CocoaPods](http://cocoapods.org). To install -it, simply add the following line to your Podfile: +## Installation +This framework doesn't contain any external dependencies. + +#### [CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html) ```ruby -pod "CollectionViewPagingLayout" -``` -#### Manually -Just add all the files under `Lib` directory to your project +# Podfile +use_frameworks! -## How to use -- make sure you imported the library if you use cocoapods -```swift -import CollectionViewPagingLayout +target 'YOUR_TARGET_NAME' do + pod 'CollectionViewPagingLayout' +end ``` -- Set the layout for collection view: -(in most cases you want a paging effect so make sure you enable it) -```swift -let layout = CollectionViewPagingLayout() -collectionView.collectionViewLayout = layout -collectionView.isPagingEnabled = true // enabling paging effect +Replace `YOUR_TARGET_NAME` and then, in the `Podfile` directory, type: + +```bash +$ pod install ``` -- Here you can set `numberOfVisibleItems`, by default it's null and that means it will load all of the cells at a time -```swift -layout.numberOfVisibleItems = ... +#### [Carthage](https://github.com/Carthage/Carthage) + +Add this to `Cartfile` + ``` -- Now you just need to conform your `UICollectionViewCell` class to `TransformableView` and start implementing your custom transforms -> `progress` is a float value that represents current position of the cell. -> When it's `0` 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. - -```swift -extension MyCollectionViewCell: TransformableView { - func transform(progress: CGFloat) { - ... - } -} +github "CollectionViewPagingLayout" ``` -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 - } -} +and then, in the `Cartfile` directory, type: +```bash +$ carthage update ``` +#### [Swift Package Manager](https://github.com/apple/swift-package-manager) + +using Xcode: + +File > Swift Packages > Add Package Dependency + +#### Manually +Just add all the files under `Lib` directory to your project + +## How to use + +### 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 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. + +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!. + + +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 -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. + +- **It doesn't support RTL layouts:** +however, you can achieve a similar result by tweaking options, for instance try `StackTransformViewOptions.Layout.reverse` + +## Credit +- [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/.gitignore b/Samples/.gitignore new file mode 100644 index 0000000..8d51f33 --- /dev/null +++ b/Samples/.gitignore @@ -0,0 +1,59 @@ +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +#Pods + +Pods/ + +## Build generated +build/ +DerivedData/ + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ + +## Other +*.moved-aside +*.xccheckout +*.xcscmblueprint + +## Obj-C/Swift specific +*.hmap +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +.build/ + +Carthage/Build + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output diff --git a/Samples/AppKitGlue/AppKitGlue-Bridging-Header.h b/Samples/AppKitGlue/AppKitGlue-Bridging-Header.h new file mode 100644 index 0000000..fe85d53 --- /dev/null +++ b/Samples/AppKitGlue/AppKitGlue-Bridging-Header.h @@ -0,0 +1,5 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#import "AppKitBridge.h" diff --git a/Samples/AppKitGlue/Info.plist b/Samples/AppKitGlue/Info.plist new file mode 100644 index 0000000..ca5c428 --- /dev/null +++ b/Samples/AppKitGlue/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright © 2020 Amir Khorsandi. All rights reserved. + NSPrincipalClass + AppKitGlue.MacApp + + diff --git a/Samples/AppKitGlue/MacApp.swift b/Samples/AppKitGlue/MacApp.swift new file mode 100644 index 0000000..647c6d8 --- /dev/null +++ b/Samples/AppKitGlue/MacApp.swift @@ -0,0 +1,59 @@ +// +// MacApp.swift +// AppKitGlue +// +// Created by Amir on 05/05/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import Cocoa +import Combine + +class MacApp: NSObject, AppKitBridge { + + private var window: NSWindow! + private var cancellable: Cancellable! + + func initialise() { + 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() { + 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) + } + } + + private func hideToolbar() { + 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 new file mode 100644 index 0000000..829be8e --- /dev/null +++ b/Samples/PagingLayoutSamples.xcodeproj/project.pbxproj @@ -0,0 +1,1129 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 52; + objects = { + +/* Begin PBXBuildFile section */ + 292489B02461A97900A316B0 /* LayoutDesignerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292489AF2461A97900A316B0 /* LayoutDesignerViewController.swift */; }; + 292489B22461E31300A316B0 /* LayoutDesignerViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 292489B12461E31300A316B0 /* LayoutDesignerViewController.xib */; }; + 292489BD2461F08B00A316B0 /* AppKitGlue.bundle in Embed PlugIns */ = {isa = PBXBuildFile; fileRef = 292489B72461F07D00A316B0 /* AppKitGlue.bundle */; platformFilter = maccatalyst; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 292489C32461FD6A00A316B0 /* MacApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292489C22461FD6A00A316B0 /* MacApp.swift */; }; + 292489C52461FE2700A316B0 /* Catalyst.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292489C42461FE2700A316B0 /* Catalyst.swift */; }; + 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 */; }; + 29A2D3D124B738E5005A0F6B /* LayoutDesignerIntroViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29A2D3D024B738E5005A0F6B /* LayoutDesignerIntroViewModel.swift */; }; + 29A2D3D324B73A19005A0F6B /* LayoutDesignerIntroCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29A2D3D224B73A19005A0F6B /* LayoutDesignerIntroCell.swift */; }; + 29A2D3D524B73AB7005A0F6B /* LayoutDesignerIntroInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29A2D3D424B73AB7005A0F6B /* LayoutDesignerIntroInfo.swift */; }; + 29A2D3D724B73CB1005A0F6B /* LayoutDesignerIntroCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 29A2D3D624B73CB1005A0F6B /* LayoutDesignerIntroCell.xib */; }; + 29B5A72024A7B02900C9843E /* ShapeLayout+StackOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B5A71F24A7B02900C9843E /* ShapeLayout+StackOptions.swift */; }; + 29B5A72224A7B06300C9843E /* ShapeLayout+SnapshotOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B5A72124A7B06300C9843E /* ShapeLayout+SnapshotOptions.swift */; }; + 29B5A72424A8CC4B00C9843E /* CGFloat+String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B5A72324A8CC4B00C9843E /* CGFloat+String.swift */; }; + 29B5A72624A8D8B300C9843E /* OptionsCodeGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B5A72524A8D8B300C9843E /* OptionsCodeGenerator.swift */; }; + 29B5A72824A8D8F700C9843E /* TransformCurve+Name.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B5A72724A8D8F700C9843E /* TransformCurve+Name.swift */; }; + 29B5A72A24A8D94300C9843E /* Values+Pair.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B5A72924A8D94300C9843E /* Values+Pair.swift */; }; + 29B5A72C24A8DD7100C9843E /* UIBlurEffect.Style+Name.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B5A72B24A8DD7100C9843E /* UIBlurEffect.Style+Name.swift */; }; + 29BEC4D52476DD9D004BA505 /* LayoutDesignerOptionsTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29BEC4D42476DD9D004BA505 /* LayoutDesignerOptionsTableView.swift */; }; + 29CE3D4624B7763C00380DCD /* SampleProject.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 29CE3D4524B7763C00380DCD /* SampleProject.bundle */; }; + 29D9F94323F7F98800656A67 /* ShapesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D9F94223F7F98800656A67 /* ShapesViewController.swift */; }; + 29D9F94523F7F99400656A67 /* ShapesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D9F94423F7F99400656A67 /* ShapesViewModel.swift */; }; + 29D9F94723F7F9B700656A67 /* ShapesViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 29D9F94623F7F9B700656A67 /* ShapesViewController.xib */; }; + 29D9F94A23F7FC4800656A67 /* LayoutTypeCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 29D9F94923F7FC4800656A67 /* LayoutTypeCollectionViewCell.xib */; }; + 29D9F94C23F7FC5800656A67 /* LayoutTypeCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D9F94B23F7FC5800656A67 /* LayoutTypeCollectionViewCell.swift */; }; + 29D9F94E23F7FDA600656A67 /* LayoutTypeCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D9F94D23F7FDA600656A67 /* LayoutTypeCellViewModel.swift */; }; + 29D9F95023F806C400656A67 /* Optional+Let.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D9F94F23F806C400656A67 /* Optional+Let.swift */; }; + 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 */; }; + AB1BBA9B23CA5179004E5C3B /* CardCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB1BBA9823CA5178004E5C3B /* CardCellViewModel.swift */; }; + AB1BBA9C23CA5179004E5C3B /* CardCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB1BBA9923CA5178004E5C3B /* CardCollectionViewCell.swift */; }; + AB1BBA9D23CA5179004E5C3B /* CardCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = AB1BBA9A23CA5179004E5C3B /* CardCollectionViewCell.xib */; }; + AB1BBA9E23CA7BD9004E5C3B /* CardsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB1BBA8D23C935D3004E5C3B /* CardsViewController.swift */; }; + AB1BBA9F23CA7BE3004E5C3B /* CardsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = AB1BBA8C23C935D3004E5C3B /* CardsViewController.xib */; }; + AB1BBAA023CA7BE6004E5C3B /* CardsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB1BBA8E23C935D3004E5C3B /* CardsViewModel.swift */; }; + AB1E03AF23B25CE70087F904 /* PageControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB1E03AE23B25CE70087F904 /* PageControlView.swift */; }; + AB500A3323B104E20056BE37 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB500A3223B104E20056BE37 /* AppDelegate.swift */; }; + AB500A3C23B104E60056BE37 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AB500A3B23B104E60056BE37 /* Assets.xcassets */; }; + AB500A3F23B104E60056BE37 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AB500A3D23B104E60056BE37 /* LaunchScreen.storyboard */; }; + AB500A4A23B13BBD0056BE37 /* FruitsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB500A4923B13BBD0056BE37 /* FruitsViewController.swift */; }; + AB500A4C23B13BC90056BE37 /* FruitsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = AB500A4B23B13BC90056BE37 /* FruitsViewController.xib */; }; + AB500A4E23B13C5D0056BE37 /* NibBased.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB500A4D23B13C5C0056BE37 /* NibBased.swift */; }; + AB500A5023B151780056BE37 /* FruitsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB500A4F23B151780056BE37 /* FruitsViewModel.swift */; }; + AB500A5523B152500056BE37 /* ViewModelBased.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB500A5423B152500056BE37 /* ViewModelBased.swift */; }; + AB500A5823B154210056BE37 /* Fruit.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB500A5723B154210056BE37 /* Fruit.swift */; }; + AB500A5B23B154640056BE37 /* FruitsCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB500A5A23B154640056BE37 /* FruitsCollectionViewCell.swift */; }; + AB500A5D23B1547C0056BE37 /* FruitCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB500A5C23B1547C0056BE37 /* FruitCellViewModel.swift */; }; + AB500A5F23B154950056BE37 /* FruitsCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = AB500A5E23B154950056BE37 /* FruitsCollectionViewCell.xib */; }; + AB7C1E0623B4E2B2006441DE /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB7C1E0523B4E2B2006441DE /* MainViewController.swift */; }; + AB7C1E0823B4E2C0006441DE /* MainViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = AB7C1E0723B4E2C0006441DE /* MainViewController.xib */; }; + ABA0DA0123F93CA3004A9C18 /* ShapeLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABA0DA0023F93CA3004A9C18 /* ShapeLayout.swift */; }; + ABA0DA0323F93CDB004A9C18 /* Shape.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABA0DA0223F93CDB004A9C18 /* Shape.swift */; }; + ABA0DA0823F98B65004A9C18 /* ShapeCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABA0DA0723F98B65004A9C18 /* ShapeCardView.swift */; }; + ABA0DA0A23F98B70004A9C18 /* ShapeCardView.xib in Resources */ = {isa = PBXBuildFile; fileRef = ABA0DA0923F98B70004A9C18 /* ShapeCardView.xib */; }; + ABA0DA0C23F98C78004A9C18 /* ShapeCardViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABA0DA0B23F98C78004A9C18 /* ShapeCardViewModel.swift */; }; + ABA0DA0E23F98ECD004A9C18 /* UICollectionViewCell+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABA0DA0D23F98ECD004A9C18 /* UICollectionViewCell+Utilities.swift */; }; + ABA1A72C23B42240006A46A3 /* PriceTagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABA1A72B23B42240006A46A3 /* PriceTagView.swift */; }; + ABA1A72E23B42247006A46A3 /* PriceTagView.xib in Resources */ = {isa = PBXBuildFile; fileRef = ABA1A72D23B42247006A46A3 /* PriceTagView.xib */; }; + ABA1A73123B422B2006A46A3 /* QuantityControllerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABA1A73023B422B2006A46A3 /* QuantityControllerView.swift */; }; + ABA1A73323B422B9006A46A3 /* QuantityControllerView.xib in Resources */ = {isa = PBXBuildFile; fileRef = ABA1A73223B422B9006A46A3 /* QuantityControllerView.xib */; }; + ABC242C323B6822200DBD4D6 /* GalleryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC242C223B6822200DBD4D6 /* GalleryViewController.swift */; }; + ABC242C523B6822A00DBD4D6 /* GalleryViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = ABC242C423B6822A00DBD4D6 /* GalleryViewController.xib */; }; + ABC242C723B6823600DBD4D6 /* GalleryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC242C623B6823600DBD4D6 /* GalleryViewModel.swift */; }; + ABC242CA23B682DD00DBD4D6 /* PhotoCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC242C923B682DD00DBD4D6 /* PhotoCellViewModel.swift */; }; + ABC242CC23B6831400DBD4D6 /* Photo.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC242CB23B6831400DBD4D6 /* Photo.swift */; }; + ABC242CE23B6860700DBD4D6 /* PhotoCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC242CD23B6860700DBD4D6 /* PhotoCollectionViewCell.swift */; }; + ABC242D023B6861000DBD4D6 /* PhotoCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = ABC242CF23B6861000DBD4D6 /* PhotoCollectionViewCell.xib */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 292489BE2461F08B00A316B0 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = AB500A2723B104E20056BE37 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 292489B62461F07D00A316B0; + remoteInfo = AppKitGlue; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 292489C02461F08B00A316B0 /* Embed PlugIns */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 292489BD2461F08B00A316B0 /* AppKitGlue.bundle in Embed PlugIns */, + ); + name = "Embed PlugIns"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 292489AD2461A57900A316B0 /* Paging Layout.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Paging Layout.entitlements"; sourceTree = ""; }; + 292489AF2461A97900A316B0 /* LayoutDesignerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutDesignerViewController.swift; sourceTree = ""; }; + 292489B12461E31300A316B0 /* LayoutDesignerViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LayoutDesignerViewController.xib; sourceTree = ""; }; + 292489B72461F07D00A316B0 /* AppKitGlue.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AppKitGlue.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; + 292489B92461F07D00A316B0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 292489C12461FD6900A316B0 /* AppKitGlue-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AppKitGlue-Bridging-Header.h"; sourceTree = ""; }; + 292489C22461FD6A00A316B0 /* MacApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacApp.swift; sourceTree = ""; }; + 292489C42461FE2700A316B0 /* Catalyst.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Catalyst.swift; sourceTree = ""; }; + 292489CA24620BD600A316B0 /* AppKitBridge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppKitBridge.h; sourceTree = ""; }; + 292489CB24620C4D00A316B0 /* PagingLayoutSamples-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "PagingLayoutSamples-Bridging-Header.h"; sourceTree = ""; }; + 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 = ""; }; + 29A2D3D024B738E5005A0F6B /* LayoutDesignerIntroViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutDesignerIntroViewModel.swift; sourceTree = ""; }; + 29A2D3D224B73A19005A0F6B /* LayoutDesignerIntroCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutDesignerIntroCell.swift; sourceTree = ""; }; + 29A2D3D424B73AB7005A0F6B /* LayoutDesignerIntroInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutDesignerIntroInfo.swift; sourceTree = ""; }; + 29A2D3D624B73CB1005A0F6B /* LayoutDesignerIntroCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LayoutDesignerIntroCell.xib; sourceTree = ""; }; + 29B5A71F24A7B02900C9843E /* ShapeLayout+StackOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ShapeLayout+StackOptions.swift"; sourceTree = ""; }; + 29B5A72124A7B06300C9843E /* ShapeLayout+SnapshotOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ShapeLayout+SnapshotOptions.swift"; sourceTree = ""; }; + 29B5A72324A8CC4B00C9843E /* CGFloat+String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGFloat+String.swift"; sourceTree = ""; }; + 29B5A72524A8D8B300C9843E /* OptionsCodeGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionsCodeGenerator.swift; sourceTree = ""; }; + 29B5A72724A8D8F700C9843E /* TransformCurve+Name.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TransformCurve+Name.swift"; sourceTree = ""; }; + 29B5A72924A8D94300C9843E /* Values+Pair.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Values+Pair.swift"; sourceTree = ""; }; + 29B5A72B24A8DD7100C9843E /* UIBlurEffect.Style+Name.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIBlurEffect.Style+Name.swift"; sourceTree = ""; }; + 29BEC4D42476DD9D004BA505 /* LayoutDesignerOptionsTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutDesignerOptionsTableView.swift; sourceTree = ""; }; + 29CE3D4524B7763C00380DCD /* SampleProject.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = SampleProject.bundle; sourceTree = ""; }; + 29D9F94223F7F98800656A67 /* ShapesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapesViewController.swift; sourceTree = ""; }; + 29D9F94423F7F99400656A67 /* ShapesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapesViewModel.swift; sourceTree = ""; }; + 29D9F94623F7F9B700656A67 /* ShapesViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ShapesViewController.xib; sourceTree = ""; }; + 29D9F94923F7FC4800656A67 /* LayoutTypeCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LayoutTypeCollectionViewCell.xib; sourceTree = ""; }; + 29D9F94B23F7FC5800656A67 /* LayoutTypeCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutTypeCollectionViewCell.swift; sourceTree = ""; }; + 29D9F94D23F7FDA600656A67 /* LayoutTypeCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutTypeCellViewModel.swift; sourceTree = ""; }; + 29D9F94F23F806C400656A67 /* Optional+Let.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Optional+Let.swift"; sourceTree = ""; }; + 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 = ""; }; + 89FF77F458B1EB2029D8978E /* Pods-PagingLayoutSamples.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PagingLayoutSamples.debug.xcconfig"; path = "Target Support Files/Pods-PagingLayoutSamples/Pods-PagingLayoutSamples.debug.xcconfig"; sourceTree = ""; }; + A489BA28D16931A0BE34BD0B /* Pods_PagingLayoutSamples.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PagingLayoutSamples.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + AB1BBA8C23C935D3004E5C3B /* CardsViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CardsViewController.xib; sourceTree = ""; }; + AB1BBA8D23C935D3004E5C3B /* CardsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardsViewController.swift; sourceTree = ""; }; + AB1BBA8E23C935D3004E5C3B /* CardsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardsViewModel.swift; sourceTree = ""; }; + AB1BBA9823CA5178004E5C3B /* CardCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardCellViewModel.swift; sourceTree = ""; }; + AB1BBA9923CA5178004E5C3B /* CardCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardCollectionViewCell.swift; sourceTree = ""; }; + AB1BBA9A23CA5179004E5C3B /* CardCollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CardCollectionViewCell.xib; sourceTree = ""; }; + AB1E03AE23B25CE70087F904 /* PageControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageControlView.swift; sourceTree = ""; }; + AB500A2F23B104E20056BE37 /* Layout Designer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Layout Designer.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + AB500A3223B104E20056BE37 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + AB500A3B23B104E60056BE37 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + AB500A3E23B104E60056BE37 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + AB500A4023B104E60056BE37 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + AB500A4923B13BBD0056BE37 /* FruitsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FruitsViewController.swift; sourceTree = ""; }; + AB500A4B23B13BC90056BE37 /* FruitsViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FruitsViewController.xib; sourceTree = ""; }; + AB500A4D23B13C5C0056BE37 /* NibBased.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NibBased.swift; sourceTree = ""; }; + AB500A4F23B151780056BE37 /* FruitsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FruitsViewModel.swift; sourceTree = ""; }; + AB500A5423B152500056BE37 /* ViewModelBased.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelBased.swift; sourceTree = ""; }; + AB500A5723B154210056BE37 /* Fruit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fruit.swift; sourceTree = ""; }; + AB500A5A23B154640056BE37 /* FruitsCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FruitsCollectionViewCell.swift; sourceTree = ""; }; + AB500A5C23B1547C0056BE37 /* FruitCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FruitCellViewModel.swift; sourceTree = ""; }; + AB500A5E23B154950056BE37 /* FruitsCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FruitsCollectionViewCell.xib; sourceTree = ""; }; + AB7C1E0523B4E2B2006441DE /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; + AB7C1E0723B4E2C0006441DE /* MainViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainViewController.xib; sourceTree = ""; }; + ABA0DA0023F93CA3004A9C18 /* ShapeLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapeLayout.swift; sourceTree = ""; }; + ABA0DA0223F93CDB004A9C18 /* Shape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shape.swift; sourceTree = ""; }; + ABA0DA0723F98B65004A9C18 /* ShapeCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapeCardView.swift; sourceTree = ""; }; + ABA0DA0923F98B70004A9C18 /* ShapeCardView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ShapeCardView.xib; sourceTree = ""; }; + ABA0DA0B23F98C78004A9C18 /* ShapeCardViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapeCardViewModel.swift; sourceTree = ""; }; + ABA0DA0D23F98ECD004A9C18 /* UICollectionViewCell+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UICollectionViewCell+Utilities.swift"; sourceTree = ""; }; + ABA1A72B23B42240006A46A3 /* PriceTagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PriceTagView.swift; sourceTree = ""; }; + ABA1A72D23B42247006A46A3 /* PriceTagView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PriceTagView.xib; sourceTree = ""; }; + ABA1A73023B422B2006A46A3 /* QuantityControllerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuantityControllerView.swift; sourceTree = ""; }; + ABA1A73223B422B9006A46A3 /* QuantityControllerView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = QuantityControllerView.xib; sourceTree = ""; }; + ABC242C223B6822200DBD4D6 /* GalleryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryViewController.swift; sourceTree = ""; }; + ABC242C423B6822A00DBD4D6 /* GalleryViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = GalleryViewController.xib; sourceTree = ""; }; + ABC242C623B6823600DBD4D6 /* GalleryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryViewModel.swift; sourceTree = ""; }; + ABC242C923B682DD00DBD4D6 /* PhotoCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoCellViewModel.swift; sourceTree = ""; }; + ABC242CB23B6831400DBD4D6 /* Photo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Photo.swift; sourceTree = ""; }; + ABC242CD23B6860700DBD4D6 /* PhotoCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoCollectionViewCell.swift; sourceTree = ""; }; + ABC242CF23B6861000DBD4D6 /* PhotoCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PhotoCollectionViewCell.xib; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 292489B42461F07D00A316B0 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AB500A2C23B104E20056BE37 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 55923226155A0B6E5A55C691 /* Pods_PagingLayoutSamples.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 05F53E609F3F8B841D87E931 /* Frameworks */ = { + isa = PBXGroup; + children = ( + A489BA28D16931A0BE34BD0B /* Pods_PagingLayoutSamples.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 292489AE2461A96600A316B0 /* LayoutDesigner */ = { + isa = PBXGroup; + children = ( + 29A2D3CB24B73870005A0F6B /* Intro */, + 29FF296024A631DC00C83DF9 /* Code */, + 2977657A247452BC00835DBD /* Options */, + 292489AF2461A97900A316B0 /* LayoutDesignerViewController.swift */, + 294BA3C224A77A73008D0569 /* LayoutDesignerViewModel.swift */, + 292489B12461E31300A316B0 /* LayoutDesignerViewController.xib */, + 29B5A72524A8D8B300C9843E /* OptionsCodeGenerator.swift */, + ); + path = LayoutDesigner; + sourceTree = ""; + }; + 292489B82461F07D00A316B0 /* AppKitGlue */ = { + isa = PBXGroup; + children = ( + 292489B92461F07D00A316B0 /* Info.plist */, + 292489C22461FD6A00A316B0 /* MacApp.swift */, + 292489C12461FD6900A316B0 /* AppKitGlue-Bridging-Header.h */, + ); + path = AppKitGlue; + sourceTree = ""; + }; + 29519ACA262B5DD400D8A4A3 /* Shapes */ = { + isa = PBXGroup; + children = ( + 29519ACB262B5DE400D8A4A3 /* ShapesListView.swift */, + 29519ACD262B646E00D8A4A3 /* ShapesListView.ShapeView.swift */, + ); + path = Shapes; + sourceTree = ""; + }; + 2977657A247452BC00835DBD /* Options */ = { + isa = PBXGroup; + children = ( + 29BEC4D62476DDA7004BA505 /* cell */, + 29BEC4D42476DD9D004BA505 /* LayoutDesignerOptionsTableView.swift */, + ); + path = Options; + sourceTree = ""; + }; + 29834F2925C5976300896343 /* SwiftUI */ = { + isa = PBXGroup; + children = ( + 29519ACA262B5DD400D8A4A3 /* Shapes */, + 29DD1E322627707A00846F7B /* WeatherTabView */, + 29834F2A25C5977300896343 /* DevicesView.swift */, + ); + path = SwiftUI; + sourceTree = ""; + }; + 29A2D3CB24B73870005A0F6B /* Intro */ = { + isa = PBXGroup; + children = ( + 29A2D3CC24B738CC005A0F6B /* LayoutDesignerIntroViewController.swift */, + 29A2D3D024B738E5005A0F6B /* LayoutDesignerIntroViewModel.swift */, + 29A2D3D224B73A19005A0F6B /* LayoutDesignerIntroCell.swift */, + 29A2D3D624B73CB1005A0F6B /* LayoutDesignerIntroCell.xib */, + ); + path = Intro; + sourceTree = ""; + }; + 29BEC4D62476DDA7004BA505 /* cell */ = { + isa = PBXGroup; + children = ( + 2977657F247454BC00835DBD /* LayoutDesignerOptionCellViewModel.swift */, + 2977657B2474531200835DBD /* LayoutDesignerOptionCell.swift */, + 2977657D2474531D00835DBD /* LayoutDesignerOptionCell.xib */, + ); + path = cell; + sourceTree = ""; + }; + 29D9F94123F7F92000656A67 /* Shapes */ = { + isa = PBXGroup; + children = ( + ABA0DA0623F98B53004A9C18 /* Card */, + 29D9F95123F8683B00656A67 /* ShapeCell */, + 29D9F94823F7FC2D00656A67 /* LayoutTypeCell */, + 29D9F94223F7F98800656A67 /* ShapesViewController.swift */, + 29D9F94423F7F99400656A67 /* ShapesViewModel.swift */, + 29D9F94623F7F9B700656A67 /* ShapesViewController.xib */, + 2993722224A79A9C0026D52F /* ShapeLayout+ScaleOptions.swift */, + 29B5A71F24A7B02900C9843E /* ShapeLayout+StackOptions.swift */, + 29B5A72124A7B06300C9843E /* ShapeLayout+SnapshotOptions.swift */, + ); + path = Shapes; + sourceTree = ""; + }; + 29D9F94823F7FC2D00656A67 /* LayoutTypeCell */ = { + isa = PBXGroup; + children = ( + 29D9F94B23F7FC5800656A67 /* LayoutTypeCollectionViewCell.swift */, + 29D9F94D23F7FDA600656A67 /* LayoutTypeCellViewModel.swift */, + 29D9F94923F7FC4800656A67 /* LayoutTypeCollectionViewCell.xib */, + ); + path = LayoutTypeCell; + sourceTree = ""; + }; + 29D9F95123F8683B00656A67 /* ShapeCell */ = { + isa = PBXGroup; + children = ( + 29D9F95223F8685C00656A67 /* BaseShapeCollectionViewCell.swift */, + 29D9F95A23F88A6900656A67 /* ShapeCollectionViewCells.swift */, + ); + 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 = ( + 29FF296124A6321100C83DF9 /* LayoutDesignerCodePreviewViewController.swift */, + 29A2D3A924B72895005A0F6B /* LayoutDesignerCodePreviewViewController.xib */, + 29FF296324A6321B00C83DF9 /* LayoutDesignerCodePreviewViewModel.swift */, + ); + path = Code; + sourceTree = ""; + }; + 4D87DB0F34A1E0FEF9A22214 /* Pods */ = { + isa = PBXGroup; + children = ( + 89FF77F458B1EB2029D8978E /* Pods-PagingLayoutSamples.debug.xcconfig */, + 444EC9E8B3BA262F3697984F /* Pods-PagingLayoutSamples.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + AB1BBA8B23C935BF004E5C3B /* Cards */ = { + isa = PBXGroup; + children = ( + AB1BBA9123CA5143004E5C3B /* Cell */, + AB1BBA8D23C935D3004E5C3B /* CardsViewController.swift */, + AB1BBA8C23C935D3004E5C3B /* CardsViewController.xib */, + AB1BBA8E23C935D3004E5C3B /* CardsViewModel.swift */, + ); + path = Cards; + sourceTree = ""; + }; + AB1BBA9123CA5143004E5C3B /* Cell */ = { + isa = PBXGroup; + children = ( + AB1BBA9823CA5178004E5C3B /* CardCellViewModel.swift */, + AB1BBA9923CA5178004E5C3B /* CardCollectionViewCell.swift */, + AB1BBA9A23CA5179004E5C3B /* CardCollectionViewCell.xib */, + ); + path = Cell; + sourceTree = ""; + }; + AB1E03AD23B25CD30087F904 /* CustomViews */ = { + isa = PBXGroup; + children = ( + ABA1A72F23B4225C006A46A3 /* QuantityControllerView */, + ABA1A72A23B4222E006A46A3 /* PriceTagView */, + AB1E03AE23B25CE70087F904 /* PageControlView.swift */, + 29D9F95823F874E900656A67 /* GradientView.swift */, + ); + path = CustomViews; + sourceTree = ""; + }; + AB500A2623B104E20056BE37 = { + isa = PBXGroup; + children = ( + AB500A3123B104E20056BE37 /* PagingLayoutSamples */, + 292489B82461F07D00A316B0 /* AppKitGlue */, + AB500A3023B104E20056BE37 /* Products */, + 4D87DB0F34A1E0FEF9A22214 /* Pods */, + 05F53E609F3F8B841D87E931 /* Frameworks */, + ); + sourceTree = ""; + }; + AB500A3023B104E20056BE37 /* Products */ = { + isa = PBXGroup; + children = ( + AB500A2F23B104E20056BE37 /* Layout Designer.app */, + 292489B72461F07D00A316B0 /* AppKitGlue.bundle */, + ); + name = Products; + sourceTree = ""; + }; + AB500A3123B104E20056BE37 /* PagingLayoutSamples */ = { + isa = PBXGroup; + children = ( + 292489AD2461A57900A316B0 /* Paging Layout.entitlements */, + AB1E03AD23B25CD30087F904 /* CustomViews */, + AB500A5223B152170056BE37 /* Modules */, + AB500A5123B152130056BE37 /* Models */, + AB500A5323B152400056BE37 /* Utilities */, + AB500A3223B104E20056BE37 /* AppDelegate.swift */, + 292489C42461FE2700A316B0 /* Catalyst.swift */, + AB500A3B23B104E60056BE37 /* Assets.xcassets */, + AB500A3D23B104E60056BE37 /* LaunchScreen.storyboard */, + AB500A4023B104E60056BE37 /* Info.plist */, + 292489CA24620BD600A316B0 /* AppKitBridge.h */, + 292489CB24620C4D00A316B0 /* PagingLayoutSamples-Bridging-Header.h */, + 29CE3D4524B7763C00380DCD /* SampleProject.bundle */, + ); + path = PagingLayoutSamples; + sourceTree = ""; + }; + AB500A4823B13B9D0056BE37 /* Fruits */ = { + isa = PBXGroup; + children = ( + AB500A5923B1544A0056BE37 /* Cell */, + AB500A4923B13BBD0056BE37 /* FruitsViewController.swift */, + AB500A4B23B13BC90056BE37 /* FruitsViewController.xib */, + AB500A4F23B151780056BE37 /* FruitsViewModel.swift */, + ); + path = Fruits; + sourceTree = ""; + }; + AB500A5123B152130056BE37 /* Models */ = { + isa = PBXGroup; + children = ( + AB500A5723B154210056BE37 /* Fruit.swift */, + ABC242CB23B6831400DBD4D6 /* Photo.swift */, + 2925CDDE23D4D21F00243F5F /* Card.swift */, + ABA0DA0023F93CA3004A9C18 /* ShapeLayout.swift */, + ABA0DA0223F93CDB004A9C18 /* Shape.swift */, + 29A2D3D424B73AB7005A0F6B /* LayoutDesignerIntroInfo.swift */, + ); + path = Models; + sourceTree = ""; + }; + AB500A5223B152170056BE37 /* Modules */ = { + isa = PBXGroup; + children = ( + 29DD1E21262753B900846F7B /* UIKit */, + 29834F2925C5976300896343 /* SwiftUI */, + 292489AE2461A96600A316B0 /* LayoutDesigner */, + AB7C1E0423B4E292006441DE /* Main */, + ); + path = Modules; + sourceTree = ""; + }; + AB500A5323B152400056BE37 /* Utilities */ = { + isa = PBXGroup; + children = ( + AB500A4D23B13C5C0056BE37 /* NibBased.swift */, + ABA0DA0D23F98ECD004A9C18 /* UICollectionViewCell+Utilities.swift */, + 2949CB262476EA8C000CC073 /* UITableView+Utilities.swift */, + AB500A5423B152500056BE37 /* ViewModelBased.swift */, + 29D9F94F23F806C400656A67 /* Optional+Let.swift */, + 29B5A72324A8CC4B00C9843E /* CGFloat+String.swift */, + 29B5A72724A8D8F700C9843E /* TransformCurve+Name.swift */, + 29B5A72B24A8DD7100C9843E /* UIBlurEffect.Style+Name.swift */, + 29B5A72924A8D94300C9843E /* Values+Pair.swift */, + 29DD1E2A26275E1D00846F7B /* VisualEffectView.swift */, + ); + path = Utilities; + sourceTree = ""; + }; + AB500A5923B1544A0056BE37 /* Cell */ = { + isa = PBXGroup; + children = ( + AB500A5A23B154640056BE37 /* FruitsCollectionViewCell.swift */, + AB500A5E23B154950056BE37 /* FruitsCollectionViewCell.xib */, + AB500A5C23B1547C0056BE37 /* FruitCellViewModel.swift */, + ); + path = Cell; + sourceTree = ""; + }; + AB7C1E0423B4E292006441DE /* Main */ = { + isa = PBXGroup; + children = ( + AB7C1E0523B4E2B2006441DE /* MainViewController.swift */, + AB7C1E0723B4E2C0006441DE /* MainViewController.xib */, + ); + path = Main; + sourceTree = ""; + }; + ABA0DA0623F98B53004A9C18 /* Card */ = { + isa = PBXGroup; + children = ( + ABA0DA0723F98B65004A9C18 /* ShapeCardView.swift */, + ABA0DA0B23F98C78004A9C18 /* ShapeCardViewModel.swift */, + ABA0DA0923F98B70004A9C18 /* ShapeCardView.xib */, + ); + path = Card; + sourceTree = ""; + }; + ABA1A72A23B4222E006A46A3 /* PriceTagView */ = { + isa = PBXGroup; + children = ( + ABA1A72B23B42240006A46A3 /* PriceTagView.swift */, + ABA1A72D23B42247006A46A3 /* PriceTagView.xib */, + ); + path = PriceTagView; + sourceTree = ""; + }; + ABA1A72F23B4225C006A46A3 /* QuantityControllerView */ = { + isa = PBXGroup; + children = ( + ABA1A73023B422B2006A46A3 /* QuantityControllerView.swift */, + ABA1A73223B422B9006A46A3 /* QuantityControllerView.xib */, + ); + path = QuantityControllerView; + sourceTree = ""; + }; + ABC242C123B681F800DBD4D6 /* Gallery */ = { + isa = PBXGroup; + children = ( + ABC242C823B682D400DBD4D6 /* Cell */, + ABC242C223B6822200DBD4D6 /* GalleryViewController.swift */, + ABC242C423B6822A00DBD4D6 /* GalleryViewController.xib */, + ABC242C623B6823600DBD4D6 /* GalleryViewModel.swift */, + ); + path = Gallery; + sourceTree = ""; + }; + ABC242C823B682D400DBD4D6 /* Cell */ = { + isa = PBXGroup; + children = ( + ABC242C923B682DD00DBD4D6 /* PhotoCellViewModel.swift */, + ABC242CD23B6860700DBD4D6 /* PhotoCollectionViewCell.swift */, + ABC242CF23B6861000DBD4D6 /* PhotoCollectionViewCell.xib */, + ); + path = Cell; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 292489B62461F07D00A316B0 /* AppKitGlue */ = { + isa = PBXNativeTarget; + buildConfigurationList = 292489BA2461F07D00A316B0 /* Build configuration list for PBXNativeTarget "AppKitGlue" */; + buildPhases = ( + 292489B32461F07D00A316B0 /* Sources */, + 292489B42461F07D00A316B0 /* Frameworks */, + 292489B52461F07D00A316B0 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = AppKitGlue; + productName = AppKitGlue; + productReference = 292489B72461F07D00A316B0 /* AppKitGlue.bundle */; + productType = "com.apple.product-type.bundle"; + }; + AB500A2E23B104E20056BE37 /* PagingLayoutSamples */ = { + isa = PBXNativeTarget; + buildConfigurationList = AB500A4323B104E60056BE37 /* Build configuration list for PBXNativeTarget "PagingLayoutSamples" */; + buildPhases = ( + 6C5C2953E0649518FCAADAB2 /* [CP] Check Pods Manifest.lock */, + 296700332442116600A1F508 /* SwiftLint */, + AB500A2B23B104E20056BE37 /* Sources */, + AB500A2C23B104E20056BE37 /* Frameworks */, + AB500A2D23B104E20056BE37 /* Resources */, + 16CABBE36A64D6ED31E82309 /* [CP] Embed Pods Frameworks */, + 292489C02461F08B00A316B0 /* Embed PlugIns */, + ); + buildRules = ( + ); + dependencies = ( + 292489BF2461F08B00A316B0 /* PBXTargetDependency */, + ); + name = PagingLayoutSamples; + productName = CollectionViewPagingLayout; + productReference = AB500A2F23B104E20056BE37 /* Layout Designer.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + AB500A2723B104E20056BE37 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1120; + LastUpgradeCheck = 1220; + ORGANIZATIONNAME = "Amir Khorsandi"; + TargetAttributes = { + 292489B62461F07D00A316B0 = { + CreatedOnToolsVersion = 11.4; + LastSwiftMigration = 1140; + }; + AB500A2E23B104E20056BE37 = { + CreatedOnToolsVersion = 11.2.1; + }; + }; + }; + buildConfigurationList = AB500A2A23B104E20056BE37 /* Build configuration list for PBXProject "PagingLayoutSamples" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = AB500A2623B104E20056BE37; + productRefGroup = AB500A3023B104E20056BE37 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + AB500A2E23B104E20056BE37 /* PagingLayoutSamples */, + 292489B62461F07D00A316B0 /* AppKitGlue */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 292489B52461F07D00A316B0 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AB500A2D23B104E20056BE37 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 29D9F94A23F7FC4800656A67 /* LayoutTypeCollectionViewCell.xib in Resources */, + AB500A3F23B104E60056BE37 /* LaunchScreen.storyboard in Resources */, + ABC242C523B6822A00DBD4D6 /* GalleryViewController.xib in Resources */, + AB500A5F23B154950056BE37 /* FruitsCollectionViewCell.xib in Resources */, + AB1BBA9F23CA7BE3004E5C3B /* CardsViewController.xib in Resources */, + 2977657E2474531D00835DBD /* LayoutDesignerOptionCell.xib in Resources */, + 29CE3D4624B7763C00380DCD /* SampleProject.bundle in Resources */, + 29D9F94723F7F9B700656A67 /* ShapesViewController.xib in Resources */, + ABC242D023B6861000DBD4D6 /* PhotoCollectionViewCell.xib in Resources */, + ABA0DA0A23F98B70004A9C18 /* ShapeCardView.xib in Resources */, + 292489B22461E31300A316B0 /* LayoutDesignerViewController.xib in Resources */, + AB500A4C23B13BC90056BE37 /* FruitsViewController.xib in Resources */, + AB500A3C23B104E60056BE37 /* Assets.xcassets in Resources */, + AB1BBA9D23CA5179004E5C3B /* CardCollectionViewCell.xib in Resources */, + 29A2D3D724B73CB1005A0F6B /* LayoutDesignerIntroCell.xib in Resources */, + AB7C1E0823B4E2C0006441DE /* MainViewController.xib in Resources */, + ABA1A72E23B42247006A46A3 /* PriceTagView.xib in Resources */, + 29A2D3AA24B72895005A0F6B /* LayoutDesignerCodePreviewViewController.xib in Resources */, + ABA1A73323B422B9006A46A3 /* QuantityControllerView.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 16CABBE36A64D6ED31E82309 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PagingLayoutSamples/Pods-PagingLayoutSamples-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PagingLayoutSamples/Pods-PagingLayoutSamples-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PagingLayoutSamples/Pods-PagingLayoutSamples-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 296700332442116600A1F508 /* SwiftLint */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = SwiftLint; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}\"/SwiftLint/swiftlint autocorrect --config \"${SRCROOT}/../.swiftlint_autocorrect.yml\";\n\"${PODS_ROOT}\"/SwiftLint/swiftlint --config \"${SRCROOT}/../.swiftlint.yml\";\n"; + }; + 6C5C2953E0649518FCAADAB2 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-PagingLayoutSamples-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 292489B32461F07D00A316B0 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 292489C32461FD6A00A316B0 /* MacApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AB500A2B23B104E20056BE37 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ABC242CE23B6860700DBD4D6 /* PhotoCollectionViewCell.swift in Sources */, + 29A2D3D324B73A19005A0F6B /* LayoutDesignerIntroCell.swift in Sources */, + 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 */, + AB1E03AF23B25CE70087F904 /* PageControlView.swift in Sources */, + 29B5A72224A7B06300C9843E /* ShapeLayout+SnapshotOptions.swift in Sources */, + 29A2D3D524B73AB7005A0F6B /* LayoutDesignerIntroInfo.swift in Sources */, + 29776580247454BC00835DBD /* LayoutDesignerOptionCellViewModel.swift in Sources */, + 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 */, + ABC242C323B6822200DBD4D6 /* GalleryViewController.swift in Sources */, + 2993722324A79A9C0026D52F /* ShapeLayout+ScaleOptions.swift in Sources */, + AB1BBAA023CA7BE6004E5C3B /* CardsViewModel.swift in Sources */, + 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 */, + AB7C1E0623B4E2B2006441DE /* MainViewController.swift in Sources */, + AB500A5D23B1547C0056BE37 /* FruitCellViewModel.swift in Sources */, + 29D9F94E23F7FDA600656A67 /* LayoutTypeCellViewModel.swift in Sources */, + 29D9F95B23F88A6900656A67 /* ShapeCollectionViewCells.swift in Sources */, + ABA0DA0123F93CA3004A9C18 /* ShapeLayout.swift in Sources */, + 294BA3C324A77A73008D0569 /* LayoutDesignerViewModel.swift in Sources */, + 29D9F94C23F7FC5800656A67 /* LayoutTypeCollectionViewCell.swift in Sources */, + AB1BBA9C23CA5179004E5C3B /* CardCollectionViewCell.swift in Sources */, + 29D9F95323F8685C00656A67 /* BaseShapeCollectionViewCell.swift in Sources */, + 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 */, + AB500A3323B104E20056BE37 /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 292489BF2461F08B00A316B0 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + platformFilter = maccatalyst; + target = 292489B62461F07D00A316B0 /* AppKitGlue */; + targetProxy = 292489BE2461F08B00A316B0 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + AB500A3D23B104E60056BE37 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + AB500A3E23B104E60056BE37 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 292489BB2461F07D00A316B0 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 4J5W7CJ2ZV; + INFOPLIST_FILE = AppKitGlue/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.15; + PRODUCT_BUNDLE_IDENTIFIER = amir.app.AppKitGlue; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = "AppKitGlue/AppKitGlue-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + WRAPPER_EXTENSION = bundle; + }; + name = Debug; + }; + 292489BC2461F07D00A316B0 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 4J5W7CJ2ZV; + INFOPLIST_FILE = AppKitGlue/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.15; + PRODUCT_BUNDLE_IDENTIFIER = amir.app.AppKitGlue; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = "AppKitGlue/AppKitGlue-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + WRAPPER_EXTENSION = bundle; + }; + name = Release; + }; + AB500A4123B104E60056BE37 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 150; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MARKETING_VERSION = 1.5.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + AB500A4223B104E60056BE37 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 150; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MARKETING_VERSION = 1.5.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + AB500A4423B104E60056BE37 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 89FF77F458B1EB2029D8978E /* Pods-PagingLayoutSamples.debug.xcconfig */; + 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; + DEVELOPMENT_TEAM = 4J5W7CJ2ZV; + INFOPLIST_FILE = PagingLayoutSamples/Info.plist; + "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + 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,6"; + }; + name = Debug; + }; + AB500A4523B104E60056BE37 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 444EC9E8B3BA262F3697984F /* Pods-PagingLayoutSamples.release.xcconfig */; + 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; + DEVELOPMENT_TEAM = 4J5W7CJ2ZV; + INFOPLIST_FILE = PagingLayoutSamples/Info.plist; + "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + 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,6"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 292489BA2461F07D00A316B0 /* Build configuration list for PBXNativeTarget "AppKitGlue" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 292489BB2461F07D00A316B0 /* Debug */, + 292489BC2461F07D00A316B0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + AB500A2A23B104E20056BE37 /* Build configuration list for PBXProject "PagingLayoutSamples" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AB500A4123B104E60056BE37 /* Debug */, + AB500A4223B104E60056BE37 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + AB500A4323B104E60056BE37 /* Build configuration list for PBXNativeTarget "PagingLayoutSamples" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AB500A4423B104E60056BE37 /* Debug */, + AB500A4523B104E60056BE37 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = AB500A2723B104E20056BE37 /* Project object */; +} diff --git a/Samples/PagingLayoutSamples.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Samples/PagingLayoutSamples.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..dcf7373 --- /dev/null +++ b/Samples/PagingLayoutSamples.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Samples/PagingLayoutSamples.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Samples/PagingLayoutSamples.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Samples/PagingLayoutSamples.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Samples/PagingLayoutSamples.xcodeproj/xcshareddata/xcschemes/PagingLayoutSamples.xcscheme b/Samples/PagingLayoutSamples.xcodeproj/xcshareddata/xcschemes/PagingLayoutSamples.xcscheme new file mode 100644 index 0000000..c405f46 --- /dev/null +++ b/Samples/PagingLayoutSamples.xcodeproj/xcshareddata/xcschemes/PagingLayoutSamples.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Samples/PagingLayoutSamples.xcworkspace/contents.xcworkspacedata b/Samples/PagingLayoutSamples.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a18fa --- /dev/null +++ b/Samples/PagingLayoutSamples.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/Samples/PagingLayoutSamples.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Samples/PagingLayoutSamples.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Samples/PagingLayoutSamples.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Samples/PagingLayoutSamples/AppDelegate.swift b/Samples/PagingLayoutSamples/AppDelegate.swift new file mode 100644 index 0000000..a322309 --- /dev/null +++ b/Samples/PagingLayoutSamples/AppDelegate.swift @@ -0,0 +1,46 @@ +// +// AppDelegate.swift +// CollectionViewPagingLayout +// +// Created by Amir Khorsandi on 12/23/19. +// Copyright © 2019 Amir Khorsandi. All rights reserved. +// + +import UIKit +import SwiftUI + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + + var window: UIWindow? + var navigationController: UINavigationController! + + override init() { + super.init() + Catalyst.bridge?.initialise() + } + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + window = UIWindow() + window?.backgroundColor = .clear + navigationController = UINavigationController() + navigationController.view.backgroundColor = .clear + navigationController.isNavigationBarHidden = true + 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/AppKitBridge.h b/Samples/PagingLayoutSamples/AppKitBridge.h new file mode 100644 index 0000000..35ee611 --- /dev/null +++ b/Samples/PagingLayoutSamples/AppKitBridge.h @@ -0,0 +1,15 @@ +// +// AppKitBridge.h +// PagingLayoutSamples +// +// Created by Amir on 05/05/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +#import + +@protocol AppKitBridge + +- (void)initialise; + +@end 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 new file mode 100644 index 0000000..f824938 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,176 @@ +{ + "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/CollectionViewPagingLayout/Assets.xcassets/Card/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Card/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Card/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Card/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Card/Glow.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Card/Glow.imageset/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Card/Glow.imageset/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Card/Glow.imageset/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Card/Glow.imageset/Glow.png b/Samples/PagingLayoutSamples/Assets.xcassets/Card/Glow.imageset/Glow.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Card/Glow.imageset/Glow.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Card/Glow.imageset/Glow.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Card/Glow.imageset/Glow@2x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Card/Glow.imageset/Glow@2x.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Card/Glow.imageset/Glow@2x.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Card/Glow.imageset/Glow@2x.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Card/Glow.imageset/Glow@3x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Card/Glow.imageset/Glow@3x.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Card/Glow.imageset/Glow@3x.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Card/Glow.imageset/Glow@3x.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Card/card01.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Card/card01.imageset/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Card/card01.imageset/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Card/card01.imageset/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Card/card01.imageset/card01.png b/Samples/PagingLayoutSamples/Assets.xcassets/Card/card01.imageset/card01.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Card/card01.imageset/card01.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Card/card01.imageset/card01.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Card/card01.imageset/card01@2x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Card/card01.imageset/card01@2x.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Card/card01.imageset/card01@2x.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Card/card01.imageset/card01@2x.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Card/card01.imageset/card01@3x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Card/card01.imageset/card01@3x.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Card/card01.imageset/card01@3x.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Card/card01.imageset/card01@3x.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Card/card02.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Card/card02.imageset/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Card/card02.imageset/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Card/card02.imageset/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Card/card02.imageset/card02.png b/Samples/PagingLayoutSamples/Assets.xcassets/Card/card02.imageset/card02.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Card/card02.imageset/card02.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Card/card02.imageset/card02.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Card/card02.imageset/card02@2x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Card/card02.imageset/card02@2x.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Card/card02.imageset/card02@2x.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Card/card02.imageset/card02@2x.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Card/card02.imageset/card02@3x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Card/card02.imageset/card02@3x.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Card/card02.imageset/card02@3x.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Card/card02.imageset/card02@3x.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Card/card06.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Card/card06.imageset/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Card/card06.imageset/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Card/card06.imageset/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Card/card06.imageset/card06.png b/Samples/PagingLayoutSamples/Assets.xcassets/Card/card06.imageset/card06.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Card/card06.imageset/card06.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Card/card06.imageset/card06.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Card/card06.imageset/card06@2x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Card/card06.imageset/card06@2x.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Card/card06.imageset/card06@2x.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Card/card06.imageset/card06@2x.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Card/card06.imageset/card06@3x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Card/card06.imageset/card06@3x.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Card/card06.imageset/card06@3x.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Card/card06.imageset/card06@3x.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Card/card07.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Card/card07.imageset/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Card/card07.imageset/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Card/card07.imageset/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Card/card07.imageset/card07.png b/Samples/PagingLayoutSamples/Assets.xcassets/Card/card07.imageset/card07.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Card/card07.imageset/card07.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Card/card07.imageset/card07.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Card/card07.imageset/card07@2x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Card/card07.imageset/card07@2x.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Card/card07.imageset/card07@2x.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Card/card07.imageset/card07@2x.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Card/card07.imageset/card07@3x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Card/card07.imageset/card07@3x.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Card/card07.imageset/card07@3x.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Card/card07.imageset/card07@3x.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Card/card08.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Card/card08.imageset/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Card/card08.imageset/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Card/card08.imageset/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Card/card08.imageset/card08.png b/Samples/PagingLayoutSamples/Assets.xcassets/Card/card08.imageset/card08.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Card/card08.imageset/card08.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Card/card08.imageset/card08.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Card/card08.imageset/card08@2x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Card/card08.imageset/card08@2x.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Card/card08.imageset/card08@2x.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Card/card08.imageset/card08@2x.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Card/card08.imageset/card08@3x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Card/card08.imageset/card08@3x.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Card/card08.imageset/card08@3x.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Card/card08.imageset/card08@3x.png diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Colors/Background.colorset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Colors/Background.colorset/Contents.json new file mode 100644 index 0000000..79225bf --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Colors/Background.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "243", + "green" : "243", + "red" : "243" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/CollectionViewPagingLayout/Assets.xcassets/Colors/ButtonBackTint.colorset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Colors/ButtonBackTint.colorset/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Colors/ButtonBackTint.colorset/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Colors/ButtonBackTint.colorset/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Colors/ButtonTint.colorset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Colors/ButtonTint.colorset/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Colors/ButtonTint.colorset/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Colors/ButtonTint.colorset/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Colors/CardsBackground.colorset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Colors/CardsBackground.colorset/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Colors/CardsBackground.colorset/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Colors/CardsBackground.colorset/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Colors/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Colors/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Colors/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Colors/Contents.json diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Colors/DesignerBackground.colorset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Colors/DesignerBackground.colorset/Contents.json new file mode 100644 index 0000000..e8e8d50 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Colors/DesignerBackground.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xD7", + "green" : "0x45", + "red" : "0x29" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/CollectionViewPagingLayout/Assets.xcassets/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Fruits/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Fruits/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Fruits/apple.colorset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Fruits/apple.colorset/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Fruits/apple.colorset/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Fruits/apple.colorset/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Fruits/appleImage.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Fruits/appleImage.imageset/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Fruits/appleImage.imageset/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Fruits/appleImage.imageset/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Fruits/appleImage.imageset/appleImage.png b/Samples/PagingLayoutSamples/Assets.xcassets/Fruits/appleImage.imageset/appleImage.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Fruits/appleImage.imageset/appleImage.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Fruits/appleImage.imageset/appleImage.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Fruits/appleImage.imageset/appleImage@2x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Fruits/appleImage.imageset/appleImage@2x.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Fruits/appleImage.imageset/appleImage@2x.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Fruits/appleImage.imageset/appleImage@2x.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Fruits/appleImage.imageset/appleImage@3x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Fruits/appleImage.imageset/appleImage@3x.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Fruits/appleImage.imageset/appleImage@3x.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Fruits/appleImage.imageset/appleImage@3x.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Fruits/blackberry.colorset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Fruits/blackberry.colorset/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Fruits/blackberry.colorset/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Fruits/blackberry.colorset/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Fruits/blackberryImage.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Fruits/blackberryImage.imageset/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Fruits/blackberryImage.imageset/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Fruits/blackberryImage.imageset/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Fruits/blackberryImage.imageset/blackberryImage.png b/Samples/PagingLayoutSamples/Assets.xcassets/Fruits/blackberryImage.imageset/blackberryImage.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Fruits/blackberryImage.imageset/blackberryImage.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Fruits/blackberryImage.imageset/blackberryImage.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Fruits/blackberryImage.imageset/blackberryImage@2x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Fruits/blackberryImage.imageset/blackberryImage@2x.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Fruits/blackberryImage.imageset/blackberryImage@2x.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Fruits/blackberryImage.imageset/blackberryImage@2x.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Fruits/blackberryImage.imageset/blackberryImage@3x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Fruits/blackberryImage.imageset/blackberryImage@3x.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Fruits/blackberryImage.imageset/blackberryImage@3x.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Fruits/blackberryImage.imageset/blackberryImage@3x.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Fruits/cherry.colorset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Fruits/cherry.colorset/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Fruits/cherry.colorset/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Fruits/cherry.colorset/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Fruits/cherryImage.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Fruits/cherryImage.imageset/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Fruits/cherryImage.imageset/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Fruits/cherryImage.imageset/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Fruits/cherryImage.imageset/cherryImage.png b/Samples/PagingLayoutSamples/Assets.xcassets/Fruits/cherryImage.imageset/cherryImage.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Fruits/cherryImage.imageset/cherryImage.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Fruits/cherryImage.imageset/cherryImage.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Fruits/cherryImage.imageset/cherryImage@2x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Fruits/cherryImage.imageset/cherryImage@2x.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Fruits/cherryImage.imageset/cherryImage@2x.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Fruits/cherryImage.imageset/cherryImage@2x.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Fruits/cherryImage.imageset/cherryImage@3x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Fruits/cherryImage.imageset/cherryImage@3x.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Fruits/cherryImage.imageset/cherryImage@3x.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Fruits/cherryImage.imageset/cherryImage@3x.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Fruits/mango.colorset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Fruits/mango.colorset/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Fruits/mango.colorset/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Fruits/mango.colorset/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Fruits/mangoImage.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Fruits/mangoImage.imageset/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Fruits/mangoImage.imageset/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Fruits/mangoImage.imageset/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Fruits/mangoImage.imageset/mangoImage.png b/Samples/PagingLayoutSamples/Assets.xcassets/Fruits/mangoImage.imageset/mangoImage.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Fruits/mangoImage.imageset/mangoImage.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Fruits/mangoImage.imageset/mangoImage.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Fruits/mangoImage.imageset/mangoImage@2x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Fruits/mangoImage.imageset/mangoImage@2x.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Fruits/mangoImage.imageset/mangoImage@2x.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Fruits/mangoImage.imageset/mangoImage@2x.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Fruits/mangoImage.imageset/mangoImage@3x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Fruits/mangoImage.imageset/mangoImage@3x.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Fruits/mangoImage.imageset/mangoImage@3x.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Fruits/mangoImage.imageset/mangoImage@3x.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Fruits/papaya.colorset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Fruits/papaya.colorset/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Fruits/papaya.colorset/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Fruits/papaya.colorset/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Fruits/papayaImage.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Fruits/papayaImage.imageset/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Fruits/papayaImage.imageset/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Fruits/papayaImage.imageset/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Fruits/papayaImage.imageset/papayaImage.png b/Samples/PagingLayoutSamples/Assets.xcassets/Fruits/papayaImage.imageset/papayaImage.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Fruits/papayaImage.imageset/papayaImage.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Fruits/papayaImage.imageset/papayaImage.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Fruits/papayaImage.imageset/papayaImage@2x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Fruits/papayaImage.imageset/papayaImage@2x.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Fruits/papayaImage.imageset/papayaImage@2x.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Fruits/papayaImage.imageset/papayaImage@2x.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Fruits/papayaImage.imageset/papayaImage@3x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Fruits/papayaImage.imageset/papayaImage@3x.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Fruits/papayaImage.imageset/papayaImage@3x.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Fruits/papayaImage.imageset/papayaImage@3x.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Fruits/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Gallery/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Fruits/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Gallery/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage01.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage01.imageset/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage01.imageset/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage01.imageset/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage01.imageset/galleryImage01.jpg b/Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage01.imageset/galleryImage01.jpg similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage01.imageset/galleryImage01.jpg rename to Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage01.imageset/galleryImage01.jpg diff --git a/CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage02.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage02.imageset/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage02.imageset/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage02.imageset/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage02.imageset/galleryImage02.jpg b/Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage02.imageset/galleryImage02.jpg similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage02.imageset/galleryImage02.jpg rename to Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage02.imageset/galleryImage02.jpg diff --git a/CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage03.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage03.imageset/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage03.imageset/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage03.imageset/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage03.imageset/galleryImage04.jpg b/Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage03.imageset/galleryImage04.jpg similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage03.imageset/galleryImage04.jpg rename to Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage03.imageset/galleryImage04.jpg diff --git a/CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage04.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage04.imageset/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage04.imageset/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage04.imageset/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage04.imageset/galleryImage06.jpg b/Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage04.imageset/galleryImage06.jpg similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage04.imageset/galleryImage06.jpg rename to Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage04.imageset/galleryImage06.jpg diff --git a/CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage05.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage05.imageset/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage05.imageset/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage05.imageset/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage05.imageset/galleryImage05.jpg b/Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage05.imageset/galleryImage05.jpg similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage05.imageset/galleryImage05.jpg rename to Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage05.imageset/galleryImage05.jpg diff --git a/CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage06.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage06.imageset/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage06.imageset/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage06.imageset/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage06.imageset/galleryImage03.jpg b/Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage06.imageset/galleryImage03.jpg similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage06.imageset/galleryImage03.jpg rename to Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage06.imageset/galleryImage03.jpg diff --git a/CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage07.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage07.imageset/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage07.imageset/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage07.imageset/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage07.imageset/galleryImage07.jpg b/Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage07.imageset/galleryImage07.jpg similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage07.imageset/galleryImage07.jpg rename to Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage07.imageset/galleryImage07.jpg diff --git a/CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage08.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage08.imageset/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage08.imageset/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage08.imageset/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage08.imageset/galleryImage08.jpg b/Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage08.imageset/galleryImage08.jpg similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Gallery/galleryImage08.imageset/galleryImage08.jpg rename to Samples/PagingLayoutSamples/Assets.xcassets/Gallery/galleryImage08.imageset/galleryImage08.jpg diff --git a/CollectionViewPagingLayout/Assets.xcassets/Gallery/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Icons/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Gallery/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Icons/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Icons/veganIcon.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Icons/veganIcon.imageset/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Icons/veganIcon.imageset/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Icons/veganIcon.imageset/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Icons/veganIcon.imageset/veganIcon.png b/Samples/PagingLayoutSamples/Assets.xcassets/Icons/veganIcon.imageset/veganIcon.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Icons/veganIcon.imageset/veganIcon.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Icons/veganIcon.imageset/veganIcon.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Icons/veganIcon.imageset/veganIcon@2x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Icons/veganIcon.imageset/veganIcon@2x.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Icons/veganIcon.imageset/veganIcon@2x.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Icons/veganIcon.imageset/veganIcon@2x.png diff --git a/CollectionViewPagingLayout/Assets.xcassets/Icons/veganIcon.imageset/veganIcon@3x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Icons/veganIcon.imageset/veganIcon@3x.png similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Icons/veganIcon.imageset/veganIcon@3x.png rename to Samples/PagingLayoutSamples/Assets.xcassets/Icons/veganIcon.imageset/veganIcon@3x.png diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Intro/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro01.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro01.imageset/Contents.json new file mode 100644 index 0000000..bc5d03e --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro01.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "intro01.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "intro01@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "intro01@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro01.imageset/intro01.png b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro01.imageset/intro01.png new file mode 100644 index 0000000..54a6978 Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro01.imageset/intro01.png differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro01.imageset/intro01@2x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro01.imageset/intro01@2x.png new file mode 100644 index 0000000..03a8452 Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro01.imageset/intro01@2x.png differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro01.imageset/intro01@3x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro01.imageset/intro01@3x.png new file mode 100644 index 0000000..28b4519 Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro01.imageset/intro01@3x.png differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro02.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro02.imageset/Contents.json new file mode 100644 index 0000000..883bf57 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro02.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "intro02.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "intro02@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "intro02@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro02.imageset/intro02.png b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro02.imageset/intro02.png new file mode 100644 index 0000000..10318a4 Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro02.imageset/intro02.png differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro02.imageset/intro02@2x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro02.imageset/intro02@2x.png new file mode 100644 index 0000000..6850a3a Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro02.imageset/intro02@2x.png differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro02.imageset/intro02@3x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro02.imageset/intro02@3x.png new file mode 100644 index 0000000..50fead2 Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro02.imageset/intro02@3x.png differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro03.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro03.imageset/Contents.json new file mode 100644 index 0000000..933341f --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro03.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "intro03.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "intro03@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "intro03@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro03.imageset/intro03.png b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro03.imageset/intro03.png new file mode 100644 index 0000000..5d85eeb Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro03.imageset/intro03.png differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro03.imageset/intro03@2x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro03.imageset/intro03@2x.png new file mode 100644 index 0000000..31d9f98 Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro03.imageset/intro03@2x.png differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro03.imageset/intro03@3x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro03.imageset/intro03@3x.png new file mode 100644 index 0000000..3017561 Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro03.imageset/intro03@3x.png differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro04.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro04.imageset/Contents.json new file mode 100644 index 0000000..0de9a80 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro04.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "intro04.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "intro04@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "intro04@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro04.imageset/intro04.png b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro04.imageset/intro04.png new file mode 100644 index 0000000..b7b7bdf Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro04.imageset/intro04.png differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro04.imageset/intro04@2x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro04.imageset/intro04@2x.png new file mode 100644 index 0000000..3b7ccea Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro04.imageset/intro04@2x.png differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro04.imageset/intro04@3x.png b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro04.imageset/intro04@3x.png new file mode 100644 index 0000000..a960ae5 Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/intro04.imageset/intro04@3x.png differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Intro/logoForIntro.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/logoForIntro.imageset/Contents.json new file mode 100644 index 0000000..3413cc0 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/logoForIntro.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "logoForIntro.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Intro/logoForIntro.imageset/logoForIntro.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/logoForIntro.imageset/logoForIntro.pdf new file mode 100644 index 0000000..aed3511 Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Intro/logoForIntro.imageset/logoForIntro.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Main/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Main/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Main/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Main/Logo.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Main/Logo.imageset/Contents.json new file mode 100644 index 0000000..93044cb --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Main/Logo.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Group.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Main/Logo.imageset/Group.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Main/Logo.imageset/Group.pdf new file mode 100644 index 0000000..e49a198 Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Main/Logo.imageset/Group.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Main/cards.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Main/cards.imageset/Contents.json new file mode 100644 index 0000000..dabde1d --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Main/cards.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "cards.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Main/cards.imageset/cards.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Main/cards.imageset/cards.pdf new file mode 100644 index 0000000..982fc8b Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Main/cards.imageset/cards.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Main/fruits.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Main/fruits.imageset/Contents.json new file mode 100644 index 0000000..7662bf5 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Main/fruits.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "fruits.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Main/fruits.imageset/fruits.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Main/fruits.imageset/fruits.pdf new file mode 100644 index 0000000..e47a8bd Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Main/fruits.imageset/fruits.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Main/gallery.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Main/gallery.imageset/Contents.json new file mode 100644 index 0000000..09e9764 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Main/gallery.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "gallery.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Main/gallery.imageset/gallery.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Main/gallery.imageset/gallery.pdf new file mode 100644 index 0000000..e58814d Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Main/gallery.imageset/gallery.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Main/layoutRect.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Main/layoutRect.imageset/Contents.json new file mode 100644 index 0000000..0fc0035 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Main/layoutRect.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "layoutRect.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Main/layoutRect.imageset/layoutRect.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Main/layoutRect.imageset/layoutRect.pdf new file mode 100644 index 0000000..1c2f017 Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Main/layoutRect.imageset/layoutRect.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Main/scale.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Main/scale.imageset/Contents.json new file mode 100644 index 0000000..20c4818 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Main/scale.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "scale.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Main/scale.imageset/scale.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Main/scale.imageset/scale.pdf new file mode 100644 index 0000000..5d329bc Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Main/scale.imageset/scale.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Main/scaleWhite.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Main/scaleWhite.imageset/Contents.json new file mode 100644 index 0000000..f006886 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Main/scaleWhite.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "scaleWhite.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Main/scaleWhite.imageset/scaleWhite.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Main/scaleWhite.imageset/scaleWhite.pdf new file mode 100644 index 0000000..ab7f372 Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Main/scaleWhite.imageset/scaleWhite.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Main/snapshot.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Main/snapshot.imageset/Contents.json new file mode 100644 index 0000000..541b806 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Main/snapshot.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "snapshot.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Main/snapshot.imageset/snapshot.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Main/snapshot.imageset/snapshot.pdf new file mode 100644 index 0000000..5b8b567 Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Main/snapshot.imageset/snapshot.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Main/snapshotWhite.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Main/snapshotWhite.imageset/Contents.json new file mode 100644 index 0000000..ffb59cc --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Main/snapshotWhite.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "snapshotWhite.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Main/snapshotWhite.imageset/snapshotWhite.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Main/snapshotWhite.imageset/snapshotWhite.pdf new file mode 100644 index 0000000..0f9c503 Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Main/snapshotWhite.imageset/snapshotWhite.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Main/stack.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Main/stack.imageset/Contents.json new file mode 100644 index 0000000..f4550db --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Main/stack.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "stack.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Main/stack.imageset/stack.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Main/stack.imageset/stack.pdf new file mode 100644 index 0000000..17f394d Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Main/stack.imageset/stack.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Main/stackWhite.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Main/stackWhite.imageset/Contents.json new file mode 100644 index 0000000..b1d12e0 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Main/stackWhite.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "stackWhite.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Main/stackWhite.imageset/stackWhite.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Main/stackWhite.imageset/stackWhite.pdf new file mode 100644 index 0000000..2a8910d Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Main/stackWhite.imageset/stackWhite.pdf differ diff --git a/CollectionViewPagingLayout/Assets.xcassets/Icons/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/Contents.json similarity index 100% rename from CollectionViewPagingLayout/Assets.xcassets/Icons/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Shapes/Contents.json diff --git a/CollectionViewPagingLayout/Assets.xcassets/Colors/Background.colorset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/babyBlue.colorset/Contents.json similarity index 75% rename from CollectionViewPagingLayout/Assets.xcassets/Colors/Background.colorset/Contents.json rename to Samples/PagingLayoutSamples/Assets.xcassets/Shapes/babyBlue.colorset/Contents.json index 377daa5..fe49bee 100644 --- a/CollectionViewPagingLayout/Assets.xcassets/Colors/Background.colorset/Contents.json +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/babyBlue.colorset/Contents.json @@ -9,10 +9,10 @@ "color" : { "color-space" : "srgb", "components" : { - "red" : "0xF3", + "red" : "0x4E", "alpha" : "1.000", - "blue" : "0xF3", - "green" : "0xF3" + "blue" : "0xF2", + "green" : "0xFF" } } } diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/chartreuseYellow.colorset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/chartreuseYellow.colorset/Contents.json new file mode 100644 index 0000000..a29ef54 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/chartreuseYellow.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0xEA", + "alpha" : "1.000", + "blue" : "0x00", + "green" : "0xFF" + } + } + } + ] +} \ No newline at end of file diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/copyButton.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/copyButton.imageset/Contents.json new file mode 100644 index 0000000..fcee656 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/copyButton.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "copyButton.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/copyButton.imageset/copyButton.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/copyButton.imageset/copyButton.pdf new file mode 100644 index 0000000..810d75d Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/copyButton.imageset/copyButton.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/downloadButton.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/downloadButton.imageset/Contents.json new file mode 100644 index 0000000..8d759f6 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/downloadButton.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "downloadButton.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/downloadButton.imageset/downloadButton.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/downloadButton.imageset/downloadButton.pdf new file mode 100644 index 0000000..e08289b Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/downloadButton.imageset/downloadButton.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/grayArrowLeft.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/grayArrowLeft.imageset/Contents.json new file mode 100644 index 0000000..2f4fa61 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/grayArrowLeft.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "grayArrowLeft.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/grayArrowLeft.imageset/grayArrowLeft.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/grayArrowLeft.imageset/grayArrowLeft.pdf new file mode 100644 index 0000000..afe7671 Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/grayArrowLeft.imageset/grayArrowLeft.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/grayArrowRight.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/grayArrowRight.imageset/Contents.json new file mode 100644 index 0000000..383333d --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/grayArrowRight.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "grayArrowRight.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/grayArrowRight.imageset/grayArrowRight.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/grayArrowRight.imageset/grayArrowRight.pdf new file mode 100644 index 0000000..472df1d Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/grayArrowRight.imageset/grayArrowRight.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/helpButton.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/helpButton.imageset/Contents.json new file mode 100644 index 0000000..c29f367 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/helpButton.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "helpButton.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/helpButton.imageset/helpButton.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/helpButton.imageset/helpButton.pdf new file mode 100644 index 0000000..59df160 Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/helpButton.imageset/helpButton.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/matisse.colorset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/matisse.colorset/Contents.json new file mode 100644 index 0000000..e15b7cc --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/matisse.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0x1D", + "alpha" : "1.000", + "blue" : "0xAA", + "green" : "0x6A" + } + } + } + ] +} \ No newline at end of file diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/mayaBlue.colorset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/mayaBlue.colorset/Contents.json new file mode 100644 index 0000000..89e4aee --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/mayaBlue.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0x50", + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xCC" + } + } + } + ] +} \ No newline at end of file diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/pastelRed.colorset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/pastelRed.colorset/Contents.json new file mode 100644 index 0000000..1e4c8f5 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/pastelRed.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0xFF", + "alpha" : "1.000", + "blue" : "0x66", + "green" : "0x6D" + } + } + } + ] +} \ No newline at end of file diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_blur.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_blur.imageset/Contents.json new file mode 100644 index 0000000..012dafa --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_blur.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "scale_blur.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_blur.imageset/scale_blur.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_blur.imageset/scale_blur.pdf new file mode 100644 index 0000000..61b5e0f Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_blur.imageset/scale_blur.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_coverflow.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_coverflow.imageset/Contents.json new file mode 100644 index 0000000..3f08498 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_coverflow.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "scale_coverflow.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_coverflow.imageset/scale_coverflow.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_coverflow.imageset/scale_coverflow.pdf new file mode 100644 index 0000000..9a5f65f Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_coverflow.imageset/scale_coverflow.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_cylinder.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_cylinder.imageset/Contents.json new file mode 100644 index 0000000..8af22bb --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_cylinder.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "scale_cylinder.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_cylinder.imageset/scale_cylinder.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_cylinder.imageset/scale_cylinder.pdf new file mode 100644 index 0000000..25f0a52 Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_cylinder.imageset/scale_cylinder.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_invertedcylinder.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_invertedcylinder.imageset/Contents.json new file mode 100644 index 0000000..87c3212 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_invertedcylinder.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "scale_invertedcylinder.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_invertedcylinder.imageset/scale_invertedcylinder.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_invertedcylinder.imageset/scale_invertedcylinder.pdf new file mode 100644 index 0000000..566288e Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_invertedcylinder.imageset/scale_invertedcylinder.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_normal.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_normal.imageset/Contents.json new file mode 100644 index 0000000..94bb2f4 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_normal.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "scale_normal.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_normal.imageset/scale_normal.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_normal.imageset/scale_normal.pdf new file mode 100644 index 0000000..8c9f0c9 Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_normal.imageset/scale_normal.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_rotary.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_rotary.imageset/Contents.json new file mode 100644 index 0000000..16efe86 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_rotary.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "scale_rotary.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_rotary.imageset/scale_rotary.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_rotary.imageset/scale_rotary.pdf new file mode 100644 index 0000000..0ffcc9e Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/scale/scale_rotary.imageset/scale_rotary.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_bars.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_bars.imageset/Contents.json new file mode 100644 index 0000000..29db977 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_bars.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "snapshot_bars.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_bars.imageset/snapshot_bars.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_bars.imageset/snapshot_bars.pdf new file mode 100644 index 0000000..80f1558 Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_bars.imageset/snapshot_bars.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_chess.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_chess.imageset/Contents.json new file mode 100644 index 0000000..bc1b554 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_chess.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "snapshot_chess.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_chess.imageset/snapshot_chess.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_chess.imageset/snapshot_chess.pdf new file mode 100644 index 0000000..509544e Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_chess.imageset/snapshot_chess.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_fade.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_fade.imageset/Contents.json new file mode 100644 index 0000000..6abc6f2 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_fade.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "snapshot_fade.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_fade.imageset/snapshot_fade.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_fade.imageset/snapshot_fade.pdf new file mode 100644 index 0000000..c31b276 Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_fade.imageset/snapshot_fade.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_grid.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_grid.imageset/Contents.json new file mode 100644 index 0000000..0804e86 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_grid.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "snapshot_grid.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_grid.imageset/snapshot_grid.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_grid.imageset/snapshot_grid.pdf new file mode 100644 index 0000000..f9ead7e Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_grid.imageset/snapshot_grid.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_lines.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_lines.imageset/Contents.json new file mode 100644 index 0000000..a68b58e --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_lines.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "snapshot_lines.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_lines.imageset/snapshot_lines.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_lines.imageset/snapshot_lines.pdf new file mode 100644 index 0000000..75a96b7 Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_lines.imageset/snapshot_lines.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_puzzle.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_puzzle.imageset/Contents.json new file mode 100644 index 0000000..099177c --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_puzzle.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "snapshot_puzzle.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_puzzle.imageset/snapshot_puzzle.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_puzzle.imageset/snapshot_puzzle.pdf new file mode 100644 index 0000000..887554c Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_puzzle.imageset/snapshot_puzzle.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_space.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_space.imageset/Contents.json new file mode 100644 index 0000000..4505279 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_space.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "snapshot_space.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_space.imageset/snapshot_space.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_space.imageset/snapshot_space.pdf new file mode 100644 index 0000000..98f7b71 Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_space.imageset/snapshot_space.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_tiles.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_tiles.imageset/Contents.json new file mode 100644 index 0000000..8e7a432 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_tiles.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "snapshot_tiles.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_tiles.imageset/snapshot_tiles.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_tiles.imageset/snapshot_tiles.pdf new file mode 100644 index 0000000..a29ed82 Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/snapshot/snapshot_tiles.imageset/snapshot_tiles.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_blur.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_blur.imageset/Contents.json new file mode 100644 index 0000000..d73452a --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_blur.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "stack_blur.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_blur.imageset/stack_blur.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_blur.imageset/stack_blur.pdf new file mode 100644 index 0000000..f0d2463 Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_blur.imageset/stack_blur.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_prespective.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_prespective.imageset/Contents.json new file mode 100644 index 0000000..589c7cb --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_prespective.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "stack_prespective.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_prespective.imageset/stack_prespective.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_prespective.imageset/stack_prespective.pdf new file mode 100644 index 0000000..69f19ea Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_prespective.imageset/stack_prespective.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_reverse.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_reverse.imageset/Contents.json new file mode 100644 index 0000000..1be21b8 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_reverse.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "stack_reverse.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_reverse.imageset/stack_reverse.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_reverse.imageset/stack_reverse.pdf new file mode 100644 index 0000000..b8704a5 Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_reverse.imageset/stack_reverse.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_rotary.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_rotary.imageset/Contents.json new file mode 100644 index 0000000..4055299 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_rotary.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "stack_rotary.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_rotary.imageset/stack_rotary.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_rotary.imageset/stack_rotary.pdf new file mode 100644 index 0000000..dc095c1 Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_rotary.imageset/stack_rotary.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_transparent.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_transparent.imageset/Contents.json new file mode 100644 index 0000000..3d9301e --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_transparent.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "stack_transparent.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_transparent.imageset/stack_transparent.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_transparent.imageset/stack_transparent.pdf new file mode 100644 index 0000000..0388fcb Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_transparent.imageset/stack_transparent.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_vortex.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_vortex.imageset/Contents.json new file mode 100644 index 0000000..430572c --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_vortex.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "stack_vortex.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_vortex.imageset/stack_vortex.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_vortex.imageset/stack_vortex.pdf new file mode 100644 index 0000000..daaca08 Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/stacks/stack_vortex.imageset/stack_vortex.pdf differ diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/supernova.colorset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/supernova.colorset/Contents.json new file mode 100644 index 0000000..4c46fe5 --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/supernova.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0xFF", + "alpha" : "1.000", + "blue" : "0x00", + "green" : "0xC7" + } + } + } + ] +} \ No newline at end of file diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/textPlaceholder.imageset/Contents.json b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/textPlaceholder.imageset/Contents.json new file mode 100644 index 0000000..0f446df --- /dev/null +++ b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/textPlaceholder.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "textPlaceholder.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/textPlaceholder.imageset/textPlaceholder.pdf b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/textPlaceholder.imageset/textPlaceholder.pdf new file mode 100644 index 0000000..8211036 Binary files /dev/null and b/Samples/PagingLayoutSamples/Assets.xcassets/Shapes/textPlaceholder.imageset/textPlaceholder.pdf differ 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 new file mode 100644 index 0000000..a2ebcf3 --- /dev/null +++ b/Samples/PagingLayoutSamples/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Samples/PagingLayoutSamples/Catalyst.swift b/Samples/PagingLayoutSamples/Catalyst.swift new file mode 100644 index 0000000..4d43017 --- /dev/null +++ b/Samples/PagingLayoutSamples/Catalyst.swift @@ -0,0 +1,26 @@ +// +// Catalyst.swift +// PagingLayoutSamples +// +// Created by Amir on 05/05/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import Foundation + +enum Catalyst { + static var bridge: AppKitBridge? = { + #if targetEnvironment(macCatalyst) + guard let url = Bundle.main.builtInPlugInsURL?.appendingPathComponent("AppKitGlue.bundle"), + let bundle = Bundle(path: url.path), + bundle.load() else { + return nil + } + guard let principalClass = bundle.principalClass as? NSObject.Type else { return nil } + guard let appKit = principalClass.init() as? AppKitBridge else { return nil } + return appKit + #else + return nil + #endif + }() +} diff --git a/Samples/PagingLayoutSamples/CustomViews/GradientView.swift b/Samples/PagingLayoutSamples/CustomViews/GradientView.swift new file mode 100644 index 0000000..fcbf7d2 --- /dev/null +++ b/Samples/PagingLayoutSamples/CustomViews/GradientView.swift @@ -0,0 +1,36 @@ +// +// GradientView.swift +// CollectionViewPagingLayout +// +// Created by Amir on 15/02/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit + +class GradientView: UIView { + + // MARK: Properties + + override class var layerClass: Swift.AnyClass { + CAGradientLayer.self + } + + override var layer: CAGradientLayer { + (super.layer as? CAGradientLayer)! + } + + + // MARK: Public functions + + public func set(colors: [UIColor]) { + backgroundColor = .clear + layer.colors = colors.map { $0.cgColor } + } + + public func set(startPoint: CGPoint, endPoint: CGPoint) { + layer.startPoint = startPoint + layer.endPoint = endPoint + } + +} diff --git a/CollectionViewPagingLayout/CustomViews/PageControlView.swift b/Samples/PagingLayoutSamples/CustomViews/PageControlView.swift similarity index 100% rename from CollectionViewPagingLayout/CustomViews/PageControlView.swift rename to Samples/PagingLayoutSamples/CustomViews/PageControlView.swift diff --git a/CollectionViewPagingLayout/CustomViews/PriceTagView/PriceTagView.swift b/Samples/PagingLayoutSamples/CustomViews/PriceTagView/PriceTagView.swift similarity index 100% rename from CollectionViewPagingLayout/CustomViews/PriceTagView/PriceTagView.swift rename to Samples/PagingLayoutSamples/CustomViews/PriceTagView/PriceTagView.swift diff --git a/CollectionViewPagingLayout/CustomViews/PriceTagView/PriceTagView.xib b/Samples/PagingLayoutSamples/CustomViews/PriceTagView/PriceTagView.xib similarity index 98% rename from CollectionViewPagingLayout/CustomViews/PriceTagView/PriceTagView.xib rename to Samples/PagingLayoutSamples/CustomViews/PriceTagView/PriceTagView.xib index 82125c2..821566d 100644 --- a/CollectionViewPagingLayout/CustomViews/PriceTagView/PriceTagView.xib +++ b/Samples/PagingLayoutSamples/CustomViews/PriceTagView/PriceTagView.xib @@ -8,7 +8,7 @@ - + diff --git a/CollectionViewPagingLayout/CustomViews/QuantityControllerView/QuantityControllerView.swift b/Samples/PagingLayoutSamples/CustomViews/QuantityControllerView/QuantityControllerView.swift similarity index 92% rename from CollectionViewPagingLayout/CustomViews/QuantityControllerView/QuantityControllerView.swift rename to Samples/PagingLayoutSamples/CustomViews/QuantityControllerView/QuantityControllerView.swift index 7c35cc0..f36dc9e 100644 --- a/CollectionViewPagingLayout/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) } @@ -62,8 +62,8 @@ class QuantityControllerView: UIView, NibBased { let animation: CABasicAnimation = CABasicAnimation(keyPath: "transform") var newTransform = CATransform3DIdentity - newTransform.m34 = -0.004; - newTransform = CATransform3DRotate(newTransform, (isDecrease ? -1 : 1) * .pi/6, 0, 1, 0) + newTransform.m34 = -0.004 + newTransform = CATransform3DRotate(newTransform, (isDecrease ? -1 : 1) * .pi / 6, 0, 1, 0) CATransaction.begin() if !reset { @@ -80,9 +80,8 @@ class QuantityControllerView: UIView, NibBased { animation.fromValue = CATransform3DIdentity animation.toValue = newTransform } - container.layer.transform = animation.toValue as! CATransform3D + container.layer.transform = (animation.toValue as? CATransform3D)! container.layer.add(animation, forKey: nil) CATransaction.commit() } } - diff --git a/CollectionViewPagingLayout/CustomViews/QuantityControllerView/QuantityControllerView.xib b/Samples/PagingLayoutSamples/CustomViews/QuantityControllerView/QuantityControllerView.xib similarity index 98% rename from CollectionViewPagingLayout/CustomViews/QuantityControllerView/QuantityControllerView.xib rename to Samples/PagingLayoutSamples/CustomViews/QuantityControllerView/QuantityControllerView.xib index 9b1f100..a48fb4d 100644 --- a/CollectionViewPagingLayout/CustomViews/QuantityControllerView/QuantityControllerView.xib +++ b/Samples/PagingLayoutSamples/CustomViews/QuantityControllerView/QuantityControllerView.xib @@ -9,7 +9,7 @@ - + diff --git a/Samples/PagingLayoutSamples/Info.plist b/Samples/PagingLayoutSamples/Info.plist new file mode 100644 index 0000000..11347b9 --- /dev/null +++ b/Samples/PagingLayoutSamples/Info.plist @@ -0,0 +1,48 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + LSApplicationCategoryType + public.app-category.developer-tools + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UIRequiresFullScreen + + UIStatusBarStyle + UIStatusBarStyleLightContent + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIUserInterfaceStyle + Light + + diff --git a/CollectionViewPagingLayout/Models/Card.swift b/Samples/PagingLayoutSamples/Models/Card.swift similarity index 100% rename from CollectionViewPagingLayout/Models/Card.swift rename to Samples/PagingLayoutSamples/Models/Card.swift diff --git a/CollectionViewPagingLayout/Models/Fruit.swift b/Samples/PagingLayoutSamples/Models/Fruit.swift similarity index 100% rename from CollectionViewPagingLayout/Models/Fruit.swift rename to Samples/PagingLayoutSamples/Models/Fruit.swift diff --git a/Samples/PagingLayoutSamples/Models/LayoutDesignerIntroInfo.swift b/Samples/PagingLayoutSamples/Models/LayoutDesignerIntroInfo.swift new file mode 100644 index 0000000..174e68a --- /dev/null +++ b/Samples/PagingLayoutSamples/Models/LayoutDesignerIntroInfo.swift @@ -0,0 +1,18 @@ +// +// LayoutDesignerIntroInfo.swift +// PagingLayoutSamples +// +// Created by Amir on 09/07/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import Foundation + +struct LayoutDesignerIntroInfo { + let title: String + let headerImageName: String? + let imageName: String? + let description: String + var leftButtonTitle: String + let rightButtonTitle: String +} diff --git a/CollectionViewPagingLayout/Models/Photo.swift b/Samples/PagingLayoutSamples/Models/Photo.swift similarity index 100% rename from CollectionViewPagingLayout/Models/Photo.swift rename to Samples/PagingLayoutSamples/Models/Photo.swift diff --git a/Samples/PagingLayoutSamples/Models/Shape.swift b/Samples/PagingLayoutSamples/Models/Shape.swift new file mode 100644 index 0000000..46c24cd --- /dev/null +++ b/Samples/PagingLayoutSamples/Models/Shape.swift @@ -0,0 +1,16 @@ +// +// Shape.swift +// CollectionViewPagingLayout +// +// Created by Amir Khorsandi on 2/16/20. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +struct Shape: Identifiable { + let name: String + let iconName: String + + var id: String { + name + } +} diff --git a/Samples/PagingLayoutSamples/Models/ShapeLayout.swift b/Samples/PagingLayoutSamples/Models/ShapeLayout.swift new file mode 100644 index 0000000..077fbf4 --- /dev/null +++ b/Samples/PagingLayoutSamples/Models/ShapeLayout.swift @@ -0,0 +1,76 @@ +// +// ShapeLayout.swift +// CollectionViewPagingLayout +// +// Created by Amir Khorsandi on 2/16/20. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit +import CollectionViewPagingLayout + +enum ShapeLayout { + case scaleInvertedCylinder + case scaleCylinder + case scaleCoverFlow + case scaleRotary + case scaleLinear + case scaleEaseIn + case scaleEaseOut + case scaleBlur + + case stackTransparent + case stackPerspective + case stackRotary + case stackVortex + case stackReverse + case stackBlur + + case snapshotGrid + case snapshotSpace + case snapshotChess + case snapshotTiles + case snapshotLines + case snapshotBars + case snapshotPuzzle + case snapshotFade +} + +extension ShapeLayout { + static let scaleLayouts: [ShapeLayout] = [ + .scaleInvertedCylinder, + .scaleCylinder, + .scaleCoverFlow, + .scaleRotary, + .scaleLinear, + .scaleEaseIn, + .scaleEaseOut, + .scaleBlur + ] + + static let stackLayouts: [ShapeLayout] = [ + .stackVortex, + .stackRotary, + .stackTransparent, + .stackBlur, + .stackReverse, + .stackPerspective + ] + + static let snapshotLayouts: [ShapeLayout] = [ + .snapshotBars, + .snapshotFade, + .snapshotGrid, + .snapshotChess, + .snapshotLines, + .snapshotSpace, + .snapshotTiles, + .snapshotPuzzle + ] +} + +extension Array where Element == ShapeLayout { + static var scale: [ShapeLayout] { ShapeLayout.scaleLayouts } + static var stack: [ShapeLayout] { ShapeLayout.stackLayouts } + static var snapshot: [ShapeLayout] { ShapeLayout.snapshotLayouts } +} diff --git a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Code/LayoutDesignerCodePreviewViewController.swift b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Code/LayoutDesignerCodePreviewViewController.swift new file mode 100644 index 0000000..4ff69f0 --- /dev/null +++ b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Code/LayoutDesignerCodePreviewViewController.swift @@ -0,0 +1,133 @@ +// +// LayoutDesignerCodePreviewViewController.swift +// PagingLayoutSamples +// +// Created by Amir on 26/06/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import Foundation +import UIKit +import Splash + +protocol LayoutDesignerCodePreviewViewControllerDelegate: AnyObject { + func layoutDesignerCodePreviewViewController(_ vc: LayoutDesignerCodePreviewViewController, onHelpButtonTouched button: UIButton) +} + + +class LayoutDesignerCodePreviewViewController: UIViewController, NibBased, ViewModelBased { + + // MARK: Properties + + var viewModel: LayoutDesignerCodePreviewViewModel! { + didSet { + refreshViews() + } + } + weak var delegate: LayoutDesignerCodePreviewViewControllerDelegate? + + @IBOutlet private weak var codeTextView: UITextView! + @IBOutlet private weak var copyButton: UIButton! + @IBOutlet private weak var saveButton: UIButton! + @IBOutlet private weak var helpButton: UIButton! + @IBOutlet private weak var codeModeSegmentedControl: UISegmentedControl! + + + // MARK: UIViewController + + override func viewDidLoad() { + super.viewDidLoad() + configureViews() + } + + + // MARK: Listener + + @IBAction private func copyButtonTouched() { + let pasteBoard = UIPasteboard.general + pasteBoard.string = codeTextView.text + } + + @IBAction private func saveButtonTouched() { + guard let exportURL = viewModel.sampleProjectTempURL else { return } + viewModel.generateSampleProject(type: selectedCodeType()) + let controller = UIDocumentPickerViewController(forExporting: [exportURL]) + controller.delegate = self + present(controller, animated: true) + } + + @IBAction private func onHelpButtonTouched() { + delegate?.layoutDesignerCodePreviewViewController(self, onHelpButtonTouched: helpButton) + } + + @IBAction private func codeTypeChanged() { + refreshViews() + } + + + // MARK: Private functions + + private func configureViews() { + configureTextView() + configureButtons() + configureCodeTypeSegmentedControl() + } + + private func configureButtons() { + [saveButton, copyButton, helpButton].forEach { + $0?.layer.cornerRadius = 8 + } + } + + private func configureCodeTypeSegmentedControl() { + codeModeSegmentedControl.backgroundColor = UIColor.black.withAlphaComponent(0.4) + codeModeSegmentedControl.selectedSegmentTintColor = UIColor.white.withAlphaComponent(0.4) + 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(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 + } + +} + + +extension LayoutDesignerCodePreviewViewController: UIDocumentPickerDelegate { + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + 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 new file mode 100644 index 0000000..1d8671d --- /dev/null +++ b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Code/LayoutDesignerCodePreviewViewController.xib @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Code/LayoutDesignerCodePreviewViewModel.swift b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Code/LayoutDesignerCodePreviewViewModel.swift new file mode 100644 index 0000000..ba86f68 --- /dev/null +++ b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Code/LayoutDesignerCodePreviewViewModel.swift @@ -0,0 +1,278 @@ +// +// LayoutDesignerCodePreviewViewModel.swift +// PagingLayoutSamples +// +// Created by Amir on 26/06/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +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: 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(type: CodeType) -> NSAttributedString { + highlighter.highlight(getCode(type: type)) + } + + 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 + } + 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 + + 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"), + to: sampleProjectTempURL.appendingPathComponent("PagingLayout.xcodeproj")) + + let projectURL = sampleProjectTempURL.appendingPathComponent("PagingLayout.xcodeproj") + + try? FileManager.default.moveItem(at: projectURL.appendingPathComponent("project.pbxproj_sample"), + to: projectURL.appendingPathComponent("project.pbxproj")) + + try? FileManager.default.moveItem(at: projectURL.appendingPathComponent("project.xcworkspace_sample"), + to: projectURL.appendingPathComponent("project.xcworkspace")) + + try? FileManager.default.moveItem(at: baseProjectPath.appendingPathComponent("info_sample.plist"), + to: baseProjectPath.appendingPathComponent("info.plist")) + } + + func removeSampleProject() { + guard let sampleProjectTempURL = sampleProjectTempURL else { + return + } + try? FileManager.default.removeItem(at: sampleProjectTempURL) + } + + + // MARK: Private functions + + 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) } ?? "" + + return """ + import UIKit + + // Make sure you added this dependency to your project + // More info at https://bit.ly/CVPagingLayout + import CollectionViewPagingLayout + + // The cell class needs to conform to `\(viewProtocolName)` protocol + // to be able to provide the transform options + class MyCell: UICollectionViewCell, \(viewProtocolName) { + + \(code.replacingOccurrences(of: "\n", with: "\n ")) + + // 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) + } + } + + // 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 + ) + } + + } + """ + } + +} diff --git a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Intro/LayoutDesignerIntroCell.swift b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Intro/LayoutDesignerIntroCell.swift new file mode 100644 index 0000000..ac161ef --- /dev/null +++ b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Intro/LayoutDesignerIntroCell.swift @@ -0,0 +1,123 @@ +// +// LayoutDesignerIntroCell.swift +// PagingLayoutSamples +// +// Created by Amir on 09/07/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit +import CollectionViewPagingLayout + +protocol LayoutDesignerIntroCellDelegate: AnyObject { + func layoutDesignerIntroCell(_ cell: LayoutDesignerIntroCell, onLeftButtonTouched button: UIButton) + func layoutDesignerIntroCell(_ cell: LayoutDesignerIntroCell, onRightButtonTouched button: UIButton) +} + + +class LayoutDesignerIntroCell: UICollectionViewCell, NibBased { + + // MARK: Properties + + var introInfo: LayoutDesignerIntroInfo? { + didSet { + updateViews() + } + } + weak var delegate: LayoutDesignerIntroCellDelegate? + + @IBOutlet private weak var containerView: UIView! + @IBOutlet private weak var stackView: UIStackView! + @IBOutlet private weak var titleLabel: UILabel! + @IBOutlet private weak var headerImageView: UIImageView! + @IBOutlet private weak var imageView: UIImageView! + @IBOutlet private weak var leftButton: UIButton! + @IBOutlet private weak var rightButton: UIButton! + @IBOutlet private weak var descriptionLabel: UILabel! + + + // MARK: Lifecycle + + override func awakeFromNib() { + super.awakeFromNib() + setupViews() + } + + + // MARK: Listeners + + @IBAction private func onLeftButtonTouched() { + delegate?.layoutDesignerIntroCell(self, onLeftButtonTouched: leftButton) + } + + @IBAction private func onRightButtonTouched() { + delegate?.layoutDesignerIntroCell(self, onRightButtonTouched: rightButton) + } + + + // MARK: Private functions + + private func setupViews() { + + } + + private func updateViews() { + guard let info = introInfo else { return } + titleLabel.text = info.title + if let headerImageName = info.headerImageName { + headerImageView.isHidden = false + headerImageView.image = UIImage(named: headerImageName) + } else { + headerImageView.isHidden = true + } + if let imageName = info.imageName { + imageView.isHidden = false + imageView.image = UIImage(named: imageName) + } else { + imageView.isHidden = true + } + descriptionLabel.text = info.description + + leftButton.setTitle(info.leftButtonTitle, for: .normal) + rightButton.setTitle(info.rightButtonTitle, for: .normal) + } + +} + + +extension LayoutDesignerIntroCell: ScaleTransformView { + + var scaleOptions: ScaleTransformViewOptions { + ScaleTransformViewOptions( + minScale: 0.00, + maxScale: 1.35, + scaleRatio: 0.39, + translationRatio: .init(x: 0.10, y: 0.10), + minTranslationRatio: .init(x: -1.00, y: 0.00), + maxTranslationRatio: .init(x: 1.00, y: 1.00), + keepVerticalSpacingEqual: true, + keepHorizontalSpacingEqual: true, + scaleCurve: .linear, + translationCurve: .linear, + shadowEnabled: false, + rotation3d: .init( + angle: 0.60, + minAngle: -1.05, + maxAngle: 1.05, + x: 0.00, + y: 0.00, + z: 1.00, + m34: 0 + ), + translation3d: .init( + translateRatios: (0.90, 0.10, 0.00), + minTranslateRatios: (-3.00, -0.80, -0.30), + maxTranslateRatios: (3.00, 0.80, -0.30) + ) + ) + } + + var scalableView: UIView { + stackView + } +} diff --git a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Intro/LayoutDesignerIntroCell.xib b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Intro/LayoutDesignerIntroCell.xib new file mode 100644 index 0000000..bcc5001 --- /dev/null +++ b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Intro/LayoutDesignerIntroCell.xib @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Intro/LayoutDesignerIntroViewController.swift b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Intro/LayoutDesignerIntroViewController.swift new file mode 100644 index 0000000..c85f585 --- /dev/null +++ b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Intro/LayoutDesignerIntroViewController.swift @@ -0,0 +1,121 @@ +// +// LayoutDesignerIntroViewController.swift +// PagingLayoutSamples +// +// Created by Amir on 09/07/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit +import CollectionViewPagingLayout + +class LayoutDesignerIntroViewController: UIViewController { + + // MARK: Properties + + var viewModel: LayoutDesignerIntroViewModel? { + didSet { + refreshViews() + } + } + private var collectionView: UICollectionView! + + + // MARK: UIViewController + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .clear + setupCollectionView() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + open() + } + + + // MARK: Private functions + + private func setupCollectionView() { + collectionView = UICollectionView(frame: .zero, collectionViewLayout: CollectionViewPagingLayout()) + collectionView.isPagingEnabled = true + collectionView.register(LayoutDesignerIntroCell.self) + collectionView.dataSource = self + collectionView.layer.cornerRadius = 44 + collectionView.clipsToBounds = true + collectionView.backgroundColor = .white + collectionView.isScrollEnabled = false + collectionView.showsHorizontalScrollIndicator = false + collectionView.alpha = 0 + + let darkOverlay = UIView() + darkOverlay.backgroundColor = UIColor.black.withAlphaComponent(0.59) + view.fill(with: darkOverlay) + let tap = UITapGestureRecognizer(target: self, action: #selector(close)) + darkOverlay.addGestureRecognizer(tap) + darkOverlay.alpha = 0 + + view.addSubview(collectionView) + collectionView.widthAnchor.constraint(equalToConstant: 950).isActive = true + collectionView.heightAnchor.constraint(equalToConstant: 600).isActive = true + darkOverlay.center(to: collectionView) + } + + private func refreshViews() { + collectionView?.reloadData() + } + + private func open() { + UIView.animate(withDuration: 0.25) { [weak self] in + self?.view.subviews.forEach { + $0.alpha = 1 + } + } + } + + @objc private func close() { + UIView.animate(withDuration: 0.25, animations: { [weak self] in + self?.view.subviews.forEach { + $0.alpha = 0 + } + }, completion: { [weak self] _ in + self?.view.removeFromSuperview() + self?.didMove(toParent: nil) + }) + } + +} + + +extension LayoutDesignerIntroViewController: UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + viewModel?.introPages.count ?? 0 + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(for: indexPath) as LayoutDesignerIntroCell + cell.introInfo = viewModel?.introPages[indexPath.row] + cell.delegate = self + return cell + } +} + + +extension LayoutDesignerIntroViewController: LayoutDesignerIntroCellDelegate { + func layoutDesignerIntroCell(_ cell: LayoutDesignerIntroCell, onLeftButtonTouched button: UIButton) { + if collectionView.indexPath(for: cell)?.item == 0 { + close() + return + } + (collectionView.collectionViewLayout as? CollectionViewPagingLayout)?.goToPreviousPage() + } + func layoutDesignerIntroCell(_ cell: LayoutDesignerIntroCell, onRightButtonTouched button: UIButton) { + if collectionView.indexPath(for: cell)?.item == viewModel.map({ $0.introPages.count - 1 }) { + close() + return + } + (collectionView.collectionViewLayout as? CollectionViewPagingLayout)?.goToNextPage() + } +} diff --git a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Intro/LayoutDesignerIntroViewModel.swift b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Intro/LayoutDesignerIntroViewModel.swift new file mode 100644 index 0000000..7cb33e1 --- /dev/null +++ b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Intro/LayoutDesignerIntroViewModel.swift @@ -0,0 +1,77 @@ +// +// LayoutDesignerIntroViewModel.swift +// PagingLayoutSamples +// +// Created by Amir on 09/07/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import Foundation + +struct LayoutDesignerIntroViewModel { + + // MARK: Properties + + let introPages: [LayoutDesignerIntroInfo] +} + + +extension Array where Element == LayoutDesignerIntroInfo { + static var all: [LayoutDesignerIntroInfo] = [ + .init(title: "Welcome to Layout Designer", + headerImageName: "logoForIntro" , + imageName: nil, + description: """ + The easiest way to make a beautiful paging layout for your UICollectionView. + You can use it for your iOS, iPadOS, and macOS Catalyst app. + """, + leftButtonTitle: "Skip", + rightButtonTitle: "Show me how!"), + .init(title: "Select the layout group", + headerImageName: nil, + imageName: "intro01", + description: """ + There are three groups of layouts available. + You can switch between them quickly and see the live preview on the middle panel. + """, + leftButtonTitle: "Previous", + rightButtonTitle: "Next"), + .init(title: "Select the layout type ", + headerImageName: nil, + imageName: "intro02", + description: """ + There are many layouts available for each group. + You can switch between them by clicking on the circles. + If you use a Trackpad you can also switch between them by scrolling to right and left. + """, + leftButtonTitle: "Previous", + rightButtonTitle: "Next"), + .init(title: "Switch between shapes and adjust options ", + headerImageName: nil, + imageName: "intro03", + description: """ + Now you can see the result on the sample cards (orange cards) + You can switch between them to see the animation by clicking on the arrows or on the card itself. + If you use a Trackpad you can also switch between them by scrolling to right and left. + Now adjust the options if needed and see changes in real-time. + """, + leftButtonTitle: "Previous", + rightButtonTitle: "Next"), + .init(title: "That’s it! your code is ready to use!", + headerImageName: nil, + imageName: "intro04", + description: """ + You can copy the generated code and use it in your project + If you need to see how to use the code try “Save as Project” and open it with Xcode + """, + leftButtonTitle: "Previous", + rightButtonTitle: "Design Layout!") + ] + + + static var allExceptWelcome: [LayoutDesignerIntroInfo] { + var list = Array(all.dropFirst()) + list[0].leftButtonTitle = "Close" + return list + } +} diff --git a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/LayoutDesignerViewController.swift b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/LayoutDesignerViewController.swift new file mode 100644 index 0000000..9fcc358 --- /dev/null +++ b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/LayoutDesignerViewController.swift @@ -0,0 +1,184 @@ +// +// LayoutDesignerViewController.swift +// CollectionViewPagingLayout +// +// Created by Amir on 05/05/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit +import Splash + +class LayoutDesignerViewController: UIViewController, ViewModelBased, NibBased { + + // MARK: Properties + + override var preferredStatusBarStyle: UIStatusBarStyle { + .lightContent + } + + // MARK: Properties + + var viewModel: LayoutDesignerViewModel! + + @IBOutlet private weak var stackButtonView: UIView! + @IBOutlet private weak var scaleButtonView: UIView! + @IBOutlet private weak var snapshotButtonView: UIView! + @IBOutlet private weak var previewContainerView: UIView! + @IBOutlet private weak var codeContainerView: UIView! + @IBOutlet private weak var optionsTableView: LayoutDesignerOptionsTableView! + + private var previewViewController: ShapesViewController! + private var codePreviewViewController: LayoutDesignerCodePreviewViewController! + + + // MARK: UIViewController + + override func viewDidLoad() { + super.viewDidLoad() + configureViews() + setOptionsList() + registerKeyboardNotifications() + + view.backgroundColor = .clear + codeContainerView.backgroundColor = codeContainerView.backgroundColor?.withAlphaComponent(0.6) + } + + + // MARK: Event listeners + + @IBAction private func layoutCategoryButtonTouched(button: UIButton) { + guard let view = button.superview else { return } + + switch view { + case stackButtonView: + viewModel.layouts = .stack + case scaleButtonView: + viewModel.layouts = .scale + case snapshotButtonView: + viewModel.layouts = .snapshot + default: + viewModel.layouts = [] + } + previewViewController.viewModel = viewModel.shapesViewModel + + setLayoutButtonSelected(view: stackButtonView, isSelected: view == stackButtonView) + setLayoutButtonSelected(view: scaleButtonView, isSelected: view == scaleButtonView) + setLayoutButtonSelected(view: snapshotButtonView, isSelected: view == snapshotButtonView) + + UIView.animate(withDuration: 0.55, delay: 0, usingSpringWithDamping: 0.9, initialSpringVelocity: 1, options: .curveEaseOut, animations: { + view.superview?.layoutIfNeeded() + }, completion: nil) + } + + + // MARK: Private functions + + private func configureViews() { + setInitialStateForLayoutButtons() + addPreviewController() + addCodePreviewController() + } + + private func setInitialStateForLayoutButtons() { + setLayoutButtonSelected(view: stackButtonView, isSelected: false) + setLayoutButtonSelected(view: scaleButtonView, isSelected: true) + setLayoutButtonSelected(view: snapshotButtonView, isSelected: false) + } + + private func setLayoutButtonSelected(view: UIView, isSelected: Bool, animated: Bool = true) { + guard let titleStackView = view.subviews.first(where: { $0 is UIStackView })? + .subviews.first(where: { $0 is UIStackView }) else { + return + } + titleStackView.isHidden = !isSelected + + let borderColor = UIColor.white.withAlphaComponent(0.66).cgColor + let noBorderColor = UIColor.clear.cgColor + + let oldBorderColor = view.layer.borderColor + view.layer.borderColor = isSelected ? borderColor : noBorderColor + + guard animated else { + return + } + let borderAnimation = CABasicAnimation(keyPath: "borderColor") + borderAnimation.duration = 0.4 + borderAnimation.fromValue = oldBorderColor + borderAnimation.toValue = view.layer.borderColor + view.layer.add(borderAnimation, forKey: nil) + } + + private func addPreviewController() { + previewViewController = ShapesViewController.instantiate(viewModel: viewModel.shapesViewModel) + addChild(previewViewController) + previewViewController.view.layer.cornerRadius = 30 + previewViewController.view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner] + previewContainerView.fill(with: previewViewController.view) + previewViewController.didMove(toParent: self) + previewViewController.delegate = self + viewModel.onOptionsChange = { [weak self] in + self?.previewViewController.reloadAndInvalidateShapes() + } + } + + private func addCodePreviewController() { + codePreviewViewController = LayoutDesignerCodePreviewViewController.instantiate() + codePreviewViewController.delegate = self + addChild(codePreviewViewController) + codeContainerView.fill(with: codePreviewViewController.view) + codePreviewViewController.didMove(toParent: self) + viewModel.onCodePreviewViewModelChange = { [weak self] in + self?.codePreviewViewController.viewModel = $0 + } + } + + private func setOptionsList() { + optionsTableView.optionViewModels = viewModel.optionViewModels + } + + private func showIntroViewController(viewModel: LayoutDesignerIntroViewModel) { + let vc = LayoutDesignerIntroViewController() + vc.viewModel = viewModel + addChild(vc) + view.fill(with: vc.view) + vc.didMove(toParent: self) + } + + private func registerKeyboardNotifications() { + let notificationCenter = NotificationCenter.default + notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillHideNotification, object: nil) + notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil) + } + + @objc private func adjustForKeyboard(notification: Notification) { + guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return } + + let keyboardScreenEndFrame = keyboardValue.cgRectValue + let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window) + var contentInset = optionsTableView.contentInset + + if keyboardViewEndFrame.minY < optionsTableView.frame.maxY { + contentInset.bottom = optionsTableView.frame.maxY - keyboardViewEndFrame.minY - 8 + } else { + contentInset.bottom = 8 + } + optionsTableView.contentInset = contentInset + } + +} + + +extension LayoutDesignerViewController: ShapesViewControllerDelegate { + func shapesViewController(_ vc: ShapesViewController, onSelectedLayoutChange layout: ShapeLayout) { + viewModel.selectedLayout = layout + setOptionsList() + } +} + + +extension LayoutDesignerViewController: LayoutDesignerCodePreviewViewControllerDelegate { + func layoutDesignerCodePreviewViewController(_ vc: LayoutDesignerCodePreviewViewController, onHelpButtonTouched button: UIButton) { + showIntroViewController(viewModel: viewModel.getIntroViewModel(showWelcome: false)) + } +} diff --git a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/LayoutDesignerViewController.xib b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/LayoutDesignerViewController.xib new file mode 100644 index 0000000..e3ec631 --- /dev/null +++ b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/LayoutDesignerViewController.xib @@ -0,0 +1,293 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/LayoutDesignerViewModel.swift b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/LayoutDesignerViewModel.swift new file mode 100644 index 0000000..3f163e2 --- /dev/null +++ b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/LayoutDesignerViewModel.swift @@ -0,0 +1,357 @@ +// +// LayoutDesignerViewModel.swift +// PagingLayoutSamples +// +// Created by Amir on 27/06/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import Foundation +import CollectionViewPagingLayout + +class LayoutDesignerViewModel { + + // MARK: Properties + + var onCodePreviewViewModelChange: ((LayoutDesignerCodePreviewViewModel) -> Void)? + var onOptionsChange: (() -> Void)? + var selectedLayout: ShapeLayout? { + didSet { + refreshOptionViewModels() + } + } + var layouts: [ShapeLayout] = .scale + var shapesViewModel: ShapesViewModel { + ShapesViewModel(layouts: layouts, showBackButton: false, showPageControl: true) + } + + + private(set) var optionViewModels: [LayoutDesignerOptionSectionViewModel] = [] + private let codeGenerator = OptionsCodeGenerator() + + + // MARK: Public functions + + func getIntroViewModel(showWelcome: Bool = true) -> LayoutDesignerIntroViewModel { + LayoutDesignerIntroViewModel(introPages: showWelcome ? .all : .allExceptWelcome) + } + + + // MARK: Private functions + + private func updateCodePreview(options: T) { + onCodePreviewViewModelChange?(.init(code: codeGenerator.generateCode(options: options))) + } + private func update(options: inout T, closure: (inout T) -> Void) { + closure(&options) + updateCodePreview(options: options) + shapesViewModel.setCustomOptions(options) + onOptionsChange?() + } + + private func refreshOptionViewModels() { + guard let selectedLayout = selectedLayout else { + optionViewModels = [] + return + } + + if let options = selectedLayout.scaleOptions { + optionViewModels = getOptionViewModels(scaleOptions: options) + } else if let options = selectedLayout.stackOptions { + optionViewModels = getOptionViewModels(stackOptions: options) + } else if let options = selectedLayout.snapshotOptions { + optionViewModels = getOptionViewModels(snapshotOptions: options) + } + } + + private func getOptionViewModels(scaleOptions: ScaleTransformViewOptions) -> [LayoutDesignerOptionSectionViewModel] { + var options = scaleOptions + updateCodePreview(options: options) + let update: ((inout ScaleTransformViewOptions) -> Void) -> Void = { [weak self] in + self?.update(options: &options, closure: $0) + } + + 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( + angle: .pi / 3, + minAngle: 0, + maxAngle: .pi, + x: 0, + y: 1, + z: 0, + m34: -0.000_1 + ) + + 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( + translateRatios: (0.1, 0, 0), + minTranslateRatios: (-0.05, 0, 0.86), + maxTranslateRatios: (0.05, 0, -0.86) + ) + + 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!) + } + }) + ] + + + return [ + .init(title: "Options", items: generalOptions), + .init(title: "Rotation 3D options", items: rotation3dOptions), + .init(title: "Translation 3D options", items: translation3dOptions) + ] + } + + private func getOptionViewModels(stackOptions: StackTransformViewOptions) -> [LayoutDesignerOptionSectionViewModel] { + var options = stackOptions + updateCodePreview(options: options) + let update: ((inout StackTransformViewOptions) -> Void) -> Void = { [weak self] in + self?.update(options: &options, closure: $0) + } + + 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 [ + .init(title: "Options", items: viewModels) + ] + } + + private func getOptionViewModels(snapshotOptions: SnapshotTransformViewOptions) -> [LayoutDesignerOptionSectionViewModel] { + var options = snapshotOptions + updateCodePreview(options: options) + + let update: ((inout SnapshotTransformViewOptions) -> Void) -> Void = { [weak self] in + self?.update(options: &options, closure: $0) + } + 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 new file mode 100644 index 0000000..8f17d70 --- /dev/null +++ b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Options/LayoutDesignerOptionsTableView.swift @@ -0,0 +1,75 @@ +// +// LayoutDesignerOptionsTableView.swift +// PagingLayoutSamples +// +// Created by Amir on 21/05/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit + +class LayoutDesignerOptionsTableView: UITableView { + + // MARK: Properties + + var optionViewModels: [LayoutDesignerOptionSectionViewModel] = [] { + didSet { + reloadData() + } + } + + + // MARK: Lifecycle + + required init?(coder: NSCoder) { + super.init(coder: coder) + configure() + } + + + // MARK: Private functions + + private func configure() { + dataSource = self + delegate = self + register(LayoutDesignerOptionCell.self) + backgroundColor = .clear + separatorStyle = .none + allowsSelection = false + sectionHeaderHeight = 40 + } + +} + + +extension LayoutDesignerOptionsTableView: UITableViewDataSource { + + func numberOfSections(in tableView: UITableView) -> Int { + optionViewModels.count + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + optionViewModels[section].items.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell: LayoutDesignerOptionCell = tableView.dequeueReusableCell(for: indexPath) + cell.viewModel = optionViewModels[indexPath.section].items[indexPath.row] + return cell + } +} + +extension LayoutDesignerOptionsTableView: UITableViewDelegate { + + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + let header = UIView() + let label = UILabel() + label.textColor = .white + label.adjustsFontSizeToFitWidth = true + label.font = .systemFont(ofSize: 22, weight: .medium) + label.text = optionViewModels[section].title + 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 new file mode 100644 index 0000000..a4c98da --- /dev/null +++ b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Options/cell/LayoutDesignerOptionCell.swift @@ -0,0 +1,256 @@ +// +// LayoutDesignerOptionCell.swift +// PagingLayoutSamples +// +// Created by Amir on 19/05/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit + +class LayoutDesignerOptionCell: UITableViewCell, NibBased { + + // MARK: Properties + + var viewModel: LayoutDesignerOptionViewModel? { + didSet { + updateViews() + } + } + + @IBOutlet private weak var label: UILabel! + @IBOutlet private weak var singleSlider: UISlider! + @IBOutlet private weak var singleSliderInput: UITextField! + @IBOutlet private weak var doubleSliderStackView: UIStackView! + @IBOutlet private weak var doubleSlider1: UISlider! + @IBOutlet private weak var doubleSliderInput1: UITextField! + @IBOutlet private weak var doubleSlider2: UISlider! + @IBOutlet private weak var doubleSliderInput2: UITextField! + @IBOutlet private weak var segmentedControl: UISegmentedControl! + @IBOutlet private weak var nilLabel: UILabel! + @IBOutlet private weak var switchView: UISwitch! + + private var onSingleSliderChange: (CGFloat) -> Void = { _ in } + private var onSingleSliderInputChange: (CGFloat) -> Void = { _ in } + private var onDoubleSlider1Change: (CGFloat) -> Void = { _ in } + private var onDoubleSliderInput1Change: (CGFloat) -> Void = { _ in } + private var onDoubleSlider2Change: (CGFloat) -> Void = { _ in } + private var onDoubleSliderInput2Change: (CGFloat) -> Void = { _ in } + private var onSwitchChange: (Bool) -> Void = { _ in } + private var onSelectedSegmentChange: (Int) -> Void = { _ in } + + + // MARK: Lifecycle + + override func awakeFromNib() { + super.awakeFromNib() + setupViews() + updateViews() + } + + + // MARK: Private functions + + private func setupViews() { + backgroundColor = .clear + label.textColor = UIColor.white.withAlphaComponent(0.7) + label.adjustsFontSizeToFitWidth = true + nilLabel.textColor = UIColor.white.withAlphaComponent(0.7) + [singleSlider, doubleSlider1, doubleSlider2].forEach { + $0?.tintColor = .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 + } + switchView.onTintColor = .systemGreen + switchView.backgroundColor = UIColor.black.withAlphaComponent(0.2) + switchView.layer.cornerRadius = switchView.layer.bounds.height / 2 + switchView.addTarget(self, action: #selector(onSwitchChange(switchView:)), for: .valueChanged) + + segmentedControl.setTitleTextAttributes([.foregroundColor: UIColor.white], for: .normal) + segmentedControl.setTitleTextAttributes([.foregroundColor: #colorLiteral(red: 0.3098039216, green: 0.5215686275, blue: 0.8666666667, alpha: 1)], for: .selected) + segmentedControl.tintColor = .white + segmentedControl.backgroundColor = UIColor.white.withAlphaComponent(0.3) + segmentedControl.addTarget(self, action: #selector(onSelectedSegmentChange(control:)), for: .valueChanged) + } + + @objc private func onSwitchChange(switchView: UISwitch) { + onSwitchChange(switchView.isOn) + } + + @objc private func onSelectedSegmentChange(control: UISegmentedControl) { + onSelectedSegmentChange(control.selectedSegmentIndex) + } + + @objc private func onInputChange(input: UITextField) { + onInputChange(input: input, fromSlider: false) + } + + @objc private func onSliderChange(slider: UISlider) { + onSliderChange(slider: slider, fromInput: false) + } + + private func onSliderChange(slider: UISlider, fromInput: Bool) { + let value = CGFloat(slider.value) + if slider == singleSlider, !(singleSlider.value == Float(value) && fromInput) { + onSingleSliderChange(value) + onInputChange(input: singleSliderInput, fromSlider: true) + singleSliderInput.set(value: value) + } else if slider == doubleSlider1, !(doubleSlider1.value == Float(value) && fromInput) { + onDoubleSlider1Change(value) + onInputChange(input: doubleSliderInput1, fromSlider: true) + doubleSliderInput1.set(value: value) + } else if slider == doubleSlider2, !(doubleSlider2.value == Float(value) && fromInput) { + onDoubleSlider2Change(value) + onInputChange(input: doubleSliderInput2, fromSlider: true) + doubleSliderInput2.set(value: value) + } + } + + private func onInputChange(input: UITextField, fromSlider: Bool) { + let value = input.floatValue ?? 0 + if input == singleSliderInput, !(singleSliderInput.floatValue == value && fromSlider) { + onSingleSliderInputChange(CGFloat(value)) + onSliderChange(slider: singleSlider, fromInput: true) + singleSlider.value = value + } else if input == doubleSliderInput1, !(doubleSliderInput1.floatValue == value && fromSlider) { + onDoubleSliderInput1Change(CGFloat(value)) + onSliderChange(slider: doubleSlider1, fromInput: true) + doubleSlider1.value = value + } else if input == doubleSliderInput2, !(doubleSliderInput2.floatValue == value && fromSlider) { + onDoubleSliderInput2Change(CGFloat(value)) + onSliderChange(slider: doubleSlider2, fromInput: true) + doubleSlider2.value = value + } + } + + private func updateViews() { + guard let viewModel = viewModel, label != nil else { return } + + label.text = viewModel.title + singleSlider.isHidden = true + singleSliderInput.isHidden = true + doubleSliderStackView.isHidden = true + segmentedControl.isHidden = true + switchView.isHidden = true + nilLabel.isHidden = true + + switch viewModel.kind { + + case let .singleSlider(current, range, optional, onChange): + let latestValue = viewModel.getLatestValue() ?? current + singleSlider.isHidden = false + singleSliderInput.isHidden = false + singleSlider.maximumValue = Float(range.upperBound) + singleSlider.minimumValue = Float(range.lowerBound) + singleSliderInput.set(value: latestValue ?? 0) + singleSlider.value = Float(latestValue ?? 0) + let onNewValue: ((CGFloat?) -> Void) = { + viewModel.onNewValue($0) + onChange($0) + } + + onSingleSliderChange = onNewValue + onSingleSliderInputChange = onNewValue + if optional { + switchView.isHidden = false + switchView.isOn = latestValue == nil + nilLabel.isHidden = false + onSwitchChange = { [weak self] in + guard let self = self else { return } + onNewValue($0 ? nil : CGFloat(self.singleSlider.value)) + self.singleSlider.isHidden = $0 + self.singleSliderInput.isHidden = $0 + } + onSwitchChange(switchView.isOn) + } + + case let .doubleSlider(current, range, optional, onChange): + let latestValue = viewModel.getLatestValue() ?? current + doubleSliderStackView.isHidden = false + doubleSliderStackView.alpha = 1 + doubleSlider1.maximumValue = Float(range.upperBound) + doubleSlider2.maximumValue = Float(range.upperBound) + doubleSlider1.minimumValue = Float(range.lowerBound) + doubleSlider2.minimumValue = Float(range.lowerBound) + doubleSliderInput1.set(value: latestValue?.0 ?? 0) + doubleSlider1.value = Float(latestValue?.0 ?? 0) + doubleSliderInput2.set(value: latestValue?.1 ?? 0) + doubleSlider2.value = Float(latestValue?.1 ?? 0) + let getValues = { [weak self] in self.map { (CGFloat($0.doubleSlider1.value), CGFloat($0.doubleSlider2.value)) } } + + let onNewValue: (((CGFloat, CGFloat)?) -> Void) = { + viewModel.onNewValue($0) + onChange($0) + } + + onDoubleSlider1Change = { _ in onNewValue(getValues()) } + onDoubleSlider2Change = { _ in onNewValue(getValues()) } + onDoubleSliderInput1Change = { _ in onNewValue(getValues()) } + onDoubleSliderInput2Change = { _ in onNewValue(getValues()) } + if optional { + switchView.isHidden = false + switchView.isOn = latestValue == nil + nilLabel.isHidden = false + onSwitchChange = { [weak self] in + guard let self = self else { return } + onNewValue($0 ? nil : getValues()) + self.doubleSliderStackView.alpha = $0 ? 0 : 1 + } + onSwitchChange(switchView.isOn) + } + + + case let .toggleSwitch(current, onChange): + switchView.isHidden = false + switchView.isOn = viewModel.getLatestValue() ?? current + onSwitchChange = { + viewModel.onNewValue($0) + onChange($0) + } + + case let .segmented(options, current, onChange): + segmentedControl.isHidden = false + segmentedControl.removeAllSegments() + options.reversed().forEach { + segmentedControl.insertSegment(withTitle: $0, at: 0, animated: false) + } + onSelectedSegmentChange = { + viewModel.onNewValue($0) + onChange(options[$0]) + } + let selected = viewModel.getLatestValue() ?? current + if let index = options.firstIndex(of: selected) { + segmentedControl.selectedSegmentIndex = index + } + } + } + +} + + +private extension UITextField { + + var floatValue: Float? { + text?.floatValue + } + + func set(value: Float) { + set(value: CGFloat(value)) + } + + func set(value: CGFloat) { + text = value.format() + } +} diff --git a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Options/cell/LayoutDesignerOptionCell.xib b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Options/cell/LayoutDesignerOptionCell.xib new file mode 100644 index 0000000..572d158 --- /dev/null +++ b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Options/cell/LayoutDesignerOptionCell.xib @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Options/cell/LayoutDesignerOptionCellViewModel.swift b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Options/cell/LayoutDesignerOptionCellViewModel.swift new file mode 100644 index 0000000..a09bc5a --- /dev/null +++ b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/Options/cell/LayoutDesignerOptionCellViewModel.swift @@ -0,0 +1,72 @@ +// +// LayoutDesignerOptionCellViewModel.swift +// PagingLayoutSamples +// +// Created by Amir on 19/05/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import Foundation +import UIKit + +class LayoutDesignerOptionSectionViewModel { + + // MARK: Properties + + let title: String + var items: [LayoutDesignerOptionViewModel] + + + // MARK: Lifecycle + + init(title: String, items: [LayoutDesignerOptionViewModel]) { + self.title = title + self.items = items + } +} + + +class LayoutDesignerOptionViewModel { + + // MARK: Constants + + enum Kind { + case singleSlider(current: CGFloat?, range: ClosedRange = 0...1, optional: Bool = false, onChange: (CGFloat?) -> Void) + case doubleSlider(current: (CGFloat, CGFloat)?, range: ClosedRange = 0...1, optional: Bool = false, onChange: ((CGFloat, CGFloat)?) -> Void) + case toggleSwitch(current: Bool, onChange: (Bool) -> Void) + case segmented(options: [String], current: String, onChange: (String) -> Void) + } + + + // MARK: Properties + + let title: String + let kind: Kind + + private var changed: Bool = false + private var latestValue: Any? + + + // MARK: Lifecycle + + init(title: String, kind: Kind) { + self.title = title + self.kind = kind + } + + + // MARK: Public functions + + func onNewValue(_ value: Any?) { + changed = true + latestValue = value + } + + func getLatestValue() -> T? { + if !changed { + return nil + } + return latestValue as? T + } + +} diff --git a/Samples/PagingLayoutSamples/Modules/LayoutDesigner/OptionsCodeGenerator.swift b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/OptionsCodeGenerator.swift new file mode 100644 index 0000000..e5e5419 --- /dev/null +++ b/Samples/PagingLayoutSamples/Modules/LayoutDesigner/OptionsCodeGenerator.swift @@ -0,0 +1,301 @@ +// +// OptionsCodeGenerator.swift +// PagingLayoutSamples +// +// Created by Amir on 28/06/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import Foundation +import CollectionViewPagingLayout + +class OptionsCodeGenerator { + + // MARK: Public functions + + func generateCode(options: T) -> String { + if let options = options as? ScaleTransformViewOptions { + + 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 { + + 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 { + + 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 "" + } + + // MARK: Private functions + + private func generateCode(options: ScaleTransformViewOptions) -> String { + """ + var scaleOptions = ScaleTransformViewOptions( + minScale: \(options.minScale.format()), + maxScale: \(options.maxScale.format()), + scaleRatio: \(options.scaleRatio.format()), + translationRatio: \(options.translationRatio.generateInitCode()), + minTranslationRatio: \(options.minTranslationRatio.map { $0.generateInitCode() } ?? "nil"), + maxTranslationRatio: \(options.maxTranslationRatio.map { $0.generateInitCode() } ?? "nil"), + keepVerticalSpacingEqual: \(options.keepVerticalSpacingEqual ? "true" : "false"), + keepHorizontalSpacingEqual: \(options.keepHorizontalSpacingEqual ? "true" : "false"), + scaleCurve: \(options.scaleCurve.generateInitCode()), + translationCurve: \(options.translationCurve.generateInitCode()), + shadowEnabled: \(options.shadowEnabled ? "true" : "false"), + shadowColor: \(options.shadowColor.generateInitCode()), + shadowOpacity: \(CGFloat(options.shadowOpacity).format()), + shadowRadiusMin: \(options.shadowRadiusMin.format()), + shadowRadiusMax: \(options.shadowRadiusMax.format()), + shadowOffsetMin: \(options.shadowOffsetMin.generateInitCode()), + shadowOffsetMax: \(options.shadowOffsetMax.generateInitCode()), + shadowOpacityMin: \(CGFloat(options.shadowOpacityMin).format()), + shadowOpacityMax: \(CGFloat(options.shadowOpacityMax).format()), + blurEffectEnabled: \(options.blurEffectEnabled ? "true" : "false"), + blurEffectRadiusRatio: \(options.blurEffectRadiusRatio.format()), + blurEffectStyle: \(options.blurEffectStyle.generateInitCode()), + rotation3d: \(options.rotation3d.map { $0.generateInitCode() } ?? "nil"), + translation3d: \(options.translation3d.map { $0.generateInitCode() } ?? "nil") + ) + """ + } + + private func generateCode(options: StackTransformViewOptions) -> String { + """ + var stackOptions = StackTransformViewOptions( + scaleFactor: \(options.scaleFactor.format()), + minScale: \(options.minScale.map { $0.format() } ?? "nil"), + maxScale: \(options.maxScale.map { $0.format() } ?? "nil"), + maxStackSize: \(options.maxStackSize), + spacingFactor: \(options.spacingFactor.format()), + maxSpacing: \(options.maxSpacing.map { $0.format() } ?? "nil"), + alphaFactor: \(options.alphaFactor.format()), + bottomStackAlphaSpeedFactor: \(options.bottomStackAlphaSpeedFactor.format()), + topStackAlphaSpeedFactor: \(options.topStackAlphaSpeedFactor.format()), + perspectiveRatio: \(options.perspectiveRatio.format()), + shadowEnabled: \(options.shadowEnabled ? "true" : "false"), + shadowColor: \(options.shadowColor.generateInitCode()), + shadowOpacity: \(CGFloat(options.shadowOpacity).format()), + shadowOffset: \(options.shadowOffset.generateInitCode()), + shadowRadius: \(options.shadowRadius.format()), + stackRotateAngel: \(options.stackRotateAngel.format()), + popAngle: \(options.popAngle.format()), + popOffsetRatio: \(options.popOffsetRatio.generateInitCode()), + stackPosition: \(options.stackPosition.generateInitCode()), + reverse: \(options.reverse ? "true" : "false"), + blurEffectEnabled: \(options.blurEffectEnabled ? "true" : "false"), + maxBlurEffectRadius: \(options.maxBlurEffectRadius.format()), + blurEffectStyle: \(options.blurEffectStyle.generateInitCode()) + ) + """ + } + + private func generateCode(options: SnapshotTransformViewOptions) -> String { + """ + var snapshotOptions = SnapshotTransformViewOptions( + pieceSizeRatio: \(options.pieceSizeRatio.generateInitCode()), + piecesCornerRadiusRatio: \(options.piecesCornerRadiusRatio.generateInitCode()), + piecesAlphaRatio: \(options.piecesAlphaRatio.generateInitCode()), + piecesTranslationRatio: \(options.piecesTranslationRatio.generateInitCode()), + piecesScaleRatio: \(options.piecesScaleRatio.generateInitCode()), + containerScaleRatio: \(options.containerScaleRatio.format()), + containerTranslationRatio: \(options.containerTranslationRatio.generateInitCode()) + ) + """ + } +} + + +private extension SnapshotTransformViewOptions.PiecesValue { + func generateInitCode() -> String { + switch self { + case let .static(value): + return ".static(\(formatValue(value)))" + case let .columnBased(value, reversed): + return """ + .columnBased( + \(formatValue(value)), + reversed: \(reversed ? "true" : "false") + ) + """ + case let .rowBased(value, reversed): + return """ + .rowBased( + \(formatValue(value)), + reversed: \(reversed ? "true" : "false") + ) + """ + case let .columnOddEven(odd, even, increasing): + return """ + .columnOddEven( + \(formatValue(odd)), + \(formatValue(even)), + increasing: \(increasing ? "true" : "false") + ) + """ + case let .rowOddEven(odd, even, increasing): + return """ + .rowOddEven( + \(formatValue(odd)), + \(formatValue(even)), + increasing: \(increasing ? "true" : "false") + ) + """ + case let .columnBasedMirror(value, reversed): + return """ + .columnBasedMirror( + \(formatValue(value)), + reversed: \(reversed ? "true" : "false") + ) + """ + case let .rowBasedMirror(value, reversed): + return """ + .rowBasedMirror( + \(formatValue(value)), + reversed: \(reversed ? "true" : "false") + ) + """ + case let .indexBasedCustom(values): + return ".indexBasedCustom(\(values.map { formatValue($0) }.joined(separator: ", ")))" + case let .rowBasedCustom(values): + return ".rowBasedCustom(\(values.map { formatValue($0) }.joined(separator: ", ")))" + case let .columnBasedCustom(values): + return ".columnBasedCustom(\(values.map { formatValue($0) }.joined(separator: ", ")))" + case let .aggregated(piecesValue, _): + // Custom detection for the functions, I don't think there is a better way for that + // This works only on the current options + var function = "+" + if case .rowBasedMirror(let value, _) = piecesValue.first, + let point = value as? CGPoint, + point.x == 1 { + function = "*" + } + return ".aggregated([\(piecesValue.map { $0.generateInitCode() }.joined(separator: ", "))], \(function))" + } + } + + private func formatValue(_ value: Any) -> String { + if let value = value as? CGFloat { + return value.format() + } + if let value = value as? CGSize { + return value.generateInitCode() + } + if let value = value as? CGPoint { + return value.generateInitCode() + } + return "-" + } +} + + +private extension UIColor { + func generateInitCode() -> String { + if self == .black { + return ".black" + } + if self == .white { + return ".white" + } + var red: CGFloat = 0 + var green: CGFloat = 0 + var blue: CGFloat = 0 + var alpha: CGFloat = 0 + getRed(&red, green: &green, blue: &blue, alpha: &alpha) + return ".init(red: \(red.format()), green: \(green.format()), blue: \(blue.format()), alpha: \(alpha.format()))" + } +} + + +private extension ScaleTransformViewOptions.Translation3dOptions { + func generateInitCode() -> String { + """ + .init( + translateRatios: (\(translateRatios.0.format()), \(translateRatios.1.format()), \(translateRatios.2.format())), + minTranslateRatios: (\(minTranslateRatios.0.format()), \(minTranslateRatios.1.format()), \(minTranslateRatios.2.format())), + maxTranslateRatios: (\(maxTranslateRatios.0.format()), \(maxTranslateRatios.1.format()), \(maxTranslateRatios.2.format())) + ) + """ + } +} + + +private extension ScaleTransformViewOptions.Rotation3dOptions { + func generateInitCode() -> String { + """ + .init( + angle: \(angle.format()), + minAngle: \(minAngle.format()), + maxAngle: \(maxAngle.format()), + x: \(x.format()), + y: \(y.format()), + z: \(z.format()), + m34: \(m34.format(fractionDigits: 6)) + ) + """ + } +} + + +private extension CGSize { + func generateInitCode() -> String { + if self == .zero { return ".zero" } + return ".init(width: \(width.format()), height: \(height.format()))" + } +} + + +private extension CGPoint { + func generateInitCode() -> String { + if self == .zero { return ".zero" } + return ".init(x: \(x.format()), y: \(y.format()))" + } +} + + +private extension TransformCurve { + func generateInitCode() -> String { + ".\(name.prefix(1).lowercased() + name.dropFirst())" + } +} + +private extension UIBlurEffect.Style { + func generateInitCode() -> String { + ".\(name.prefix(1).lowercased() + name.dropFirst())" + } +} diff --git a/Samples/PagingLayoutSamples/Modules/Main/MainViewController.swift b/Samples/PagingLayoutSamples/Modules/Main/MainViewController.swift new file mode 100644 index 0000000..b881919 --- /dev/null +++ b/Samples/PagingLayoutSamples/Modules/Main/MainViewController.swift @@ -0,0 +1,169 @@ +// +// MainViewController.swift +// CollectionViewPagingLayout +// +// Created by Amir Khorsandi on 12/26/19. +// Copyright © 2019 Amir Khorsandi. All rights reserved. +// + +import Foundation +import UIKit +import SwiftUI + +class MainViewController: UIViewController, NibBased { + + // MARK: Properties + + 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() { + if isSwiftUI { + push(makeViewController(ShapesListView(layoutGroup: .stack), + backButtonColor: .gray, + statusBarStyle: .darkContent)) + } else { + push(ShapesViewController.instantiate(viewModel: ShapesViewModel(layouts: .stack))) + } + } + + @IBAction private func scaleButtonTouched() { + if isSwiftUI { + push(makeViewController(ShapesListView(layoutGroup: .scale), + backButtonColor: .gray, + statusBarStyle: .darkContent)) + } else { + push(ShapesViewController.instantiate(viewModel: ShapesViewModel(layouts: .scale))) + } + } + + @IBAction private func snapshotButtonTouched() { + if isSwiftUI { + push(makeViewController(ShapesListView(layoutGroup: .snapshot), + backButtonColor: .gray, + statusBarStyle: .darkContent)) + } else { + push(ShapesViewController.instantiate(viewModel: ShapesViewModel(layouts: .snapshot))) + } + } + + @IBAction private func fruitsButtonTouched() { + push(FruitsViewController.instantiate(viewModel: FruitsViewModel())) + } + + @IBAction private func galleryButtonTouched() { + push(GalleryViewController.instantiate(viewModel: GalleryViewModel())) + } + + @IBAction private func cardsButtonTouched() { + 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 new file mode 100644 index 0000000..cbe3a62 --- /dev/null +++ b/Samples/PagingLayoutSamples/Modules/Main/MainViewController.xib @@ -0,0 +1,651 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/CollectionViewPagingLayout/Modules/Cards/CardsViewController.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Cards/CardsViewController.swift similarity index 82% rename from CollectionViewPagingLayout/Modules/Cards/CardsViewController.swift rename to Samples/PagingLayoutSamples/Modules/UIKit/Cards/CardsViewController.swift index 99d05a8..a5ca682 100644 --- a/CollectionViewPagingLayout/Modules/Cards/CardsViewController.swift +++ b/Samples/PagingLayoutSamples/Modules/UIKit/Cards/CardsViewController.swift @@ -8,6 +8,7 @@ import Foundation import UIKit +import CollectionViewPagingLayout class CardsViewController: UIViewController, NibBased, ViewModelBased { @@ -15,7 +16,7 @@ class CardsViewController: UIViewController, NibBased, ViewModelBased { private struct Constants { - static let infiniteNumberOfItems = 100000 + static let infiniteNumberOfItems = 100_000 } @@ -28,6 +29,7 @@ class CardsViewController: UIViewController, NibBased, ViewModelBased { } private let layout = CollectionViewPagingLayout() + private var didScrollCollectionViewToMiddle = false @IBOutlet private weak var backButton: UIButton! @IBOutlet private weak var collectionView: UICollectionView! @@ -40,17 +42,15 @@ class CardsViewController: UIViewController, NibBased, ViewModelBased { configureViews() } - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - layout.setCurrentPage(Constants.infiniteNumberOfItems/2, animated: false) - UIView.animate(withDuration: 0.25) { - self.collectionView.alpha = 1 - } - } - override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() - layout.invalidateLayout() + layout.invalidateLayoutInBatchUpdate() + if !didScrollCollectionViewToMiddle { + didScrollCollectionViewToMiddle = true + collectionView?.performBatchUpdates({ [weak self] in + self?.layout.setCurrentPage(Constants.infiniteNumberOfItems / 2, animated: false) + }) + } } @@ -65,7 +65,7 @@ class CardsViewController: UIViewController, NibBased, ViewModelBased { } @IBAction private func onPreviousTouched() { - layout.goToPrevPage() + layout.goToPreviousPage() } @@ -83,11 +83,11 @@ 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 collectionView.backgroundColor = .clear - collectionView.alpha = 0 collectionView.scrollsToTop = false } @@ -108,4 +108,3 @@ extension CardsViewController: UICollectionViewDataSource { } } - diff --git a/CollectionViewPagingLayout/Modules/Cards/CardsViewController.xib b/Samples/PagingLayoutSamples/Modules/UIKit/Cards/CardsViewController.xib similarity index 100% rename from CollectionViewPagingLayout/Modules/Cards/CardsViewController.xib rename to Samples/PagingLayoutSamples/Modules/UIKit/Cards/CardsViewController.xib diff --git a/CollectionViewPagingLayout/Modules/Cards/CardsViewModel.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Cards/CardsViewModel.swift similarity index 94% rename from CollectionViewPagingLayout/Modules/Cards/CardsViewModel.swift rename to Samples/PagingLayoutSamples/Modules/UIKit/Cards/CardsViewModel.swift index 5f102f3..f72755a 100644 --- a/CollectionViewPagingLayout/Modules/Cards/CardsViewModel.swift +++ b/Samples/PagingLayoutSamples/Modules/UIKit/Cards/CardsViewModel.swift @@ -19,7 +19,7 @@ class CardsViewModel { Card(imageName: "card02"), Card(imageName: "card06"), Card(imageName: "card07"), - Card(imageName: "card08"), + Card(imageName: "card08") ] diff --git a/CollectionViewPagingLayout/Modules/Cards/Cell/CardCellViewModel.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Cards/Cell/CardCellViewModel.swift similarity index 100% rename from CollectionViewPagingLayout/Modules/Cards/Cell/CardCellViewModel.swift rename to Samples/PagingLayoutSamples/Modules/UIKit/Cards/Cell/CardCellViewModel.swift diff --git a/CollectionViewPagingLayout/Modules/Cards/Cell/CardCollectionViewCell.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Cards/Cell/CardCollectionViewCell.swift similarity index 96% rename from CollectionViewPagingLayout/Modules/Cards/Cell/CardCollectionViewCell.swift rename to Samples/PagingLayoutSamples/Modules/UIKit/Cards/Cell/CardCollectionViewCell.swift index e21b6ba..f24ce1d 100644 --- a/CollectionViewPagingLayout/Modules/Cards/Cell/CardCollectionViewCell.swift +++ b/Samples/PagingLayoutSamples/Modules/UIKit/Cards/Cell/CardCollectionViewCell.swift @@ -8,6 +8,7 @@ import Foundation import UIKit +import CollectionViewPagingLayout class CardCollectionViewCell: UICollectionViewCell, NibBased { @@ -26,6 +27,7 @@ class CardCollectionViewCell: UICollectionViewCell, NibBased { // MARK: Lifecycle override func awakeFromNib() { + super.awakeFromNib() setupViews() } @@ -84,7 +86,7 @@ extension CardCollectionViewCell: TransformableView { } if progress < -0.5 { - scale = 1 + 0.5 * 0.05 + ((progress + 0.5) * 0.35) + scale = 1 + 0.5 * 0.05 + ((progress + 0.5) * 0.35) } let adjustScaleProgress = abs(round(progress) - progress) diff --git a/CollectionViewPagingLayout/Modules/Cards/Cell/CardCollectionViewCell.xib b/Samples/PagingLayoutSamples/Modules/UIKit/Cards/Cell/CardCollectionViewCell.xib similarity index 97% rename from CollectionViewPagingLayout/Modules/Cards/Cell/CardCollectionViewCell.xib rename to Samples/PagingLayoutSamples/Modules/UIKit/Cards/Cell/CardCollectionViewCell.xib index c6cc69c..ac06179 100644 --- a/CollectionViewPagingLayout/Modules/Cards/Cell/CardCollectionViewCell.xib +++ b/Samples/PagingLayoutSamples/Modules/UIKit/Cards/Cell/CardCollectionViewCell.xib @@ -9,7 +9,7 @@ - + diff --git a/CollectionViewPagingLayout/Modules/Fruits/Cell/FruitCellViewModel.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Fruits/Cell/FruitCellViewModel.swift similarity index 100% rename from CollectionViewPagingLayout/Modules/Fruits/Cell/FruitCellViewModel.swift rename to Samples/PagingLayoutSamples/Modules/UIKit/Fruits/Cell/FruitCellViewModel.swift diff --git a/CollectionViewPagingLayout/Modules/Fruits/Cell/FruitsCollectionViewCell.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Fruits/Cell/FruitsCollectionViewCell.swift similarity index 85% rename from CollectionViewPagingLayout/Modules/Fruits/Cell/FruitsCollectionViewCell.swift rename to Samples/PagingLayoutSamples/Modules/UIKit/Fruits/Cell/FruitsCollectionViewCell.swift index cc4f77b..7a74885 100644 --- a/CollectionViewPagingLayout/Modules/Fruits/Cell/FruitsCollectionViewCell.swift +++ b/Samples/PagingLayoutSamples/Modules/UIKit/Fruits/Cell/FruitsCollectionViewCell.swift @@ -7,6 +7,7 @@ // import UIKit +import CollectionViewPagingLayout class FruitsCollectionViewCell: UICollectionViewCell, NibBased { @@ -36,6 +37,7 @@ class FruitsCollectionViewCell: UICollectionViewCell, NibBased { // MARK: Lifecycle override func awakeFromNib() { + super.awakeFromNib() setupViews() } @@ -101,8 +103,8 @@ extension FruitsCollectionViewCell: TransformableView { private func transformPriceTagView(progress: CGFloat) { let angle = .pi * progress - var transform = CATransform3DIdentity; - transform.m34 = -0.008; + var transform = CATransform3DIdentity + transform.m34 = -0.008 transform = CATransform3DRotate(transform, angle, 0, 1, 0) priceTagViewContainer.layer.transform = transform priceTagViewContainer.alpha = abs(progress) > 0.5 ? 0 : 1 @@ -111,7 +113,7 @@ extension FruitsCollectionViewCell: TransformableView { private func transformPageTitle(progress: CGFloat) { let pageTitleWidth: CGFloat = 80 let pageTitleScale = max(1 - abs(0.3 * progress), 0.7) - let pageTitleX = pageTitleWidth/2 + progress * pageTitleWidth - (pageTitleScale * pageTitleWidth)/2 + let pageTitleX = pageTitleWidth / 2 + progress * pageTitleWidth - (pageTitleScale * pageTitleWidth) / 2 pageTitleLabel.transform = CGAffineTransform(translationX: pageTitleX, y: 0) .scaledBy(x: pageTitleScale, y: pageTitleScale) var pageTitleAlpha = max(1 - abs(0.7 * progress), 0.3) @@ -124,22 +126,22 @@ extension FruitsCollectionViewCell: TransformableView { private func transformCardView(progress: CGFloat) { let translationX: CGFloat = bounds.width * progress - let imageScale = 1 - abs(0.5 * progress) - imageView.transform = CGAffineTransform(translationX: translationX, y: progress * imageView.frame.height/8) + let imageScale = 1 - abs(0.5 * progress) + imageView.transform = CGAffineTransform(translationX: translationX, y: progress * imageView.frame.height / 8) .scaledBy(x: imageScale, y: imageScale) - let cardBackgroundScale = 1 - abs(0.3 * progress) - cardBackgroundView.transform = CGAffineTransform(translationX: translationX/1.55, y: 0) + let cardBackgroundScale = 1 - abs(0.3 * progress) + cardBackgroundView.transform = CGAffineTransform(translationX: translationX / 1.55, y: 0) .scaledBy(x: cardBackgroundScale, y: cardBackgroundScale) - var transform = CATransform3DIdentity; + var transform = CATransform3DIdentity if progress < 0 { - let angle = max(0 - abs(-CGFloat.pi/3 * progress), -CGFloat.pi/3) - transform.m34 = -0.011; + let angle = max(0 - abs(-CGFloat.pi / 3 * progress), -CGFloat.pi / 3) + transform.m34 = -0.011 transform = CATransform3DRotate(transform, angle, 0, 1, 1) } else { - let angle = max(0 - abs(-CGFloat.pi/8 * progress), -CGFloat.pi/8) - transform.m34 = 0.002; + let angle = max(0 - abs(-CGFloat.pi / 8 * progress), -CGFloat.pi / 8) + transform.m34 = 0.002 transform.m41 = bounds.width * 0.093 * progress transform = CATransform3DRotate(transform, angle, 0, 1, -0.1) } @@ -155,9 +157,11 @@ extension FruitsCollectionViewCell: QuantityControllerViewDelegate { // this is an example, in an ideal world we want to save the new quantity properly func onDecreaseButtonTouched(view: QuantityControllerView) { - viewModel?.quantity = max(0, (viewModel?.quantity ?? 0) - 1) + let current = viewModel?.quantity ?? 0 + viewModel?.quantity = max(0, current - 1) } func onIncreaseButtonTouched(view: QuantityControllerView) { - viewModel?.quantity = (viewModel?.quantity ?? 0) + 1 + let current = viewModel?.quantity ?? 0 + viewModel?.quantity = current + 1 } } diff --git a/CollectionViewPagingLayout/Modules/Fruits/Cell/FruitsCollectionViewCell.xib b/Samples/PagingLayoutSamples/Modules/UIKit/Fruits/Cell/FruitsCollectionViewCell.xib similarity index 98% rename from CollectionViewPagingLayout/Modules/Fruits/Cell/FruitsCollectionViewCell.xib rename to Samples/PagingLayoutSamples/Modules/UIKit/Fruits/Cell/FruitsCollectionViewCell.xib index 0b134af..4a7c5c7 100644 --- a/CollectionViewPagingLayout/Modules/Fruits/Cell/FruitsCollectionViewCell.xib +++ b/Samples/PagingLayoutSamples/Modules/UIKit/Fruits/Cell/FruitsCollectionViewCell.xib @@ -9,7 +9,7 @@ - + @@ -33,7 +33,7 @@ - + diff --git a/CollectionViewPagingLayout/Modules/Fruits/FruitsViewController.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Fruits/FruitsViewController.swift similarity index 93% rename from CollectionViewPagingLayout/Modules/Fruits/FruitsViewController.swift rename to Samples/PagingLayoutSamples/Modules/UIKit/Fruits/FruitsViewController.swift index 063b225..e5b0ef9 100644 --- a/CollectionViewPagingLayout/Modules/Fruits/FruitsViewController.swift +++ b/Samples/PagingLayoutSamples/Modules/UIKit/Fruits/FruitsViewController.swift @@ -8,6 +8,7 @@ import Foundation import UIKit +import CollectionViewPagingLayout class FruitsViewController: UIViewController, NibBased, ViewModelBased { @@ -26,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/CollectionViewPagingLayout/Modules/Fruits/FruitsViewController.xib b/Samples/PagingLayoutSamples/Modules/UIKit/Fruits/FruitsViewController.xib similarity index 97% rename from CollectionViewPagingLayout/Modules/Fruits/FruitsViewController.xib rename to Samples/PagingLayoutSamples/Modules/UIKit/Fruits/FruitsViewController.xib index a0af5a5..86d385b 100644 --- a/CollectionViewPagingLayout/Modules/Fruits/FruitsViewController.xib +++ b/Samples/PagingLayoutSamples/Modules/UIKit/Fruits/FruitsViewController.xib @@ -1,14 +1,14 @@ - + - + - + diff --git a/CollectionViewPagingLayout/Modules/Fruits/FruitsViewModel.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Fruits/FruitsViewModel.swift similarity index 100% rename from CollectionViewPagingLayout/Modules/Fruits/FruitsViewModel.swift rename to Samples/PagingLayoutSamples/Modules/UIKit/Fruits/FruitsViewModel.swift diff --git a/CollectionViewPagingLayout/Modules/Gallery/Cell/PhotoCellViewModel.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Gallery/Cell/PhotoCellViewModel.swift similarity index 100% rename from CollectionViewPagingLayout/Modules/Gallery/Cell/PhotoCellViewModel.swift rename to Samples/PagingLayoutSamples/Modules/UIKit/Gallery/Cell/PhotoCellViewModel.swift diff --git a/CollectionViewPagingLayout/Modules/Gallery/Cell/PhotoCollectionViewCell.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Gallery/Cell/PhotoCollectionViewCell.swift similarity index 95% rename from CollectionViewPagingLayout/Modules/Gallery/Cell/PhotoCollectionViewCell.swift rename to Samples/PagingLayoutSamples/Modules/UIKit/Gallery/Cell/PhotoCollectionViewCell.swift index 4869e0d..59300f8 100644 --- a/CollectionViewPagingLayout/Modules/Gallery/Cell/PhotoCollectionViewCell.swift +++ b/Samples/PagingLayoutSamples/Modules/UIKit/Gallery/Cell/PhotoCollectionViewCell.swift @@ -8,6 +8,7 @@ import Foundation import UIKit +import CollectionViewPagingLayout class PhotoCollectionViewCell: UICollectionViewCell, NibBased { @@ -36,6 +37,7 @@ class PhotoCollectionViewCell: UICollectionViewCell, NibBased { // MARK: Lifecycle override func awakeFromNib() { + super.awakeFromNib() setupViews() } @@ -75,8 +77,8 @@ class PhotoCollectionViewCell: UICollectionViewCell, NibBased { subtitleLabel.layer.shadowRadius = 2 photoContainerView.layer.zPosition = 500 - titleLabel.layer.zPosition = 1000 - subtitleLabel.layer.zPosition = 1000 + titleLabel.layer.zPosition = 1_000 + subtitleLabel.layer.zPosition = 1_000 backgroundVisualEffectView.effect = UIBlurEffect(style: .dark) } @@ -95,7 +97,7 @@ class PhotoCollectionViewCell: UICollectionViewCell, NibBased { zoomAnimator?.startAnimation() return } - zooming = !zooming + zooming.toggle() zoomAnimator = UIViewPropertyAnimator(duration: 7, curve: .linear) { [weak self] in guard let self = self else { return } let scale: CGFloat = self.zooming ? 1.2 : 1 @@ -163,8 +165,8 @@ extension PhotoCollectionViewCell: TransformableView { zoomAnimator?.pauseAnimation() } let angle = .pi * progress - var transform = CATransform3DIdentity; - transform.m34 = -0.0015; + var transform = CATransform3DIdentity + transform.m34 = -0.001_5 transform = CATransform3DRotate(transform, angle, 0, 1, 0) photoContainerView.layer.transform = transform diff --git a/CollectionViewPagingLayout/Modules/Gallery/Cell/PhotoCollectionViewCell.xib b/Samples/PagingLayoutSamples/Modules/UIKit/Gallery/Cell/PhotoCollectionViewCell.xib similarity index 99% rename from CollectionViewPagingLayout/Modules/Gallery/Cell/PhotoCollectionViewCell.xib rename to Samples/PagingLayoutSamples/Modules/UIKit/Gallery/Cell/PhotoCollectionViewCell.xib index a5c401d..1391f77 100644 --- a/CollectionViewPagingLayout/Modules/Gallery/Cell/PhotoCollectionViewCell.xib +++ b/Samples/PagingLayoutSamples/Modules/UIKit/Gallery/Cell/PhotoCollectionViewCell.xib @@ -9,7 +9,7 @@ - + diff --git a/CollectionViewPagingLayout/Modules/Gallery/GalleryViewController.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Gallery/GalleryViewController.swift similarity index 92% rename from CollectionViewPagingLayout/Modules/Gallery/GalleryViewController.swift rename to Samples/PagingLayoutSamples/Modules/UIKit/Gallery/GalleryViewController.swift index 49ac2f9..5544c23 100644 --- a/CollectionViewPagingLayout/Modules/Gallery/GalleryViewController.swift +++ b/Samples/PagingLayoutSamples/Modules/UIKit/Gallery/GalleryViewController.swift @@ -8,6 +8,7 @@ import Foundation import UIKit +import CollectionViewPagingLayout class GalleryViewController: UIViewController, NibBased, ViewModelBased { @@ -32,11 +33,6 @@ class GalleryViewController: UIViewController, NibBased, ViewModelBased { configureViews() } - override func viewDidLayoutSubviews() { - super.viewDidLayoutSubviews() - collectionView.collectionViewLayout.invalidateLayout() - } - // MARK: Event listener @@ -49,7 +45,7 @@ class GalleryViewController: UIViewController, NibBased, ViewModelBased { } @IBAction private func onPreviousTouched() { - layout.goToPrevPage() + layout.goToPreviousPage() } @@ -88,4 +84,3 @@ extension GalleryViewController: UICollectionViewDataSource { } } - diff --git a/CollectionViewPagingLayout/Modules/Gallery/GalleryViewController.xib b/Samples/PagingLayoutSamples/Modules/UIKit/Gallery/GalleryViewController.xib similarity index 99% rename from CollectionViewPagingLayout/Modules/Gallery/GalleryViewController.xib rename to Samples/PagingLayoutSamples/Modules/UIKit/Gallery/GalleryViewController.xib index 4c13ed7..ff91a86 100644 --- a/CollectionViewPagingLayout/Modules/Gallery/GalleryViewController.xib +++ b/Samples/PagingLayoutSamples/Modules/UIKit/Gallery/GalleryViewController.xib @@ -7,7 +7,7 @@ - + diff --git a/CollectionViewPagingLayout/Modules/Gallery/GalleryViewModel.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Gallery/GalleryViewModel.swift similarity index 100% rename from CollectionViewPagingLayout/Modules/Gallery/GalleryViewModel.swift rename to Samples/PagingLayoutSamples/Modules/UIKit/Gallery/GalleryViewModel.swift diff --git a/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/Card/ShapeCardView.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/Card/ShapeCardView.swift new file mode 100644 index 0000000..d5b3e43 --- /dev/null +++ b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/Card/ShapeCardView.swift @@ -0,0 +1,49 @@ +// +// ShapeCardView.swift +// CollectionViewPagingLayout +// +// Created by Amir Khorsandi on 2/16/20. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit + +class ShapeCardView: GradientView, NibBased { + + // MARK: Properties + + var viewModel: ShapeCardViewModel? { + didSet { + updateViews() + } + } + + @IBOutlet private weak var imageView: UIImageView! + @IBOutlet private weak var titleLabel: UILabel! + + + // MARK: UICollectionViewCell + + override func awakeFromNib() { + super.awakeFromNib() + setupViews() + } + + + // MARK: Private functions + + private func setupViews() { + titleLabel.font = .systemFont(ofSize: 36, weight: .light) + set(startPoint: .zero, endPoint: CGPoint(x: 1, y: 1)) + layer.borderColor = UIColor.white.cgColor + layer.borderWidth = 5 + layer.cornerRadius = 30 + } + + private func updateViews() { + titleLabel.text = viewModel?.title + imageView.image = viewModel.let { UIImage(systemName: $0.iconName) } + viewModel.let { set(colors: $0.colors) } + } + +} diff --git a/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/Card/ShapeCardView.xib b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/Card/ShapeCardView.xib new file mode 100644 index 0000000..04fdf38 --- /dev/null +++ b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/Card/ShapeCardView.xib @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/Card/ShapeCardViewModel.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/Card/ShapeCardViewModel.swift new file mode 100644 index 0000000..de1d4c2 --- /dev/null +++ b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/Card/ShapeCardViewModel.swift @@ -0,0 +1,18 @@ +// +// ShapeCardViewModel.swift +// CollectionViewPagingLayout +// +// Created by Amir Khorsandi on 2/16/20. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit + +struct ShapeCardViewModel { + + // MARK: Properties + + let iconName: String + let title: String + let colors: [UIColor] +} diff --git a/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/LayoutTypeCell/LayoutTypeCellViewModel.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/LayoutTypeCell/LayoutTypeCellViewModel.swift new file mode 100644 index 0000000..03aa70b --- /dev/null +++ b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/LayoutTypeCell/LayoutTypeCellViewModel.swift @@ -0,0 +1,18 @@ +// +// LayoutTypeCellViewModel.swift +// CollectionViewPagingLayout +// +// Created by Amir on 15/02/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +struct LayoutTypeCellViewModel { + + // MARK: Properties + + let layout: ShapeLayout + let iconName: String + let title: String + let subtitle: String + let cardViewModels: [ShapeCardViewModel] +} diff --git a/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/LayoutTypeCell/LayoutTypeCollectionViewCell.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/LayoutTypeCell/LayoutTypeCollectionViewCell.swift new file mode 100644 index 0000000..11991d3 --- /dev/null +++ b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/LayoutTypeCell/LayoutTypeCollectionViewCell.swift @@ -0,0 +1,74 @@ +// +// LayoutTypeCollectionViewCell.swift +// CollectionViewPagingLayout +// +// Created by Amir on 15/02/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit +import CollectionViewPagingLayout + +class LayoutTypeCollectionViewCell: UICollectionViewCell, NibBased { + + // MARK: Properties + + var viewModel: LayoutTypeCellViewModel? { + didSet { + updateViews() + } + } + + @IBOutlet private weak var circleView: UIView! + @IBOutlet private weak var imageView: UIImageView! + @IBOutlet private weak var titleLabel: UILabel! + @IBOutlet private weak var subtitleLabel: UILabel! + + + // MARK: UICollectionViewCell + + override func awakeFromNib() { + super.awakeFromNib() + setupViews() + } + + + // MARK: Private functions + + private func setupViews() { + titleLabel.font = .systemFont(ofSize: 18, weight: .regular) + titleLabel.textColor = .gray + subtitleLabel.font = .systemFont(ofSize: 18, weight: .ultraLight) + subtitleLabel.textColor = .gray + imageView.tintColor = .gray + clipsToBounds = false + contentView.clipsToBounds = false + circleView.layer.cornerRadius = 50 + } + + private func updateViews() { + titleLabel.text = viewModel?.title + subtitleLabel.text = viewModel?.subtitle + imageView.image = (viewModel?.iconName).let { UIImage(named: $0) } + } + +} + + +extension LayoutTypeCollectionViewCell: ScaleTransformView { + + var scalableView: UIView { + circleView + } + + var selectableView: UIView? { + circleView + } + + func transform(progress: CGFloat) { + applyScaleTransform(progress: progress) + titleLabel.alpha = 1 - abs(progress) + subtitleLabel.alpha = titleLabel.alpha + } + +} diff --git a/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/LayoutTypeCell/LayoutTypeCollectionViewCell.xib b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/LayoutTypeCell/LayoutTypeCollectionViewCell.xib new file mode 100644 index 0000000..6cf5f93 --- /dev/null +++ b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/LayoutTypeCell/LayoutTypeCollectionViewCell.xib @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapeCell/BaseShapeCollectionViewCell.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapeCell/BaseShapeCollectionViewCell.swift new file mode 100644 index 0000000..451ac1e --- /dev/null +++ b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapeCell/BaseShapeCollectionViewCell.swift @@ -0,0 +1,71 @@ +// +// BaseShapeCollectionViewCell.swift +// CollectionViewPagingLayout +// +// Created by Amir on 15/02/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit + +class BaseShapeCollectionViewCell: UICollectionViewCell { + + // MARK: Properties + + var viewModel: ShapeCardViewModel? { + didSet { + updateViews() + } + } + + private(set) var shapeCardView: ShapeCardView! + private var edgeConstraints: [NSLayoutConstraint]? + + + // MARK: Lifecycle + + override init(frame: CGRect) { + super.init(frame: frame) + setupViews() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setupViews() + } + + + override func layoutSubviews() { + super.layoutSubviews() + guard let edgeConstraints = edgeConstraints else { + return + } + let leftRightMargin = frame.width * 0.18 + let topBottomMargin = frame.height * 0.06 + edgeConstraints[0].constant = leftRightMargin + edgeConstraints[2].constant = -leftRightMargin + + edgeConstraints[1].constant = topBottomMargin + edgeConstraints[3].constant = -topBottomMargin + } + + + // MARK: Private functions + + private func setupViews() { + shapeCardView = ShapeCardView.instantiate() + let leftRightMargin = frame.width * 0.18 + let topBottomMargin = frame.height * 0.06 + edgeConstraints = contentView.fill( + with: shapeCardView, + edges: UIEdgeInsets(top: topBottomMargin, left: leftRightMargin, bottom: -topBottomMargin, right: -leftRightMargin) + ) + clipsToBounds = false + contentView.clipsToBounds = false + } + + private func updateViews() { + shapeCardView.viewModel = viewModel + } + +} diff --git a/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapeCell/ShapeCollectionViewCells.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapeCell/ShapeCollectionViewCells.swift new file mode 100644 index 0000000..a366545 --- /dev/null +++ b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapeCell/ShapeCollectionViewCells.swift @@ -0,0 +1,30 @@ +// +// ShapeCollectionViewCells.swift +// CollectionViewPagingLayout +// +// Created by Amir on 15/02/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit +import CollectionViewPagingLayout + +class ScaleShapeCollectionViewCell: BaseShapeCollectionViewCell, ScaleTransformView { + var scaleOptions: ScaleTransformViewOptions { + ShapesViewModel.scaleOptions + } +} + + +class StackShapeCollectionViewCell: BaseShapeCollectionViewCell, StackTransformView { + var stackOptions: StackTransformViewOptions { + ShapesViewModel.stackOptions + } +} + + +class SnapshotShapeCollectionViewCell: BaseShapeCollectionViewCell, SnapshotTransformView { + var snapshotOptions: SnapshotTransformViewOptions { + ShapesViewModel.snapshotOptions + } +} 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/UIKit/Shapes/ShapesViewController.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapesViewController.swift new file mode 100644 index 0000000..8a07842 --- /dev/null +++ b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapesViewController.swift @@ -0,0 +1,259 @@ +// +// ShapesViewController.swift +// CollectionViewPagingLayout +// +// Created by Amir on 15/02/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit +import CollectionViewPagingLayout + +protocol ShapesViewControllerDelegate: AnyObject { + func shapesViewController(_ vc: ShapesViewController, onSelectedLayoutChange layout: ShapeLayout) +} + + +class ShapesViewController: UIViewController, NibBased, ViewModelBased { + + // MARK: Constants + + private struct Constants { + + static let infiniteNumberOfItems = 10_000 + } + + + // MARK: Properties + + var viewModel: ShapesViewModel! { + didSet { + updateSelectedLayout() + reloadDataAndLayouts() + } + } + + weak var delegate: ShapesViewControllerDelegate? + + @IBOutlet private weak var backButton: UIButton! + @IBOutlet private weak var collectionView: UICollectionView! + @IBOutlet private weak var layoutTypeCollectionView: UICollectionView! + @IBOutlet private weak var pageControlView: PageControlView! + + private var didScrollCollectionViewToMiddle = false + + + // MARK: UIViewController + + override func viewDidLoad() { + super.viewDidLoad() + configureViews() + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + if !didScrollCollectionViewToMiddle { + didScrollCollectionViewToMiddle = true + collectionView?.performBatchUpdates({ [weak self] in + self?.getPagingLayout(layoutTypeCollectionView)?.setCurrentPage( + Constants.infiniteNumberOfItems / 2, + animated: false + ) + }) + } + + updateSelectedLayout() + getPagingLayout(layoutTypeCollectionView)?.invalidateLayoutInBatchUpdate() + } + + + // MARK: Public functions + + func reloadAndInvalidateShapes() { + collectionView?.reloadData() + invalidateCollectionViewLayout() + } + + + // MARK: Event listener + + @IBAction private func onBackTouched() { + navigationController?.popViewController(animated: true) + } + + @IBAction private func onNextButtonTouched() { + (collectionView.collectionViewLayout as? CollectionViewPagingLayout)?.goToNextPage() + } + + @IBAction private func onPreviousButtonTouched() { + (collectionView.collectionViewLayout as? CollectionViewPagingLayout)?.goToPreviousPage() + } + + + // MARK: Private functions + + private func configureViews() { + view.backgroundColor = #colorLiteral(red: 0.9529411765, green: 0.9529411765, blue: 0.9529411765, alpha: 1) + view.clipsToBounds = true + backButton.isHidden = !viewModel.showBackButton + configureCollectionView() + configureLayoutTypeCollectionView() + + pageControlView.numberOfPages = 8 + pageControlView.preferences = .init(color: UIColor.black.withAlphaComponent(0.5), dimFactor: 0.2, dotRadius: 5, gapSize: 6, currentDotBorderWidth: 3.5) + pageControlView.superview?.isHidden = !viewModel.showPageControl + } + + private func configureCollectionView() { + collectionView.registerClass(StackShapeCollectionViewCell.self) + collectionView.registerClass(ScaleShapeCollectionViewCell.self) + collectionView.registerClass(SnapshotShapeCollectionViewCell.self) + + collectionView.isPagingEnabled = true + collectionView.dataSource = self + let layout = CollectionViewPagingLayout() + collectionView.collectionViewLayout = layout + layout.delegate = self + collectionView.showsHorizontalScrollIndicator = false + collectionView.clipsToBounds = false + collectionView.backgroundColor = .clear + collectionView.delegate = self + } + + private func configureLayoutTypeCollectionView() { + layoutTypeCollectionView.register(LayoutTypeCollectionViewCell.self) + layoutTypeCollectionView.isPagingEnabled = true + layoutTypeCollectionView.dataSource = self + let layout = CollectionViewPagingLayout() + layout.numberOfVisibleItems = 10 + layoutTypeCollectionView.collectionViewLayout = layout + layoutTypeCollectionView.showsHorizontalScrollIndicator = false + layoutTypeCollectionView.clipsToBounds = false + layoutTypeCollectionView.backgroundColor = .clear + layoutTypeCollectionView.delegate = self + } + + private func updateSelectedLayout() { + 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) + reloadAndInvalidateShapes() + } + + private func reloadDataAndLayouts() { + layoutTypeCollectionView?.reloadData() + collectionView?.reloadData() + invalidateLayouts() + } + + private func invalidateLayouts() { + invalidateLayoutTypeCollectionViewLayout() + invalidateCollectionViewLayout() + } + + private func invalidateLayoutTypeCollectionViewLayout() { + layoutTypeCollectionView?.performBatchUpdates({ [weak self] in + self?.layoutTypeCollectionView?.collectionViewLayout.invalidateLayout() + }) + } + + private func invalidateCollectionViewLayout() { + collectionView?.performBatchUpdates({ [weak self] in + self?.collectionView?.collectionViewLayout.invalidateLayout() + }) + } + + private func getPagingLayout(_ collectionView: UICollectionView?) -> CollectionViewPagingLayout? { + collectionView?.collectionViewLayout as? CollectionViewPagingLayout + } + +} + + +extension ShapesViewController: UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + if collectionView == layoutTypeCollectionView { + return Constants.infiniteNumberOfItems + } + if collectionView == self.collectionView { + return viewModel.selectedLayout.cardViewModels.count + } + + fatalError("unknown collection view") + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + if collectionView == layoutTypeCollectionView { + let itemViewModel = viewModel.layoutTypeViewModels[indexPath.row % viewModel.layoutTypeViewModels.count] + let cell: LayoutTypeCollectionViewCell = collectionView.dequeueReusableCell(for: indexPath) + cell.viewModel = itemViewModel + return cell + } + + if collectionView == self.collectionView { + let itemViewModel = viewModel.selectedLayout.cardViewModels[indexPath.row] + var cell: BaseShapeCollectionViewCell! + if ShapeLayout.scaleLayouts.contains(viewModel.selectedLayout.layout) { + cell = collectionView.dequeueReusableCellClass(for: indexPath) as ScaleShapeCollectionViewCell + + } else if ShapeLayout.stackLayouts.contains(viewModel.selectedLayout.layout) { + cell = collectionView.dequeueReusableCellClass(for: indexPath) as StackShapeCollectionViewCell + + } else if ShapeLayout.snapshotLayouts.contains(viewModel.selectedLayout.layout) { + cell = collectionView.dequeueReusableCellClass(for: indexPath) as SnapshotShapeCollectionViewCell + } + + cell.viewModel = itemViewModel + return cell + } + + fatalError("unknown collection view") + } + +} + + +extension ShapesViewController: UICollectionViewDelegate { + + func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + guard scrollView == layoutTypeCollectionView else { + return + } + if !decelerate { + updateSelectedLayout() + } + } + + func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + guard scrollView == layoutTypeCollectionView else { + return + } + updateSelectedLayout() + } + + func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { + guard scrollView == layoutTypeCollectionView else { + return + } + updateSelectedLayout() + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + (collectionView.collectionViewLayout as? CollectionViewPagingLayout)?.setCurrentPage(indexPath.row) { [weak self] in + DispatchQueue.main.async { + self?.updateSelectedLayout() + } + } + } +} + + +extension ShapesViewController: CollectionViewPagingLayoutDelegate { + func onCurrentPageChanged(layout: CollectionViewPagingLayout, currentPage: Int) { + guard layout.collectionView == collectionView else { return } + pageControlView.currentPage = currentPage + } +} diff --git a/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapesViewController.xib b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapesViewController.xib new file mode 100644 index 0000000..8367f48 --- /dev/null +++ b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapesViewController.xib @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapesViewModel.swift b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapesViewModel.swift new file mode 100644 index 0000000..b67cd0c --- /dev/null +++ b/Samples/PagingLayoutSamples/Modules/UIKit/Shapes/ShapesViewModel.swift @@ -0,0 +1,195 @@ +// +// ShapesViewModel.swift +// CollectionViewPagingLayout +// +// Created by Amir on 15/02/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit +import CollectionViewPagingLayout + +class ShapesViewModel { + + // MARK: Static Properties + + private(set) static var scaleOptions: ScaleTransformViewOptions = .init() + private(set) static var stackOptions: StackTransformViewOptions = .init() + private(set) static var snapshotOptions: SnapshotTransformViewOptions = .init() + + + // MARK: Properties + + var selectedLayout: LayoutTypeCellViewModel { + didSet { + if let options = selectedLayout.layout.scaleOptions { + ShapesViewModel.scaleOptions = options + } else if let options = selectedLayout.layout.stackOptions { + ShapesViewModel.stackOptions = options + } else if let options = selectedLayout.layout.snapshotOptions { + ShapesViewModel.snapshotOptions = options + } + } + } + let layoutTypeViewModels: [LayoutTypeCellViewModel] + let showBackButton: Bool + let showPageControl: Bool + + + // MARK: Lifecycle + + init(layouts: [ShapeLayout], showBackButton: Bool = true, showPageControl: Bool = false) { + self.showBackButton = showBackButton + self.showPageControl = showPageControl + self.layoutTypeViewModels = layouts.compactMap { layout in ShapesViewModel.allLayoutViewModes.first { $0.layout == layout } } + selectedLayout = layoutTypeViewModels.first! + } + + + // MARK: Public functions + + func setCustomOptions(_ options: T) { + if let options = options as? ScaleTransformViewOptions { + ShapesViewModel.scaleOptions = options + } else if let options = options as? StackTransformViewOptions { + ShapesViewModel.stackOptions = options + } else if let options = options as? SnapshotTransformViewOptions { + ShapesViewModel.snapshotOptions = options + } + } +} + + +extension ShapesViewModel { + + static let allLayoutViewModes: [LayoutTypeCellViewModel] = [ + .init(layout: .scaleInvertedCylinder, + iconName: "scale_invertedcylinder", + title: "Scale", + subtitle: "Inverted Cylinder", + cardViewModels: generateCardViewModels(colors: [#colorLiteral(red: 1, green: 0.4274509804, blue: 0.4, alpha: 1), #colorLiteral(red: 1, green: 0.7803921569, blue: 0, alpha: 1)])), + .init(layout: .scaleCylinder, + iconName: "scale_cylinder", + title: "Scale", + subtitle: "Cylinder", + cardViewModels: generateCardViewModels(colors: [#colorLiteral(red: 1, green: 0.4274509804, blue: 0.4, alpha: 1), #colorLiteral(red: 1, green: 0.7803921569, blue: 0, alpha: 1)])), + .init(layout: .scaleCoverFlow, + iconName: "scale_coverflow", + title: "Scale", + subtitle: "Cover Flow", + cardViewModels: generateCardViewModels(colors: [#colorLiteral(red: 1, green: 0.4274509804, blue: 0.4, alpha: 1), #colorLiteral(red: 1, green: 0.7803921569, blue: 0, alpha: 1)])), + .init(layout: .scaleLinear, + iconName: "scale_normal", + title: "Scale", + subtitle: "Linear", + cardViewModels: generateCardViewModels(colors: [#colorLiteral(red: 1, green: 0.4274509804, blue: 0.4, alpha: 1), #colorLiteral(red: 1, green: 0.7803921569, blue: 0, alpha: 1)])), + .init(layout: .scaleEaseIn, + iconName: "scale_normal", + title: "Scale", + subtitle: "EaseIn", + cardViewModels: generateCardViewModels(colors: [#colorLiteral(red: 1, green: 0.4274509804, blue: 0.4, alpha: 1), #colorLiteral(red: 1, green: 0.7803921569, blue: 0, alpha: 1)])), + .init(layout: .scaleEaseOut, + iconName: "scale_normal", + title: "Scale", + subtitle: "EaseOut", + cardViewModels: generateCardViewModels(colors: [#colorLiteral(red: 1, green: 0.4274509804, blue: 0.4, alpha: 1), #colorLiteral(red: 1, green: 0.7803921569, blue: 0, alpha: 1)])), + .init(layout: .scaleRotary, + iconName: "scale_rotary", + title: "Scale", + subtitle: "Rotary", + cardViewModels: generateCardViewModels(colors: [#colorLiteral(red: 1, green: 0.4274509804, blue: 0.4, alpha: 1), #colorLiteral(red: 1, green: 0.7803921569, blue: 0, alpha: 1)])), + .init(layout: .scaleBlur, + iconName: "scale_blur", + title: "Scale", + subtitle: "Blurry", + cardViewModels: generateCardViewModels(colors: [#colorLiteral(red: 1, green: 0.4274509804, blue: 0.4, alpha: 1), #colorLiteral(red: 1, green: 0.7803921569, blue: 0, alpha: 1)])), + + .init(layout: .stackTransparent, + iconName: "stack_transparent", + title: "Stack", + subtitle: "Transparent", + cardViewModels: generateCardViewModels(colors: [#colorLiteral(red: 0.3058823529, green: 1, blue: 0.9490196078, alpha: 1), #colorLiteral(red: 0.1137254902, green: 0.4156862745, blue: 0.6666666667, alpha: 1)])), + .init(layout: .stackPerspective, + iconName: "stack_prespective", + title: "Stack", + subtitle: "Perspective", + cardViewModels: generateCardViewModels(colors: [#colorLiteral(red: 0.3058823529, green: 1, blue: 0.9490196078, alpha: 1), #colorLiteral(red: 0.1137254902, green: 0.4156862745, blue: 0.6666666667, alpha: 1)])), + .init(layout: .stackRotary, + iconName: "stack_rotary", + title: "Stack", + subtitle: "Rotary", + cardViewModels: generateCardViewModels(colors: [#colorLiteral(red: 0.3058823529, green: 1, blue: 0.9490196078, alpha: 1), #colorLiteral(red: 0.1137254902, green: 0.4156862745, blue: 0.6666666667, alpha: 1)])), + .init(layout: .stackBlur, + iconName: "stack_blur", + title: "Stack", + subtitle: "Blur", + cardViewModels: generateCardViewModels(colors: [#colorLiteral(red: 0.3058823529, green: 1, blue: 0.9490196078, alpha: 1), #colorLiteral(red: 0.1137254902, green: 0.4156862745, blue: 0.6666666667, alpha: 1)])), + .init(layout: .stackVortex, + iconName: "stack_vortex", + title: "Stack", + subtitle: "Vortex", + cardViewModels: generateCardViewModels(colors: [#colorLiteral(red: 0.3058823529, green: 1, blue: 0.9490196078, alpha: 1), #colorLiteral(red: 0.1137254902, green: 0.4156862745, blue: 0.6666666667, alpha: 1)])), + .init(layout: .stackReverse, + iconName: "stack_reverse", + title: "Stack", + subtitle: "Reverse", + cardViewModels: generateCardViewModels(colors: [#colorLiteral(red: 0.3058823529, green: 1, blue: 0.9490196078, alpha: 1), #colorLiteral(red: 0.1137254902, green: 0.4156862745, blue: 0.6666666667, alpha: 1)])), + + .init(layout: .snapshotGrid, + iconName: "snapshot_grid", + title: "Snapshot", + subtitle: "Grid", + cardViewModels: generateCardViewModels(colors: [#colorLiteral(red: 0.9176470588, green: 1, blue: 0, alpha: 1), #colorLiteral(red: 0.3137254902, green: 0.8, blue: 1, alpha: 1)])), + .init(layout: .snapshotSpace, + iconName: "snapshot_space", + title: "Snapshot", + subtitle: "Space", + cardViewModels: generateCardViewModels(colors: [#colorLiteral(red: 0.9176470588, green: 1, blue: 0, alpha: 1), #colorLiteral(red: 0.3137254902, green: 0.8, blue: 1, alpha: 1)])), + .init(layout: .snapshotChess, + iconName: "snapshot_chess", + title: "Snapshot", + subtitle: "Chess", + cardViewModels: generateCardViewModels(colors: [#colorLiteral(red: 0.9176470588, green: 1, blue: 0, alpha: 1), #colorLiteral(red: 0.3137254902, green: 0.8, blue: 1, alpha: 1)])), + .init(layout: .snapshotTiles, + iconName: "snapshot_tiles", + title: "Snapshot", + subtitle: "Tiles", + cardViewModels: generateCardViewModels(colors: [#colorLiteral(red: 0.9176470588, green: 1, blue: 0, alpha: 1), #colorLiteral(red: 0.3137254902, green: 0.8, blue: 1, alpha: 1)])), + .init(layout: .snapshotLines, + iconName: "snapshot_lines", + title: "Snapshot", + subtitle: "Lines", + cardViewModels: generateCardViewModels(colors: [#colorLiteral(red: 0.9176470588, green: 1, blue: 0, alpha: 1), #colorLiteral(red: 0.3137254902, green: 0.8, blue: 1, alpha: 1)])), + .init(layout: .snapshotBars, + iconName: "snapshot_bars", + title: "Snapshot", + subtitle: "Bars", + cardViewModels: generateCardViewModels(colors: [#colorLiteral(red: 0.9176470588, green: 1, blue: 0, alpha: 1), #colorLiteral(red: 0.3137254902, green: 0.8, blue: 1, alpha: 1)])), + .init(layout: .snapshotPuzzle, + iconName: "snapshot_puzzle", + title: "Snapshot", + subtitle: "Puzzle", + cardViewModels: generateCardViewModels(colors: [#colorLiteral(red: 0.9176470588, green: 1, blue: 0, alpha: 1), #colorLiteral(red: 0.3137254902, green: 0.8, blue: 1, alpha: 1)])), + .init(layout: .snapshotFade, + iconName: "snapshot_fade", + title: "Snapshot", + subtitle: "Fade", + cardViewModels: generateCardViewModels(colors: [#colorLiteral(red: 0.9176470588, green: 1, blue: 0, alpha: 1), #colorLiteral(red: 0.3137254902, green: 0.8, blue: 1, alpha: 1)])) + ] + + private static func generateCardViewModels(colors: [UIColor]) -> [ShapeCardViewModel] { + 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") + ] + return shapes.map { + ShapeCardViewModel(iconName: $0.iconName, title: $0.name, colors: colors) + } + } +} diff --git a/Samples/PagingLayoutSamples/Paging Layout.entitlements b/Samples/PagingLayoutSamples/Paging Layout.entitlements new file mode 100644 index 0000000..a046386 --- /dev/null +++ b/Samples/PagingLayoutSamples/Paging Layout.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-write + + com.apple.security.network.client + + + diff --git a/Samples/PagingLayoutSamples/PagingLayoutSamples-Bridging-Header.h b/Samples/PagingLayoutSamples/PagingLayoutSamples-Bridging-Header.h new file mode 100644 index 0000000..fe85d53 --- /dev/null +++ b/Samples/PagingLayoutSamples/PagingLayoutSamples-Bridging-Header.h @@ -0,0 +1,5 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#import "AppKitBridge.h" 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 new file mode 100644 index 0000000..3aad101 --- /dev/null +++ b/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout.xcodeproj_sample/project.pbxproj_sample @@ -0,0 +1,356 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 52; + objects = { + +/* Begin PBXBuildFile section */ + 29A2D3E524B76783005A0F6B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29A2D3E424B76783005A0F6B /* AppDelegate.swift */; }; + 29A2D3E924B76783005A0F6B /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29A2D3E824B76783005A0F6B /* ViewController.swift */; }; + 29A2D3EE24B76783005A0F6B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 29A2D3ED24B76783005A0F6B /* Assets.xcassets */; }; + 29A2D3F124B76783005A0F6B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 29A2D3EF24B76783005A0F6B /* LaunchScreen.storyboard */; }; + 29A2D3FA24B767E0005A0F6B /* CollectionViewPagingLayout in Frameworks */ = {isa = PBXBuildFile; productRef = 29A2D3F924B767E0005A0F6B /* CollectionViewPagingLayout */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 29A2D3E124B76783005A0F6B /* PagingLayout.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PagingLayout.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 29A2D3E424B76783005A0F6B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 29A2D3E824B76783005A0F6B /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 29A2D3ED24B76783005A0F6B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 29A2D3F024B76783005A0F6B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 29A2D3F224B76783005A0F6B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 29A2D3DE24B76783005A0F6B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 29A2D3FA24B767E0005A0F6B /* CollectionViewPagingLayout in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 29A2D3D824B76783005A0F6B = { + isa = PBXGroup; + children = ( + 29A2D3E324B76783005A0F6B /* PagingLayout */, + 29A2D3E224B76783005A0F6B /* Products */, + ); + sourceTree = ""; + }; + 29A2D3E224B76783005A0F6B /* Products */ = { + isa = PBXGroup; + children = ( + 29A2D3E124B76783005A0F6B /* PagingLayout.app */, + ); + name = Products; + sourceTree = ""; + }; + 29A2D3E324B76783005A0F6B /* PagingLayout */ = { + isa = PBXGroup; + children = ( + 29A2D3E424B76783005A0F6B /* AppDelegate.swift */, + 29A2D3E824B76783005A0F6B /* ViewController.swift */, + 29A2D3ED24B76783005A0F6B /* Assets.xcassets */, + 29A2D3EF24B76783005A0F6B /* LaunchScreen.storyboard */, + 29A2D3F224B76783005A0F6B /* Info.plist */, + ); + path = PagingLayout; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 29A2D3E024B76783005A0F6B /* PagingLayout */ = { + isa = PBXNativeTarget; + buildConfigurationList = 29A2D3F524B76783005A0F6B /* Build configuration list for PBXNativeTarget "PagingLayout" */; + buildPhases = ( + 29A2D3DD24B76783005A0F6B /* Sources */, + 29A2D3DE24B76783005A0F6B /* Frameworks */, + 29A2D3DF24B76783005A0F6B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = PagingLayout; + packageProductDependencies = ( + 29A2D3F924B767E0005A0F6B /* CollectionViewPagingLayout */, + ); + productName = PagingLayout; + productReference = 29A2D3E124B76783005A0F6B /* PagingLayout.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 29A2D3D924B76783005A0F6B /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1150; + LastUpgradeCheck = 1150; + ORGANIZATIONNAME = none; + TargetAttributes = { + 29A2D3E024B76783005A0F6B = { + CreatedOnToolsVersion = 11.5; + }; + }; + }; + buildConfigurationList = 29A2D3DC24B76783005A0F6B /* Build configuration list for PBXProject "PagingLayout" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 29A2D3D824B76783005A0F6B; + packageReferences = ( + 29A2D3F824B767E0005A0F6B /* XCRemoteSwiftPackageReference "CollectionViewPagingLayout" */, + ); + productRefGroup = 29A2D3E224B76783005A0F6B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 29A2D3E024B76783005A0F6B /* PagingLayout */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 29A2D3DF24B76783005A0F6B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 29A2D3F124B76783005A0F6B /* LaunchScreen.storyboard in Resources */, + 29A2D3EE24B76783005A0F6B /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 29A2D3DD24B76783005A0F6B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 29A2D3E924B76783005A0F6B /* ViewController.swift in Sources */, + 29A2D3E524B76783005A0F6B /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 29A2D3EF24B76783005A0F6B /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 29A2D3F024B76783005A0F6B /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 29A2D3F324B76783005A0F6B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 29A2D3F424B76783005A0F6B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 29A2D3F624B76783005A0F6B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = PagingLayout/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.none.PagingLayout; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Debug; + }; + 29A2D3F724B76783005A0F6B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = PagingLayout/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.none.PagingLayout; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 29A2D3DC24B76783005A0F6B /* Build configuration list for PBXProject "PagingLayout" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 29A2D3F324B76783005A0F6B /* Debug */, + 29A2D3F424B76783005A0F6B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 29A2D3F524B76783005A0F6B /* Build configuration list for PBXNativeTarget "PagingLayout" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 29A2D3F624B76783005A0F6B /* Debug */, + 29A2D3F724B76783005A0F6B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 29A2D3F824B767E0005A0F6B /* XCRemoteSwiftPackageReference "CollectionViewPagingLayout" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/amirdew/CollectionViewPagingLayout"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 29A2D3F924B767E0005A0F6B /* CollectionViewPagingLayout */ = { + isa = XCSwiftPackageProductDependency; + package = 29A2D3F824B767E0005A0F6B /* XCRemoteSwiftPackageReference "CollectionViewPagingLayout" */; + productName = CollectionViewPagingLayout; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 29A2D3D924B76783005A0F6B /* Project object */; +} diff --git a/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout.xcodeproj_sample/project.xcworkspace_sample/contents.xcworkspacedata b/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout.xcodeproj_sample/project.xcworkspace_sample/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout.xcodeproj_sample/project.xcworkspace_sample/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout.xcodeproj_sample/project.xcworkspace_sample/xcshareddata/IDEWorkspaceChecks.plist b/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout.xcodeproj_sample/project.xcworkspace_sample/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout.xcodeproj_sample/project.xcworkspace_sample/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout.xcodeproj_sample/project.xcworkspace_sample/xcshareddata/swiftpm/Package.resolved b/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout.xcodeproj_sample/project.xcworkspace_sample/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..f7adb29 --- /dev/null +++ b/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout.xcodeproj_sample/project.xcworkspace_sample/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "CollectionViewPagingLayout", + "repositoryURL": "https://github.com/amirdew/CollectionViewPagingLayout", + "state": { + "branch": null, + "revision": "d120b9c01469df42f371e2cf42d612cc04db8c94", + "version": "0.3.0" + } + } + ] + }, + "version": 1 +} diff --git a/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout/AppDelegate.swift b/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout/AppDelegate.swift new file mode 100644 index 0000000..4c4f416 --- /dev/null +++ b/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout/AppDelegate.swift @@ -0,0 +1,23 @@ +// +// AppDelegate.swift +// PagingLayout +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + let mainVC = ViewController() + window = UIWindow() + window!.rootViewController = mainVC + window!.makeKeyAndVisible() + return true + } + + +} diff --git a/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout/Assets.xcassets/AppIcon.appiconset/Contents.json b/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..9221b9b --- /dev/null +++ b/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout/Assets.xcassets/Contents.json b/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/CollectionViewPagingLayout/Base.lproj/LaunchScreen.storyboard b/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from CollectionViewPagingLayout/Base.lproj/LaunchScreen.storyboard rename to Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout/Base.lproj/LaunchScreen.storyboard diff --git a/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout/Info_sample.plist b/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout/Info_sample.plist new file mode 100644 index 0000000..ff665c4 --- /dev/null +++ b/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout/Info_sample.plist @@ -0,0 +1,46 @@ + + + + + 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 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout/ViewController.swift b/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout/ViewController.swift new file mode 100644 index 0000000..a80aaab --- /dev/null +++ b/Samples/PagingLayoutSamples/SampleProject.bundle/SampleProject/PagingLayout/ViewController.swift @@ -0,0 +1,16 @@ +// +// ViewController.swift +// PagingLayout +// + +import UIKit + +class ViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + } + + +} diff --git a/Samples/PagingLayoutSamples/Utilities/CGFloat+String.swift b/Samples/PagingLayoutSamples/Utilities/CGFloat+String.swift new file mode 100644 index 0000000..066cb63 --- /dev/null +++ b/Samples/PagingLayoutSamples/Utilities/CGFloat+String.swift @@ -0,0 +1,33 @@ +// +// CGFloat+String.swift +// PagingLayoutSamples +// +// Created by Amir on 28/06/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit + + +extension String { + var floatValue: Float? { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.allowsFloats = true + formatter.maximumFractionDigits = 5 + formatter.locale = Locale(identifier: "en_US") + let text = self.replacingOccurrences(of: ",", with: ".") + let number = formatter.number(from: text) + return number?.floatValue + } +} + +extension CGFloat { + func format(fractionDigits: Int = 2) -> String { + var formatted = String(format: "%.\(fractionDigits)f", self) + if self == 0 { + formatted = formatted.replacingOccurrences(of: "-0.00", with: "0.00") + } + return formatted + } +} diff --git a/CollectionViewPagingLayout/Utilities/NibBased.swift b/Samples/PagingLayoutSamples/Utilities/NibBased.swift similarity index 57% rename from CollectionViewPagingLayout/Utilities/NibBased.swift rename to Samples/PagingLayoutSamples/Utilities/NibBased.swift index d9187c3..b2b9456 100644 --- a/CollectionViewPagingLayout/Utilities/NibBased.swift +++ b/Samples/PagingLayoutSamples/Utilities/NibBased.swift @@ -35,7 +35,7 @@ extension NibBased where Self: UIView { static func instantiate(owner: Any? = nil) -> Self { let nib = UINib(nibName: nibName, bundle: nil) let items = nib.instantiate(withOwner: owner, options: nil) - return items.first! as! Self + return (items.first! as? Self)! } } @@ -60,8 +60,15 @@ extension NibBased where Self: UICollectionViewCell { UINib(nibName: String(describing: self), bundle: nil) } - static var reuseIdentifier: String { - String(describing: self) +} + + +extension NibBased where Self: UITableViewCell { + + // MARK: Static properties + + static var nib: UINib { + UINib(nibName: String(describing: self), bundle: nil) } } @@ -71,12 +78,28 @@ extension UICollectionView { // MARK: Public functions - func register(_ cellType: T.Type) { - register(T.nib, forCellWithReuseIdentifier: T.reuseIdentifier) + func register(_ cellType: T.Type, nibName: String? = nil) { + let nib = nibName.let { UINib(nibName: $0, bundle: nil) } ?? T.nib + register(nib, forCellWithReuseIdentifier: T.reuseIdentifier) } func dequeueReusableCell(for indexPath: IndexPath) -> T { - dequeueReusableCell(withReuseIdentifier: T.reuseIdentifier, for: indexPath) as! T + (dequeueReusableCell(withReuseIdentifier: T.reuseIdentifier, for: indexPath) as? T)! + } + +} + +extension UITableView { + + // MARK: Public functions + + func register(_ cellType: T.Type, nibName: String? = nil) { + let nib = nibName.let { UINib(nibName: $0, bundle: nil) } ?? T.nib + register(nib, forCellReuseIdentifier: T.reuseIdentifier) + } + + func dequeueReusableCell(for indexPath: IndexPath) -> T { + (dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as? T)! } } diff --git a/Samples/PagingLayoutSamples/Utilities/Optional+Let.swift b/Samples/PagingLayoutSamples/Utilities/Optional+Let.swift new file mode 100644 index 0000000..30e0718 --- /dev/null +++ b/Samples/PagingLayoutSamples/Utilities/Optional+Let.swift @@ -0,0 +1,18 @@ +// +// Optional+Let.swift +// CollectionViewPagingLayout +// +// Created by Amir on 15/02/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import Foundation + +extension Optional { + func `let`(_ transform: (Wrapped) -> T?) -> T? { + if case .some(let value) = self { + return transform(value) + } + return nil + } +} diff --git a/Samples/PagingLayoutSamples/Utilities/TransformCurve+Name.swift b/Samples/PagingLayoutSamples/Utilities/TransformCurve+Name.swift new file mode 100644 index 0000000..4e6a924 --- /dev/null +++ b/Samples/PagingLayoutSamples/Utilities/TransformCurve+Name.swift @@ -0,0 +1,40 @@ +// +// TransformCurve+Name.swift +// PagingLayoutSamples +// +// Created by Amir on 28/06/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import CollectionViewPagingLayout + +extension TransformCurve { + + static var all: [TransformCurve] { + [.linear, .easeIn, .easeOut] + } + + static func by(name: String) -> TransformCurve? { + switch name.lowercased() { + case "EaseIn".lowercased(): + return .easeIn + case "EaseOut".lowercased(): + return .easeOut + case "Linear".lowercased(): + return .linear + default: + return nil + } + } + + var name: String { + switch self { + case .easeIn: + return "EaseIn" + case .easeOut: + return "EaseOut" + case .linear: + return "Linear" + } + } +} diff --git a/Samples/PagingLayoutSamples/Utilities/UIBlurEffect.Style+Name.swift b/Samples/PagingLayoutSamples/Utilities/UIBlurEffect.Style+Name.swift new file mode 100644 index 0000000..0ef75fb --- /dev/null +++ b/Samples/PagingLayoutSamples/Utilities/UIBlurEffect.Style+Name.swift @@ -0,0 +1,42 @@ +// +// UIBlurEffect.Style+Name.swift +// PagingLayoutSamples +// +// Created by Amir on 28/06/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit + +extension UIBlurEffect.Style { + + static var all: [UIBlurEffect.Style] { + [.dark, .regular, .light] + } + + static func by(name: String) -> UIBlurEffect.Style? { + switch name.lowercased() { + case "Dark".lowercased(): + return .dark + case "Regular".lowercased(): + return .regular + case "Light".lowercased(): + return .light + default: + return nil + } + } + + var name: String { + switch self { + case .dark: + return "Dark" + case .regular: + return "Regular" + case .light: + return "Light" + default: + return "" + } + } +} diff --git a/Samples/PagingLayoutSamples/Utilities/UICollectionViewCell+Utilities.swift b/Samples/PagingLayoutSamples/Utilities/UICollectionViewCell+Utilities.swift new file mode 100644 index 0000000..9eb4aed --- /dev/null +++ b/Samples/PagingLayoutSamples/Utilities/UICollectionViewCell+Utilities.swift @@ -0,0 +1,32 @@ +// +// UICollectionViewCell+Utilities.swift +// CollectionViewPagingLayout +// +// Created by Amir Khorsandi on 2/16/20. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit + +extension UICollectionView { + + // MARK: Public functions + + 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)! + } + +} + + +extension UICollectionViewCell { + + static var reuseIdentifier: String { + String(describing: self) + } + +} diff --git a/Samples/PagingLayoutSamples/Utilities/UITableView+Utilities.swift b/Samples/PagingLayoutSamples/Utilities/UITableView+Utilities.swift new file mode 100644 index 0000000..3b44ed0 --- /dev/null +++ b/Samples/PagingLayoutSamples/Utilities/UITableView+Utilities.swift @@ -0,0 +1,32 @@ +// +// UITableView+Utilities.swift +// PagingLayoutSamples +// +// Created by Amir on 21/05/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import UIKit + +extension UITableView { + + // MARK: Public functions + + func registerClass(_ cellType: T.Type, reuseIdentifier: String = T.reuseIdentifier) { + register(cellType, forCellReuseIdentifier: reuseIdentifier) + } + + func dequeueReusableCellClass(for indexPath: IndexPath, type: T.Type? = nil, reuseIdentifier: String = T.reuseIdentifier) -> T { + (dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as? T)! + } + +} + + +extension UITableViewCell { + + static var reuseIdentifier: String { + String(describing: self) + } + +} diff --git a/Samples/PagingLayoutSamples/Utilities/Values+Pair.swift b/Samples/PagingLayoutSamples/Utilities/Values+Pair.swift new file mode 100644 index 0000000..b385c34 --- /dev/null +++ b/Samples/PagingLayoutSamples/Utilities/Values+Pair.swift @@ -0,0 +1,29 @@ +// +// CGPoint.swift +// PagingLayoutSamples +// +// Created by Amir on 28/06/2020. +// Copyright © 2020 Amir Khorsandi. All rights reserved. +// + +import Foundation +import UIKit + +extension CGSize { + var pair: (CGFloat, CGFloat) { + (width, height) + } + static func by(pair: (CGFloat, CGFloat)) -> CGSize { + .init(width: pair.0, height: pair.1) + } +} + + +extension CGPoint { + var pair: (CGFloat, CGFloat) { + (x, y) + } + static func by(pair: (CGFloat, CGFloat)) -> CGPoint { + .init(x: pair.0, y: pair.1) + } +} diff --git a/CollectionViewPagingLayout/Utilities/ViewModelBased.swift b/Samples/PagingLayoutSamples/Utilities/ViewModelBased.swift similarity index 93% rename from CollectionViewPagingLayout/Utilities/ViewModelBased.swift rename to Samples/PagingLayoutSamples/Utilities/ViewModelBased.swift index 4096943..d60ceb8 100644 --- a/CollectionViewPagingLayout/Utilities/ViewModelBased.swift +++ b/Samples/PagingLayoutSamples/Utilities/ViewModelBased.swift @@ -12,7 +12,7 @@ import UIKit protocol ViewModelBased { associatedtype ViewModelType - var viewModel: ViewModelType! { set get } + var viewModel: ViewModelType! { get set } } 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 new file mode 100644 index 0000000..164b4d3 --- /dev/null +++ b/Samples/Podfile @@ -0,0 +1,8 @@ +platform :ios, '14.0' + +target 'PagingLayoutSamples' do + use_frameworks! + pod 'CollectionViewPagingLayout', :path => './../' + pod 'Splash', :git => 'https://github.com/amirdew/Splash.git' + pod 'SwiftLint' +end diff --git a/Samples/Podfile.lock b/Samples/Podfile.lock new file mode 100644 index 0000000..6bb5023 --- /dev/null +++ b/Samples/Podfile.lock @@ -0,0 +1,33 @@ +PODS: + - CollectionViewPagingLayout (1.1.0) + - Splash (0.13.0) + - SwiftLint (0.47.0) + +DEPENDENCIES: + - CollectionViewPagingLayout (from `./../`) + - Splash (from `https://github.com/amirdew/Splash.git`) + - SwiftLint + +SPEC REPOS: + trunk: + - SwiftLint + +EXTERNAL SOURCES: + CollectionViewPagingLayout: + :path: "./../" + Splash: + :git: https://github.com/amirdew/Splash.git + +CHECKOUT OPTIONS: + Splash: + :commit: 919ad99dfdc16b82665c44c3b6494f4e5f22bcec + :git: https://github.com/amirdew/Splash.git + +SPEC CHECKSUMS: + CollectionViewPagingLayout: 2f8d50683bae57ca88f47fa4ab0195c9079582cf + Splash: 5ec9a07c4bbf047ddd659bfc11d35331d31004f7 + SwiftLint: d41cc46a2ae58ac6d9f26954bc89f1d72e71fdef + +PODFILE CHECKSUM: a829b1accd80b68e4033b32927c85146318f7597 + +COCOAPODS: 1.11.3