├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── Example ├── Podfile ├── Podfile.lock ├── Pods │ ├── Local Podspecs │ │ └── StoriesLayout.podspec.json │ ├── Manifest.lock │ ├── Pods.xcodeproj │ │ └── project.pbxproj │ └── Target Support Files │ │ ├── Pods-StoriesLayout_Example │ │ ├── Pods-StoriesLayout_Example-Info.plist │ │ ├── Pods-StoriesLayout_Example-acknowledgements.markdown │ │ ├── Pods-StoriesLayout_Example-acknowledgements.plist │ │ ├── Pods-StoriesLayout_Example-dummy.m │ │ ├── Pods-StoriesLayout_Example-frameworks.sh │ │ ├── Pods-StoriesLayout_Example-umbrella.h │ │ ├── Pods-StoriesLayout_Example.debug.xcconfig │ │ ├── Pods-StoriesLayout_Example.modulemap │ │ └── Pods-StoriesLayout_Example.release.xcconfig │ │ ├── Pods-StoriesLayout_Tests │ │ ├── Pods-StoriesLayout_Tests-Info.plist │ │ ├── Pods-StoriesLayout_Tests-acknowledgements.markdown │ │ ├── Pods-StoriesLayout_Tests-acknowledgements.plist │ │ ├── Pods-StoriesLayout_Tests-dummy.m │ │ ├── Pods-StoriesLayout_Tests-umbrella.h │ │ ├── Pods-StoriesLayout_Tests.debug.xcconfig │ │ ├── Pods-StoriesLayout_Tests.modulemap │ │ └── Pods-StoriesLayout_Tests.release.xcconfig │ │ └── StoriesLayout │ │ ├── StoriesLayout-Info.plist │ │ ├── StoriesLayout-dummy.m │ │ ├── StoriesLayout-prefix.pch │ │ ├── StoriesLayout-umbrella.h │ │ ├── StoriesLayout.modulemap │ │ └── StoriesLayout.xcconfig ├── StoriesLayout.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── StoriesLayout-Example.xcscheme ├── StoriesLayout.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── StoriesLayout │ ├── AppDelegate.swift │ ├── Base.lproj │ │ └── LaunchScreen.xib │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ └── storiesLayout │ │ │ ├── 001.imageset │ │ │ ├── Contents.json │ │ │ └── architecture-blue-sky-cliff-248771.jpg │ │ │ ├── 002.imageset │ │ │ ├── Contents.json │ │ │ └── bird-s-eye-view-curve-drone-photography-1658967.jpg │ │ │ ├── 003.imageset │ │ │ ├── Contents.json │ │ │ └── bay-beach-boats-919237.jpg │ │ │ ├── 004.imageset │ │ │ ├── Contents.json │ │ │ └── aerial-architecture-blue-731229.jpg │ │ │ ├── 005.imageset │ │ │ ├── Contents.json │ │ │ └── architecture-building-city-311066.jpg │ │ │ ├── 006.imageset │ │ │ ├── Contents.json │ │ │ └── aerial-architecture-buildings-434194.jpg │ │ │ └── Contents.json │ ├── Info.plist │ ├── LayoutCollectionViewCell.swift │ ├── LayoutCollectionViewCell.xib │ ├── MySafariCollectionViewCell.swift │ ├── MySafariCollectionViewCell.xib │ ├── MyStoriesCollectionViewCell.swift │ ├── MyStoriesCollectionViewCell.xib │ ├── ViewControllers │ │ ├── DemoController │ │ │ ├── DemoController.swift │ │ │ └── DemoRoutable.swift │ │ └── ListViewController │ │ │ └── ListViewController.swift │ └── ViewModels │ │ ├── ImageViewModel.swift │ │ ├── LayoutItemViewModel.swift │ │ └── Section.swift ├── Tests │ ├── Helpers │ │ └── CATransform3D+Helper.swift │ ├── Info.plist │ ├── Mocks │ │ └── UICollectionDataSourceMock.swift │ └── StoriesLayout │ │ ├── StoriesCollectionViewLayoutSpec.swift │ │ └── StoriesLayoutAttributesSpec.swift └── fastlane │ ├── Appfile │ ├── Fastfile │ ├── README.md │ └── report.xml ├── LICENSE ├── README.md ├── SafariLayout.podspec ├── SafariLayout └── Classes │ ├── SafariCollectionViewCell.swift │ ├── SafariCollectionViewLayout.swift │ └── SafariCollectionViewLayoutAttributes.swift ├── StoriesLayout.podspec ├── StoriesLayout ├── Assets │ └── .gitkeep └── Classes │ ├── .gitkeep │ ├── StoriesCollectionViewCell.swift │ ├── StoriesCollectionViewLayout.swift │ └── StoriesLayoutAttributes.swift └── _Pods.xcodeproj /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata/ 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # Bundler 23 | .bundle 24 | 25 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 26 | # Carthage/Checkouts 27 | 28 | Carthage/Build 29 | 30 | # We recommend against adding the Pods directory to your .gitignore. However 31 | # you should judge for yourself, the pros and cons are mentioned at: 32 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 33 | # 34 | # Note: if you ignore the Pods directory, make sure to uncomment 35 | # `pod install` in .travis.yml 36 | # 37 | Pods/ 38 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode10.2 3 | before_install: 4 | - gem update fastlane --no-document 5 | - gem update cocoapods --no-document 6 | script: 7 | - cd Example/ 8 | - fastlane ci 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a 11 | build. 12 | 2. Ensure your work is thoroughly tested, to the best of your abilities 13 | 2. You may merge the Pull Request in once you have the sign-off from a maintainer 14 | 15 | ## Code of Conduct 16 | 17 | ### Our Pledge 18 | 19 | In the interest of fostering an open and welcoming environment, we as 20 | contributors and maintainers pledge to making participation in our project and 21 | our community a harassment-free experience for everyone, regardless of age, body 22 | size, disability, ethnicity, gender identity and expression, level of experience, 23 | nationality, personal appearance, race, religion, or sexual identity and 24 | orientation. 25 | 26 | ### Our Standards 27 | 28 | Examples of behavior that contributes to creating a positive environment 29 | include: 30 | 31 | - Using welcoming and inclusive language 32 | - Being respectful of differing viewpoints and experiences 33 | - Gracefully accepting constructive criticism 34 | - Focusing on what is best for the community 35 | - Showing empathy towards other community members 36 | 37 | Examples of unacceptable behavior by participants include: 38 | 39 | - The use of sexualized language or imagery and unwelcome sexual attention or 40 | advances 41 | - Trolling, insulting/derogatory comments, and personal or political attacks 42 | - Public or private harassment 43 | - Publishing others' private information, such as a physical or electronic 44 | address, without explicit permission 45 | - Other conduct which could reasonably be considered inappropriate in a 46 | professional setting 47 | 48 | ### Our Responsibilities 49 | 50 | Project maintainers are responsible for clarifying the standards of acceptable 51 | behavior and are expected to take appropriate and fair corrective action in 52 | response to any instances of unacceptable behavior. 53 | 54 | Project maintainers have the right and responsibility to remove, edit, or 55 | reject comments, commits, code, wiki edits, issues, and other contributions 56 | that are not aligned to this Code of Conduct, or to ban temporarily or 57 | permanently any contributor for other behaviors that they deem inappropriate, 58 | threatening, offensive, or harmful. 59 | 60 | ### Scope 61 | 62 | This Code of Conduct applies both within project spaces and in public spaces 63 | when an individual is representing the project or its community. Examples of 64 | representing a project or community include using an official project e-mail 65 | address, posting via an official social media account, or acting as an appointed 66 | representative at an online or offline event. Representation of a project may be 67 | further defined and clarified by project maintainers. 68 | 69 | ### Enforcement 70 | 71 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 72 | reported by contacting the project team at andy@hankchizljaw.io. All 73 | complaints will be reviewed and investigated and will result in a response that 74 | is deemed necessary and appropriate to the circumstances. The project team is 75 | obligated to maintain confidentiality with regard to the reporter of an incident. 76 | Further details of specific enforcement policies may be posted separately. 77 | 78 | Project maintainers who do not follow or enforce the Code of Conduct in good 79 | faith may face temporary or permanent repercussions as determined by other 80 | members of the project's leadership. 81 | 82 | ### Attribution 83 | 84 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 85 | available at [http://contributor-covenant.org/version/1/4][version] 86 | 87 | [homepage]: http://contributor-covenant.org 88 | [version]: http://contributor-covenant.org/version/1/4/ 89 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '10.0' 2 | use_frameworks! 3 | 4 | target 'StoriesLayout_Example' do 5 | pod 'PowerTools', '~> 0.3' 6 | pod 'StoriesLayout', :path => '../' 7 | pod 'SafariLayout', :path => '../' 8 | 9 | target 'StoriesLayout_Tests' do 10 | inherit! :search_paths 11 | pod 'Quick' 12 | pod 'Nimble' 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Nimble (8.0.2) 3 | - PowerTools (0.3.0): 4 | - PowerTools/CollectionVM (= 0.3.0) 5 | - PowerTools/Core (= 0.3.0) 6 | - PowerTools/CollectionVM (0.3.0): 7 | - PowerTools/Core 8 | - PowerTools/Core (0.3.0) 9 | - Quick (2.1.0) 10 | - SafariLayout (0.1.1) 11 | - StoriesLayout (0.1.1) 12 | 13 | DEPENDENCIES: 14 | - Nimble 15 | - PowerTools (~> 0.3) 16 | - Quick 17 | - SafariLayout (from `../`) 18 | - StoriesLayout (from `../`) 19 | 20 | SPEC REPOS: 21 | https://github.com/cocoapods/specs.git: 22 | - Nimble 23 | - PowerTools 24 | - Quick 25 | 26 | EXTERNAL SOURCES: 27 | SafariLayout: 28 | :path: "../" 29 | StoriesLayout: 30 | :path: "../" 31 | 32 | SPEC CHECKSUMS: 33 | Nimble: 622629381bda1dd5678162f21f1368cec7cbba60 34 | PowerTools: c8c15dae195d431668aef9feb79b684ea764def9 35 | Quick: 4be43f6634acfa727dd106bdf3929ce125ffa79d 36 | SafariLayout: ead4b2e58882f84b0ebbd32cd3255795277ac744 37 | StoriesLayout: 694a3c67a705dbb8bdd6b4ce7c512fd7389d487c 38 | 39 | PODFILE CHECKSUM: 309fea3f2752413a2a2904e6e028b24f0d958e2a 40 | 41 | COCOAPODS: 1.7.2 42 | -------------------------------------------------------------------------------- /Example/Pods/Local Podspecs/StoriesLayout.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "StoriesLayout", 3 | "version": "0.1.1", 4 | "summary": "An Instagram Stories like UICollectionViewLayout", 5 | "swift_versions": "5.0", 6 | "description": "This is a UICollectionViewLayout that reproduce the Instagram Stories experience,\njust use StoriesCollectionViewLayout in your UICollectionView and subclass your cells from StoriesCollectionViewCell!", 7 | "homepage": "https://github.com/Oni-zerone/CollectionLayouts", 8 | "license": { 9 | "type": "MIT", 10 | "file": "LICENSE" 11 | }, 12 | "authors": { 13 | "Andrea Altea": "oni.zerone@gmail.com" 14 | }, 15 | "source": { 16 | "git": "https://github.com/Oni-zerone/CollectionLayouts.git", 17 | "tag": "StoriesLayout-0.1.1" 18 | }, 19 | "social_media_url": "https://twitter.com/Oni_zerone", 20 | "platforms": { 21 | "ios": "10.0" 22 | }, 23 | "source_files": "StoriesLayout/Classes/**/*", 24 | "frameworks": [ 25 | "UIKit", 26 | "CoreGraphics" 27 | ], 28 | "swift_version": "5.0" 29 | } 30 | -------------------------------------------------------------------------------- /Example/Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Nimble (8.0.2) 3 | - PowerTools (0.3.0): 4 | - PowerTools/CollectionVM (= 0.3.0) 5 | - PowerTools/Core (= 0.3.0) 6 | - PowerTools/CollectionVM (0.3.0): 7 | - PowerTools/Core 8 | - PowerTools/Core (0.3.0) 9 | - Quick (2.1.0) 10 | - SafariLayout (0.1.1) 11 | - StoriesLayout (0.1.1) 12 | 13 | DEPENDENCIES: 14 | - Nimble 15 | - PowerTools (~> 0.3) 16 | - Quick 17 | - SafariLayout (from `../`) 18 | - StoriesLayout (from `../`) 19 | 20 | SPEC REPOS: 21 | https://github.com/cocoapods/specs.git: 22 | - Nimble 23 | - PowerTools 24 | - Quick 25 | 26 | EXTERNAL SOURCES: 27 | SafariLayout: 28 | :path: "../" 29 | StoriesLayout: 30 | :path: "../" 31 | 32 | SPEC CHECKSUMS: 33 | Nimble: 622629381bda1dd5678162f21f1368cec7cbba60 34 | PowerTools: c8c15dae195d431668aef9feb79b684ea764def9 35 | Quick: 4be43f6634acfa727dd106bdf3929ce125ffa79d 36 | SafariLayout: ead4b2e58882f84b0ebbd32cd3255795277ac744 37 | StoriesLayout: 694a3c67a705dbb8bdd6b4ce7c512fd7389d487c 38 | 39 | PODFILE CHECKSUM: 309fea3f2752413a2a2904e6e028b24f0d958e2a 40 | 41 | COCOAPODS: 1.7.2 42 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-StoriesLayout_Example/Pods-StoriesLayout_Example-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-StoriesLayout_Example/Pods-StoriesLayout_Example-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## PowerTools 5 | 6 | Copyright (c) 2018 acct= 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | 27 | ## SafariLayout 28 | 29 | Copyright (c) 2019 oni.zerone@gmail.com 30 | 31 | Permission is hereby granted, free of charge, to any person obtaining a copy 32 | of this software and associated documentation files (the "Software"), to deal 33 | in the Software without restriction, including without limitation the rights 34 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 35 | copies of the Software, and to permit persons to whom the Software is 36 | furnished to do so, subject to the following conditions: 37 | 38 | The above copyright notice and this permission notice shall be included in 39 | all copies or substantial portions of the Software. 40 | 41 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 42 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 43 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 44 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 45 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 46 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 47 | THE SOFTWARE. 48 | 49 | 50 | ## StoriesLayout 51 | 52 | Copyright (c) 2019 oni.zerone@gmail.com 53 | 54 | Permission is hereby granted, free of charge, to any person obtaining a copy 55 | of this software and associated documentation files (the "Software"), to deal 56 | in the Software without restriction, including without limitation the rights 57 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 58 | copies of the Software, and to permit persons to whom the Software is 59 | furnished to do so, subject to the following conditions: 60 | 61 | The above copyright notice and this permission notice shall be included in 62 | all copies or substantial portions of the Software. 63 | 64 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 65 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 66 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 67 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 68 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 69 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 70 | THE SOFTWARE. 71 | 72 | Generated by CocoaPods - https://cocoapods.org 73 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-StoriesLayout_Example/Pods-StoriesLayout_Example-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Copyright (c) 2018 acct<blob>=<NULL> <oni.zerone@gmail.com> 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy 20 | of this software and associated documentation files (the "Software"), to deal 21 | in the Software without restriction, including without limitation the rights 22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | copies of the Software, and to permit persons to whom the Software is 24 | furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in 27 | all copies or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 35 | THE SOFTWARE. 36 | 37 | License 38 | MIT 39 | Title 40 | PowerTools 41 | Type 42 | PSGroupSpecifier 43 | 44 | 45 | FooterText 46 | Copyright (c) 2019 oni.zerone@gmail.com <oni.zerone@gmail.com> 47 | 48 | Permission is hereby granted, free of charge, to any person obtaining a copy 49 | of this software and associated documentation files (the "Software"), to deal 50 | in the Software without restriction, including without limitation the rights 51 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 52 | copies of the Software, and to permit persons to whom the Software is 53 | furnished to do so, subject to the following conditions: 54 | 55 | The above copyright notice and this permission notice shall be included in 56 | all copies or substantial portions of the Software. 57 | 58 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 59 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 60 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 61 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 62 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 63 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 64 | THE SOFTWARE. 65 | 66 | License 67 | MIT 68 | Title 69 | SafariLayout 70 | Type 71 | PSGroupSpecifier 72 | 73 | 74 | FooterText 75 | Copyright (c) 2019 oni.zerone@gmail.com <oni.zerone@gmail.com> 76 | 77 | Permission is hereby granted, free of charge, to any person obtaining a copy 78 | of this software and associated documentation files (the "Software"), to deal 79 | in the Software without restriction, including without limitation the rights 80 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 81 | copies of the Software, and to permit persons to whom the Software is 82 | furnished to do so, subject to the following conditions: 83 | 84 | The above copyright notice and this permission notice shall be included in 85 | all copies or substantial portions of the Software. 86 | 87 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 88 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 89 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 90 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 91 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 92 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 93 | THE SOFTWARE. 94 | 95 | License 96 | MIT 97 | Title 98 | StoriesLayout 99 | Type 100 | PSGroupSpecifier 101 | 102 | 103 | FooterText 104 | Generated by CocoaPods - https://cocoapods.org 105 | Title 106 | 107 | Type 108 | PSGroupSpecifier 109 | 110 | 111 | StringsTable 112 | Acknowledgements 113 | Title 114 | Acknowledgements 115 | 116 | 117 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-StoriesLayout_Example/Pods-StoriesLayout_Example-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_StoriesLayout_Example : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_StoriesLayout_Example 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-StoriesLayout_Example/Pods-StoriesLayout_Example-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | set -u 4 | set -o pipefail 5 | 6 | function on_error { 7 | echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" 8 | } 9 | trap 'on_error $LINENO' ERR 10 | 11 | if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then 12 | # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy 13 | # frameworks to, so exit 0 (signalling the script phase was successful). 14 | exit 0 15 | fi 16 | 17 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 18 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 19 | 20 | COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" 21 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 22 | 23 | # Used as a return value for each invocation of `strip_invalid_archs` function. 24 | STRIP_BINARY_RETVAL=0 25 | 26 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 27 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 28 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 29 | 30 | # Copies and strips a vendored framework 31 | install_framework() 32 | { 33 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 34 | local source="${BUILT_PRODUCTS_DIR}/$1" 35 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 36 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 37 | elif [ -r "$1" ]; then 38 | local source="$1" 39 | fi 40 | 41 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 42 | 43 | if [ -L "${source}" ]; then 44 | echo "Symlinked..." 45 | source="$(readlink "${source}")" 46 | fi 47 | 48 | # Use filter instead of exclude so missing patterns don't throw errors. 49 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 50 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 51 | 52 | local basename 53 | basename="$(basename -s .framework "$1")" 54 | binary="${destination}/${basename}.framework/${basename}" 55 | 56 | if ! [ -r "$binary" ]; then 57 | binary="${destination}/${basename}" 58 | elif [ -L "${binary}" ]; then 59 | echo "Destination binary is symlinked..." 60 | dirname="$(dirname "${binary}")" 61 | binary="${dirname}/$(readlink "${binary}")" 62 | fi 63 | 64 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 65 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 66 | strip_invalid_archs "$binary" 67 | fi 68 | 69 | # Resign the code if required by the build settings to avoid unstable apps 70 | code_sign_if_enabled "${destination}/$(basename "$1")" 71 | 72 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 73 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 74 | local swift_runtime_libs 75 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) 76 | for lib in $swift_runtime_libs; do 77 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 78 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 79 | code_sign_if_enabled "${destination}/${lib}" 80 | done 81 | fi 82 | } 83 | 84 | # Copies and strips a vendored dSYM 85 | install_dsym() { 86 | local source="$1" 87 | if [ -r "$source" ]; then 88 | # Copy the dSYM into a the targets temp dir. 89 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" 90 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" 91 | 92 | local basename 93 | basename="$(basename -s .framework.dSYM "$source")" 94 | binary="${DERIVED_FILES_DIR}/${basename}.framework.dSYM/Contents/Resources/DWARF/${basename}" 95 | 96 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 97 | if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then 98 | strip_invalid_archs "$binary" 99 | fi 100 | 101 | if [[ $STRIP_BINARY_RETVAL == 1 ]]; then 102 | # Move the stripped file into its final destination. 103 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" 104 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.framework.dSYM" "${DWARF_DSYM_FOLDER_PATH}" 105 | else 106 | # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. 107 | touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.framework.dSYM" 108 | fi 109 | fi 110 | } 111 | 112 | # Copies the bcsymbolmap files of a vendored framework 113 | install_bcsymbolmap() { 114 | local bcsymbolmap_path="$1" 115 | local destination="${BUILT_PRODUCTS_DIR}" 116 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" 117 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" 118 | } 119 | 120 | # Signs a framework with the provided identity 121 | code_sign_if_enabled() { 122 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 123 | # Use the current code_sign_identity 124 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 125 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" 126 | 127 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 128 | code_sign_cmd="$code_sign_cmd &" 129 | fi 130 | echo "$code_sign_cmd" 131 | eval "$code_sign_cmd" 132 | fi 133 | } 134 | 135 | # Strip invalid architectures 136 | strip_invalid_archs() { 137 | binary="$1" 138 | # Get architectures for current target binary 139 | binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" 140 | # Intersect them with the architectures we are building for 141 | intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" 142 | # If there are no archs supported by this binary then warn the user 143 | if [[ -z "$intersected_archs" ]]; then 144 | echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." 145 | STRIP_BINARY_RETVAL=0 146 | return 147 | fi 148 | stripped="" 149 | for arch in $binary_archs; do 150 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then 151 | # Strip non-valid architectures in-place 152 | lipo -remove "$arch" -output "$binary" "$binary" 153 | stripped="$stripped $arch" 154 | fi 155 | done 156 | if [[ "$stripped" ]]; then 157 | echo "Stripped $binary of architectures:$stripped" 158 | fi 159 | STRIP_BINARY_RETVAL=1 160 | } 161 | 162 | 163 | if [[ "$CONFIGURATION" == "Debug" ]]; then 164 | install_framework "${BUILT_PRODUCTS_DIR}/PowerTools/PowerTools.framework" 165 | install_framework "${BUILT_PRODUCTS_DIR}/SafariLayout/SafariLayout.framework" 166 | install_framework "${BUILT_PRODUCTS_DIR}/StoriesLayout/StoriesLayout.framework" 167 | fi 168 | if [[ "$CONFIGURATION" == "Release" ]]; then 169 | install_framework "${BUILT_PRODUCTS_DIR}/PowerTools/PowerTools.framework" 170 | install_framework "${BUILT_PRODUCTS_DIR}/SafariLayout/SafariLayout.framework" 171 | install_framework "${BUILT_PRODUCTS_DIR}/StoriesLayout/StoriesLayout.framework" 172 | fi 173 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 174 | wait 175 | fi 176 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-StoriesLayout_Example/Pods-StoriesLayout_Example-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_StoriesLayout_ExampleVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_StoriesLayout_ExampleVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-StoriesLayout_Example/Pods-StoriesLayout_Example.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/PowerTools" "${PODS_CONFIGURATION_BUILD_DIR}/SafariLayout" "${PODS_CONFIGURATION_BUILD_DIR}/StoriesLayout" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/PowerTools/PowerTools.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SafariLayout/SafariLayout.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/StoriesLayout/StoriesLayout.framework/Headers" 5 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 6 | OTHER_LDFLAGS = $(inherited) -framework "CoreGraphics" -framework "PowerTools" -framework "SafariLayout" -framework "StoriesLayout" -framework "UIKit" 7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-StoriesLayout_Example/Pods-StoriesLayout_Example.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_StoriesLayout_Example { 2 | umbrella header "Pods-StoriesLayout_Example-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-StoriesLayout_Example/Pods-StoriesLayout_Example.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/PowerTools" "${PODS_CONFIGURATION_BUILD_DIR}/SafariLayout" "${PODS_CONFIGURATION_BUILD_DIR}/StoriesLayout" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/PowerTools/PowerTools.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SafariLayout/SafariLayout.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/StoriesLayout/StoriesLayout.framework/Headers" 5 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 6 | OTHER_LDFLAGS = $(inherited) -framework "CoreGraphics" -framework "PowerTools" -framework "SafariLayout" -framework "StoriesLayout" -framework "UIKit" 7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-StoriesLayout_Tests/Pods-StoriesLayout_Tests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-StoriesLayout_Tests/Pods-StoriesLayout_Tests-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## Nimble 5 | 6 | Apache License 7 | Version 2.0, January 2004 8 | http://www.apache.org/licenses/ 9 | 10 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 11 | 12 | 1. Definitions. 13 | 14 | "License" shall mean the terms and conditions for use, reproduction, 15 | and distribution as defined by Sections 1 through 9 of this document. 16 | 17 | "Licensor" shall mean the copyright owner or entity authorized by 18 | the copyright owner that is granting the License. 19 | 20 | "Legal Entity" shall mean the union of the acting entity and all 21 | other entities that control, are controlled by, or are under common 22 | control with that entity. For the purposes of this definition, 23 | "control" means (i) the power, direct or indirect, to cause the 24 | direction or management of such entity, whether by contract or 25 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 26 | outstanding shares, or (iii) beneficial ownership of such entity. 27 | 28 | "You" (or "Your") shall mean an individual or Legal Entity 29 | exercising permissions granted by this License. 30 | 31 | "Source" form shall mean the preferred form for making modifications, 32 | including but not limited to software source code, documentation 33 | source, and configuration files. 34 | 35 | "Object" form shall mean any form resulting from mechanical 36 | transformation or translation of a Source form, including but 37 | not limited to compiled object code, generated documentation, 38 | and conversions to other media types. 39 | 40 | "Work" shall mean the work of authorship, whether in Source or 41 | Object form, made available under the License, as indicated by a 42 | copyright notice that is included in or attached to the work 43 | (an example is provided in the Appendix below). 44 | 45 | "Derivative Works" shall mean any work, whether in Source or Object 46 | form, that is based on (or derived from) the Work and for which the 47 | editorial revisions, annotations, elaborations, or other modifications 48 | represent, as a whole, an original work of authorship. For the purposes 49 | of this License, Derivative Works shall not include works that remain 50 | separable from, or merely link (or bind by name) to the interfaces of, 51 | the Work and Derivative Works thereof. 52 | 53 | "Contribution" shall mean any work of authorship, including 54 | the original version of the Work and any modifications or additions 55 | to that Work or Derivative Works thereof, that is intentionally 56 | submitted to Licensor for inclusion in the Work by the copyright owner 57 | or by an individual or Legal Entity authorized to submit on behalf of 58 | the copyright owner. For the purposes of this definition, "submitted" 59 | means any form of electronic, verbal, or written communication sent 60 | to the Licensor or its representatives, including but not limited to 61 | communication on electronic mailing lists, source code control systems, 62 | and issue tracking systems that are managed by, or on behalf of, the 63 | Licensor for the purpose of discussing and improving the Work, but 64 | excluding communication that is conspicuously marked or otherwise 65 | designated in writing by the copyright owner as "Not a Contribution." 66 | 67 | "Contributor" shall mean Licensor and any individual or Legal Entity 68 | on behalf of whom a Contribution has been received by Licensor and 69 | subsequently incorporated within the Work. 70 | 71 | 2. Grant of Copyright License. Subject to the terms and conditions of 72 | this License, each Contributor hereby grants to You a perpetual, 73 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 74 | copyright license to reproduce, prepare Derivative Works of, 75 | publicly display, publicly perform, sublicense, and distribute the 76 | Work and such Derivative Works in Source or Object form. 77 | 78 | 3. Grant of Patent License. Subject to the terms and conditions of 79 | this License, each Contributor hereby grants to You a perpetual, 80 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 81 | (except as stated in this section) patent license to make, have made, 82 | use, offer to sell, sell, import, and otherwise transfer the Work, 83 | where such license applies only to those patent claims licensable 84 | by such Contributor that are necessarily infringed by their 85 | Contribution(s) alone or by combination of their Contribution(s) 86 | with the Work to which such Contribution(s) was submitted. If You 87 | institute patent litigation against any entity (including a 88 | cross-claim or counterclaim in a lawsuit) alleging that the Work 89 | or a Contribution incorporated within the Work constitutes direct 90 | or contributory patent infringement, then any patent licenses 91 | granted to You under this License for that Work shall terminate 92 | as of the date such litigation is filed. 93 | 94 | 4. Redistribution. You may reproduce and distribute copies of the 95 | Work or Derivative Works thereof in any medium, with or without 96 | modifications, and in Source or Object form, provided that You 97 | meet the following conditions: 98 | 99 | (a) You must give any other recipients of the Work or 100 | Derivative Works a copy of this License; and 101 | 102 | (b) You must cause any modified files to carry prominent notices 103 | stating that You changed the files; and 104 | 105 | (c) You must retain, in the Source form of any Derivative Works 106 | that You distribute, all copyright, patent, trademark, and 107 | attribution notices from the Source form of the Work, 108 | excluding those notices that do not pertain to any part of 109 | the Derivative Works; and 110 | 111 | (d) If the Work includes a "NOTICE" text file as part of its 112 | distribution, then any Derivative Works that You distribute must 113 | include a readable copy of the attribution notices contained 114 | within such NOTICE file, excluding those notices that do not 115 | pertain to any part of the Derivative Works, in at least one 116 | of the following places: within a NOTICE text file distributed 117 | as part of the Derivative Works; within the Source form or 118 | documentation, if provided along with the Derivative Works; or, 119 | within a display generated by the Derivative Works, if and 120 | wherever such third-party notices normally appear. The contents 121 | of the NOTICE file are for informational purposes only and 122 | do not modify the License. You may add Your own attribution 123 | notices within Derivative Works that You distribute, alongside 124 | or as an addendum to the NOTICE text from the Work, provided 125 | that such additional attribution notices cannot be construed 126 | as modifying the License. 127 | 128 | You may add Your own copyright statement to Your modifications and 129 | may provide additional or different license terms and conditions 130 | for use, reproduction, or distribution of Your modifications, or 131 | for any such Derivative Works as a whole, provided Your use, 132 | reproduction, and distribution of the Work otherwise complies with 133 | the conditions stated in this License. 134 | 135 | 5. Submission of Contributions. Unless You explicitly state otherwise, 136 | any Contribution intentionally submitted for inclusion in the Work 137 | by You to the Licensor shall be under the terms and conditions of 138 | this License, without any additional terms or conditions. 139 | Notwithstanding the above, nothing herein shall supersede or modify 140 | the terms of any separate license agreement you may have executed 141 | with Licensor regarding such Contributions. 142 | 143 | 6. Trademarks. This License does not grant permission to use the trade 144 | names, trademarks, service marks, or product names of the Licensor, 145 | except as required for reasonable and customary use in describing the 146 | origin of the Work and reproducing the content of the NOTICE file. 147 | 148 | 7. Disclaimer of Warranty. Unless required by applicable law or 149 | agreed to in writing, Licensor provides the Work (and each 150 | Contributor provides its Contributions) on an "AS IS" BASIS, 151 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 152 | implied, including, without limitation, any warranties or conditions 153 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 154 | PARTICULAR PURPOSE. You are solely responsible for determining the 155 | appropriateness of using or redistributing the Work and assume any 156 | risks associated with Your exercise of permissions under this License. 157 | 158 | 8. Limitation of Liability. In no event and under no legal theory, 159 | whether in tort (including negligence), contract, or otherwise, 160 | unless required by applicable law (such as deliberate and grossly 161 | negligent acts) or agreed to in writing, shall any Contributor be 162 | liable to You for damages, including any direct, indirect, special, 163 | incidental, or consequential damages of any character arising as a 164 | result of this License or out of the use or inability to use the 165 | Work (including but not limited to damages for loss of goodwill, 166 | work stoppage, computer failure or malfunction, or any and all 167 | other commercial damages or losses), even if such Contributor 168 | has been advised of the possibility of such damages. 169 | 170 | 9. Accepting Warranty or Additional Liability. While redistributing 171 | the Work or Derivative Works thereof, You may choose to offer, 172 | and charge a fee for, acceptance of support, warranty, indemnity, 173 | or other liability obligations and/or rights consistent with this 174 | License. However, in accepting such obligations, You may act only 175 | on Your own behalf and on Your sole responsibility, not on behalf 176 | of any other Contributor, and only if You agree to indemnify, 177 | defend, and hold each Contributor harmless for any liability 178 | incurred by, or claims asserted against, such Contributor by reason 179 | of your accepting any such warranty or additional liability. 180 | 181 | END OF TERMS AND CONDITIONS 182 | 183 | APPENDIX: How to apply the Apache License to your work. 184 | 185 | To apply the Apache License to your work, attach the following 186 | boilerplate notice, with the fields enclosed by brackets "{}" 187 | replaced with your own identifying information. (Don't include 188 | the brackets!) The text should be enclosed in the appropriate 189 | comment syntax for the file format. We also recommend that a 190 | file or class name and description of purpose be included on the 191 | same "printed page" as the copyright notice for easier 192 | identification within third-party archives. 193 | 194 | Copyright 2016 Quick Team 195 | 196 | Licensed under the Apache License, Version 2.0 (the "License"); 197 | you may not use this file except in compliance with the License. 198 | You may obtain a copy of the License at 199 | 200 | http://www.apache.org/licenses/LICENSE-2.0 201 | 202 | Unless required by applicable law or agreed to in writing, software 203 | distributed under the License is distributed on an "AS IS" BASIS, 204 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 205 | See the License for the specific language governing permissions and 206 | limitations under the License. 207 | 208 | 209 | ## Quick 210 | 211 | Apache License 212 | Version 2.0, January 2004 213 | http://www.apache.org/licenses/ 214 | 215 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 216 | 217 | 1. Definitions. 218 | 219 | "License" shall mean the terms and conditions for use, reproduction, 220 | and distribution as defined by Sections 1 through 9 of this document. 221 | 222 | "Licensor" shall mean the copyright owner or entity authorized by 223 | the copyright owner that is granting the License. 224 | 225 | "Legal Entity" shall mean the union of the acting entity and all 226 | other entities that control, are controlled by, or are under common 227 | control with that entity. For the purposes of this definition, 228 | "control" means (i) the power, direct or indirect, to cause the 229 | direction or management of such entity, whether by contract or 230 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 231 | outstanding shares, or (iii) beneficial ownership of such entity. 232 | 233 | "You" (or "Your") shall mean an individual or Legal Entity 234 | exercising permissions granted by this License. 235 | 236 | "Source" form shall mean the preferred form for making modifications, 237 | including but not limited to software source code, documentation 238 | source, and configuration files. 239 | 240 | "Object" form shall mean any form resulting from mechanical 241 | transformation or translation of a Source form, including but 242 | not limited to compiled object code, generated documentation, 243 | and conversions to other media types. 244 | 245 | "Work" shall mean the work of authorship, whether in Source or 246 | Object form, made available under the License, as indicated by a 247 | copyright notice that is included in or attached to the work 248 | (an example is provided in the Appendix below). 249 | 250 | "Derivative Works" shall mean any work, whether in Source or Object 251 | form, that is based on (or derived from) the Work and for which the 252 | editorial revisions, annotations, elaborations, or other modifications 253 | represent, as a whole, an original work of authorship. For the purposes 254 | of this License, Derivative Works shall not include works that remain 255 | separable from, or merely link (or bind by name) to the interfaces of, 256 | the Work and Derivative Works thereof. 257 | 258 | "Contribution" shall mean any work of authorship, including 259 | the original version of the Work and any modifications or additions 260 | to that Work or Derivative Works thereof, that is intentionally 261 | submitted to Licensor for inclusion in the Work by the copyright owner 262 | or by an individual or Legal Entity authorized to submit on behalf of 263 | the copyright owner. For the purposes of this definition, "submitted" 264 | means any form of electronic, verbal, or written communication sent 265 | to the Licensor or its representatives, including but not limited to 266 | communication on electronic mailing lists, source code control systems, 267 | and issue tracking systems that are managed by, or on behalf of, the 268 | Licensor for the purpose of discussing and improving the Work, but 269 | excluding communication that is conspicuously marked or otherwise 270 | designated in writing by the copyright owner as "Not a Contribution." 271 | 272 | "Contributor" shall mean Licensor and any individual or Legal Entity 273 | on behalf of whom a Contribution has been received by Licensor and 274 | subsequently incorporated within the Work. 275 | 276 | 2. Grant of Copyright License. Subject to the terms and conditions of 277 | this License, each Contributor hereby grants to You a perpetual, 278 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 279 | copyright license to reproduce, prepare Derivative Works of, 280 | publicly display, publicly perform, sublicense, and distribute the 281 | Work and such Derivative Works in Source or Object form. 282 | 283 | 3. Grant of Patent License. Subject to the terms and conditions of 284 | this License, each Contributor hereby grants to You a perpetual, 285 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 286 | (except as stated in this section) patent license to make, have made, 287 | use, offer to sell, sell, import, and otherwise transfer the Work, 288 | where such license applies only to those patent claims licensable 289 | by such Contributor that are necessarily infringed by their 290 | Contribution(s) alone or by combination of their Contribution(s) 291 | with the Work to which such Contribution(s) was submitted. If You 292 | institute patent litigation against any entity (including a 293 | cross-claim or counterclaim in a lawsuit) alleging that the Work 294 | or a Contribution incorporated within the Work constitutes direct 295 | or contributory patent infringement, then any patent licenses 296 | granted to You under this License for that Work shall terminate 297 | as of the date such litigation is filed. 298 | 299 | 4. Redistribution. You may reproduce and distribute copies of the 300 | Work or Derivative Works thereof in any medium, with or without 301 | modifications, and in Source or Object form, provided that You 302 | meet the following conditions: 303 | 304 | (a) You must give any other recipients of the Work or 305 | Derivative Works a copy of this License; and 306 | 307 | (b) You must cause any modified files to carry prominent notices 308 | stating that You changed the files; and 309 | 310 | (c) You must retain, in the Source form of any Derivative Works 311 | that You distribute, all copyright, patent, trademark, and 312 | attribution notices from the Source form of the Work, 313 | excluding those notices that do not pertain to any part of 314 | the Derivative Works; and 315 | 316 | (d) If the Work includes a "NOTICE" text file as part of its 317 | distribution, then any Derivative Works that You distribute must 318 | include a readable copy of the attribution notices contained 319 | within such NOTICE file, excluding those notices that do not 320 | pertain to any part of the Derivative Works, in at least one 321 | of the following places: within a NOTICE text file distributed 322 | as part of the Derivative Works; within the Source form or 323 | documentation, if provided along with the Derivative Works; or, 324 | within a display generated by the Derivative Works, if and 325 | wherever such third-party notices normally appear. The contents 326 | of the NOTICE file are for informational purposes only and 327 | do not modify the License. You may add Your own attribution 328 | notices within Derivative Works that You distribute, alongside 329 | or as an addendum to the NOTICE text from the Work, provided 330 | that such additional attribution notices cannot be construed 331 | as modifying the License. 332 | 333 | You may add Your own copyright statement to Your modifications and 334 | may provide additional or different license terms and conditions 335 | for use, reproduction, or distribution of Your modifications, or 336 | for any such Derivative Works as a whole, provided Your use, 337 | reproduction, and distribution of the Work otherwise complies with 338 | the conditions stated in this License. 339 | 340 | 5. Submission of Contributions. Unless You explicitly state otherwise, 341 | any Contribution intentionally submitted for inclusion in the Work 342 | by You to the Licensor shall be under the terms and conditions of 343 | this License, without any additional terms or conditions. 344 | Notwithstanding the above, nothing herein shall supersede or modify 345 | the terms of any separate license agreement you may have executed 346 | with Licensor regarding such Contributions. 347 | 348 | 6. Trademarks. This License does not grant permission to use the trade 349 | names, trademarks, service marks, or product names of the Licensor, 350 | except as required for reasonable and customary use in describing the 351 | origin of the Work and reproducing the content of the NOTICE file. 352 | 353 | 7. Disclaimer of Warranty. Unless required by applicable law or 354 | agreed to in writing, Licensor provides the Work (and each 355 | Contributor provides its Contributions) on an "AS IS" BASIS, 356 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 357 | implied, including, without limitation, any warranties or conditions 358 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 359 | PARTICULAR PURPOSE. You are solely responsible for determining the 360 | appropriateness of using or redistributing the Work and assume any 361 | risks associated with Your exercise of permissions under this License. 362 | 363 | 8. Limitation of Liability. In no event and under no legal theory, 364 | whether in tort (including negligence), contract, or otherwise, 365 | unless required by applicable law (such as deliberate and grossly 366 | negligent acts) or agreed to in writing, shall any Contributor be 367 | liable to You for damages, including any direct, indirect, special, 368 | incidental, or consequential damages of any character arising as a 369 | result of this License or out of the use or inability to use the 370 | Work (including but not limited to damages for loss of goodwill, 371 | work stoppage, computer failure or malfunction, or any and all 372 | other commercial damages or losses), even if such Contributor 373 | has been advised of the possibility of such damages. 374 | 375 | 9. Accepting Warranty or Additional Liability. While redistributing 376 | the Work or Derivative Works thereof, You may choose to offer, 377 | and charge a fee for, acceptance of support, warranty, indemnity, 378 | or other liability obligations and/or rights consistent with this 379 | License. However, in accepting such obligations, You may act only 380 | on Your own behalf and on Your sole responsibility, not on behalf 381 | of any other Contributor, and only if You agree to indemnify, 382 | defend, and hold each Contributor harmless for any liability 383 | incurred by, or claims asserted against, such Contributor by reason 384 | of your accepting any such warranty or additional liability. 385 | 386 | END OF TERMS AND CONDITIONS 387 | 388 | APPENDIX: How to apply the Apache License to your work. 389 | 390 | To apply the Apache License to your work, attach the following 391 | boilerplate notice, with the fields enclosed by brackets "{}" 392 | replaced with your own identifying information. (Don't include 393 | the brackets!) The text should be enclosed in the appropriate 394 | comment syntax for the file format. We also recommend that a 395 | file or class name and description of purpose be included on the 396 | same "printed page" as the copyright notice for easier 397 | identification within third-party archives. 398 | 399 | Copyright 2014, Quick Team 400 | 401 | Licensed under the Apache License, Version 2.0 (the "License"); 402 | you may not use this file except in compliance with the License. 403 | You may obtain a copy of the License at 404 | 405 | http://www.apache.org/licenses/LICENSE-2.0 406 | 407 | Unless required by applicable law or agreed to in writing, software 408 | distributed under the License is distributed on an "AS IS" BASIS, 409 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 410 | See the License for the specific language governing permissions and 411 | limitations under the License. 412 | 413 | Generated by CocoaPods - https://cocoapods.org 414 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-StoriesLayout_Tests/Pods-StoriesLayout_Tests-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Apache License 18 | Version 2.0, January 2004 19 | http://www.apache.org/licenses/ 20 | 21 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 22 | 23 | 1. Definitions. 24 | 25 | "License" shall mean the terms and conditions for use, reproduction, 26 | and distribution as defined by Sections 1 through 9 of this document. 27 | 28 | "Licensor" shall mean the copyright owner or entity authorized by 29 | the copyright owner that is granting the License. 30 | 31 | "Legal Entity" shall mean the union of the acting entity and all 32 | other entities that control, are controlled by, or are under common 33 | control with that entity. For the purposes of this definition, 34 | "control" means (i) the power, direct or indirect, to cause the 35 | direction or management of such entity, whether by contract or 36 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 37 | outstanding shares, or (iii) beneficial ownership of such entity. 38 | 39 | "You" (or "Your") shall mean an individual or Legal Entity 40 | exercising permissions granted by this License. 41 | 42 | "Source" form shall mean the preferred form for making modifications, 43 | including but not limited to software source code, documentation 44 | source, and configuration files. 45 | 46 | "Object" form shall mean any form resulting from mechanical 47 | transformation or translation of a Source form, including but 48 | not limited to compiled object code, generated documentation, 49 | and conversions to other media types. 50 | 51 | "Work" shall mean the work of authorship, whether in Source or 52 | Object form, made available under the License, as indicated by a 53 | copyright notice that is included in or attached to the work 54 | (an example is provided in the Appendix below). 55 | 56 | "Derivative Works" shall mean any work, whether in Source or Object 57 | form, that is based on (or derived from) the Work and for which the 58 | editorial revisions, annotations, elaborations, or other modifications 59 | represent, as a whole, an original work of authorship. For the purposes 60 | of this License, Derivative Works shall not include works that remain 61 | separable from, or merely link (or bind by name) to the interfaces of, 62 | the Work and Derivative Works thereof. 63 | 64 | "Contribution" shall mean any work of authorship, including 65 | the original version of the Work and any modifications or additions 66 | to that Work or Derivative Works thereof, that is intentionally 67 | submitted to Licensor for inclusion in the Work by the copyright owner 68 | or by an individual or Legal Entity authorized to submit on behalf of 69 | the copyright owner. For the purposes of this definition, "submitted" 70 | means any form of electronic, verbal, or written communication sent 71 | to the Licensor or its representatives, including but not limited to 72 | communication on electronic mailing lists, source code control systems, 73 | and issue tracking systems that are managed by, or on behalf of, the 74 | Licensor for the purpose of discussing and improving the Work, but 75 | excluding communication that is conspicuously marked or otherwise 76 | designated in writing by the copyright owner as "Not a Contribution." 77 | 78 | "Contributor" shall mean Licensor and any individual or Legal Entity 79 | on behalf of whom a Contribution has been received by Licensor and 80 | subsequently incorporated within the Work. 81 | 82 | 2. Grant of Copyright License. Subject to the terms and conditions of 83 | this License, each Contributor hereby grants to You a perpetual, 84 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 85 | copyright license to reproduce, prepare Derivative Works of, 86 | publicly display, publicly perform, sublicense, and distribute the 87 | Work and such Derivative Works in Source or Object form. 88 | 89 | 3. Grant of Patent License. Subject to the terms and conditions of 90 | this License, each Contributor hereby grants to You a perpetual, 91 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 92 | (except as stated in this section) patent license to make, have made, 93 | use, offer to sell, sell, import, and otherwise transfer the Work, 94 | where such license applies only to those patent claims licensable 95 | by such Contributor that are necessarily infringed by their 96 | Contribution(s) alone or by combination of their Contribution(s) 97 | with the Work to which such Contribution(s) was submitted. If You 98 | institute patent litigation against any entity (including a 99 | cross-claim or counterclaim in a lawsuit) alleging that the Work 100 | or a Contribution incorporated within the Work constitutes direct 101 | or contributory patent infringement, then any patent licenses 102 | granted to You under this License for that Work shall terminate 103 | as of the date such litigation is filed. 104 | 105 | 4. Redistribution. You may reproduce and distribute copies of the 106 | Work or Derivative Works thereof in any medium, with or without 107 | modifications, and in Source or Object form, provided that You 108 | meet the following conditions: 109 | 110 | (a) You must give any other recipients of the Work or 111 | Derivative Works a copy of this License; and 112 | 113 | (b) You must cause any modified files to carry prominent notices 114 | stating that You changed the files; and 115 | 116 | (c) You must retain, in the Source form of any Derivative Works 117 | that You distribute, all copyright, patent, trademark, and 118 | attribution notices from the Source form of the Work, 119 | excluding those notices that do not pertain to any part of 120 | the Derivative Works; and 121 | 122 | (d) If the Work includes a "NOTICE" text file as part of its 123 | distribution, then any Derivative Works that You distribute must 124 | include a readable copy of the attribution notices contained 125 | within such NOTICE file, excluding those notices that do not 126 | pertain to any part of the Derivative Works, in at least one 127 | of the following places: within a NOTICE text file distributed 128 | as part of the Derivative Works; within the Source form or 129 | documentation, if provided along with the Derivative Works; or, 130 | within a display generated by the Derivative Works, if and 131 | wherever such third-party notices normally appear. The contents 132 | of the NOTICE file are for informational purposes only and 133 | do not modify the License. You may add Your own attribution 134 | notices within Derivative Works that You distribute, alongside 135 | or as an addendum to the NOTICE text from the Work, provided 136 | that such additional attribution notices cannot be construed 137 | as modifying the License. 138 | 139 | You may add Your own copyright statement to Your modifications and 140 | may provide additional or different license terms and conditions 141 | for use, reproduction, or distribution of Your modifications, or 142 | for any such Derivative Works as a whole, provided Your use, 143 | reproduction, and distribution of the Work otherwise complies with 144 | the conditions stated in this License. 145 | 146 | 5. Submission of Contributions. Unless You explicitly state otherwise, 147 | any Contribution intentionally submitted for inclusion in the Work 148 | by You to the Licensor shall be under the terms and conditions of 149 | this License, without any additional terms or conditions. 150 | Notwithstanding the above, nothing herein shall supersede or modify 151 | the terms of any separate license agreement you may have executed 152 | with Licensor regarding such Contributions. 153 | 154 | 6. Trademarks. This License does not grant permission to use the trade 155 | names, trademarks, service marks, or product names of the Licensor, 156 | except as required for reasonable and customary use in describing the 157 | origin of the Work and reproducing the content of the NOTICE file. 158 | 159 | 7. Disclaimer of Warranty. Unless required by applicable law or 160 | agreed to in writing, Licensor provides the Work (and each 161 | Contributor provides its Contributions) on an "AS IS" BASIS, 162 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 163 | implied, including, without limitation, any warranties or conditions 164 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 165 | PARTICULAR PURPOSE. You are solely responsible for determining the 166 | appropriateness of using or redistributing the Work and assume any 167 | risks associated with Your exercise of permissions under this License. 168 | 169 | 8. Limitation of Liability. In no event and under no legal theory, 170 | whether in tort (including negligence), contract, or otherwise, 171 | unless required by applicable law (such as deliberate and grossly 172 | negligent acts) or agreed to in writing, shall any Contributor be 173 | liable to You for damages, including any direct, indirect, special, 174 | incidental, or consequential damages of any character arising as a 175 | result of this License or out of the use or inability to use the 176 | Work (including but not limited to damages for loss of goodwill, 177 | work stoppage, computer failure or malfunction, or any and all 178 | other commercial damages or losses), even if such Contributor 179 | has been advised of the possibility of such damages. 180 | 181 | 9. Accepting Warranty or Additional Liability. While redistributing 182 | the Work or Derivative Works thereof, You may choose to offer, 183 | and charge a fee for, acceptance of support, warranty, indemnity, 184 | or other liability obligations and/or rights consistent with this 185 | License. However, in accepting such obligations, You may act only 186 | on Your own behalf and on Your sole responsibility, not on behalf 187 | of any other Contributor, and only if You agree to indemnify, 188 | defend, and hold each Contributor harmless for any liability 189 | incurred by, or claims asserted against, such Contributor by reason 190 | of your accepting any such warranty or additional liability. 191 | 192 | END OF TERMS AND CONDITIONS 193 | 194 | APPENDIX: How to apply the Apache License to your work. 195 | 196 | To apply the Apache License to your work, attach the following 197 | boilerplate notice, with the fields enclosed by brackets "{}" 198 | replaced with your own identifying information. (Don't include 199 | the brackets!) The text should be enclosed in the appropriate 200 | comment syntax for the file format. We also recommend that a 201 | file or class name and description of purpose be included on the 202 | same "printed page" as the copyright notice for easier 203 | identification within third-party archives. 204 | 205 | Copyright 2016 Quick Team 206 | 207 | Licensed under the Apache License, Version 2.0 (the "License"); 208 | you may not use this file except in compliance with the License. 209 | You may obtain a copy of the License at 210 | 211 | http://www.apache.org/licenses/LICENSE-2.0 212 | 213 | Unless required by applicable law or agreed to in writing, software 214 | distributed under the License is distributed on an "AS IS" BASIS, 215 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 216 | See the License for the specific language governing permissions and 217 | limitations under the License. 218 | 219 | License 220 | Apache 2.0 221 | Title 222 | Nimble 223 | Type 224 | PSGroupSpecifier 225 | 226 | 227 | FooterText 228 | Apache License 229 | Version 2.0, January 2004 230 | http://www.apache.org/licenses/ 231 | 232 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 233 | 234 | 1. Definitions. 235 | 236 | "License" shall mean the terms and conditions for use, reproduction, 237 | and distribution as defined by Sections 1 through 9 of this document. 238 | 239 | "Licensor" shall mean the copyright owner or entity authorized by 240 | the copyright owner that is granting the License. 241 | 242 | "Legal Entity" shall mean the union of the acting entity and all 243 | other entities that control, are controlled by, or are under common 244 | control with that entity. For the purposes of this definition, 245 | "control" means (i) the power, direct or indirect, to cause the 246 | direction or management of such entity, whether by contract or 247 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 248 | outstanding shares, or (iii) beneficial ownership of such entity. 249 | 250 | "You" (or "Your") shall mean an individual or Legal Entity 251 | exercising permissions granted by this License. 252 | 253 | "Source" form shall mean the preferred form for making modifications, 254 | including but not limited to software source code, documentation 255 | source, and configuration files. 256 | 257 | "Object" form shall mean any form resulting from mechanical 258 | transformation or translation of a Source form, including but 259 | not limited to compiled object code, generated documentation, 260 | and conversions to other media types. 261 | 262 | "Work" shall mean the work of authorship, whether in Source or 263 | Object form, made available under the License, as indicated by a 264 | copyright notice that is included in or attached to the work 265 | (an example is provided in the Appendix below). 266 | 267 | "Derivative Works" shall mean any work, whether in Source or Object 268 | form, that is based on (or derived from) the Work and for which the 269 | editorial revisions, annotations, elaborations, or other modifications 270 | represent, as a whole, an original work of authorship. For the purposes 271 | of this License, Derivative Works shall not include works that remain 272 | separable from, or merely link (or bind by name) to the interfaces of, 273 | the Work and Derivative Works thereof. 274 | 275 | "Contribution" shall mean any work of authorship, including 276 | the original version of the Work and any modifications or additions 277 | to that Work or Derivative Works thereof, that is intentionally 278 | submitted to Licensor for inclusion in the Work by the copyright owner 279 | or by an individual or Legal Entity authorized to submit on behalf of 280 | the copyright owner. For the purposes of this definition, "submitted" 281 | means any form of electronic, verbal, or written communication sent 282 | to the Licensor or its representatives, including but not limited to 283 | communication on electronic mailing lists, source code control systems, 284 | and issue tracking systems that are managed by, or on behalf of, the 285 | Licensor for the purpose of discussing and improving the Work, but 286 | excluding communication that is conspicuously marked or otherwise 287 | designated in writing by the copyright owner as "Not a Contribution." 288 | 289 | "Contributor" shall mean Licensor and any individual or Legal Entity 290 | on behalf of whom a Contribution has been received by Licensor and 291 | subsequently incorporated within the Work. 292 | 293 | 2. Grant of Copyright License. Subject to the terms and conditions of 294 | this License, each Contributor hereby grants to You a perpetual, 295 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 296 | copyright license to reproduce, prepare Derivative Works of, 297 | publicly display, publicly perform, sublicense, and distribute the 298 | Work and such Derivative Works in Source or Object form. 299 | 300 | 3. Grant of Patent License. Subject to the terms and conditions of 301 | this License, each Contributor hereby grants to You a perpetual, 302 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 303 | (except as stated in this section) patent license to make, have made, 304 | use, offer to sell, sell, import, and otherwise transfer the Work, 305 | where such license applies only to those patent claims licensable 306 | by such Contributor that are necessarily infringed by their 307 | Contribution(s) alone or by combination of their Contribution(s) 308 | with the Work to which such Contribution(s) was submitted. If You 309 | institute patent litigation against any entity (including a 310 | cross-claim or counterclaim in a lawsuit) alleging that the Work 311 | or a Contribution incorporated within the Work constitutes direct 312 | or contributory patent infringement, then any patent licenses 313 | granted to You under this License for that Work shall terminate 314 | as of the date such litigation is filed. 315 | 316 | 4. Redistribution. You may reproduce and distribute copies of the 317 | Work or Derivative Works thereof in any medium, with or without 318 | modifications, and in Source or Object form, provided that You 319 | meet the following conditions: 320 | 321 | (a) You must give any other recipients of the Work or 322 | Derivative Works a copy of this License; and 323 | 324 | (b) You must cause any modified files to carry prominent notices 325 | stating that You changed the files; and 326 | 327 | (c) You must retain, in the Source form of any Derivative Works 328 | that You distribute, all copyright, patent, trademark, and 329 | attribution notices from the Source form of the Work, 330 | excluding those notices that do not pertain to any part of 331 | the Derivative Works; and 332 | 333 | (d) If the Work includes a "NOTICE" text file as part of its 334 | distribution, then any Derivative Works that You distribute must 335 | include a readable copy of the attribution notices contained 336 | within such NOTICE file, excluding those notices that do not 337 | pertain to any part of the Derivative Works, in at least one 338 | of the following places: within a NOTICE text file distributed 339 | as part of the Derivative Works; within the Source form or 340 | documentation, if provided along with the Derivative Works; or, 341 | within a display generated by the Derivative Works, if and 342 | wherever such third-party notices normally appear. The contents 343 | of the NOTICE file are for informational purposes only and 344 | do not modify the License. You may add Your own attribution 345 | notices within Derivative Works that You distribute, alongside 346 | or as an addendum to the NOTICE text from the Work, provided 347 | that such additional attribution notices cannot be construed 348 | as modifying the License. 349 | 350 | You may add Your own copyright statement to Your modifications and 351 | may provide additional or different license terms and conditions 352 | for use, reproduction, or distribution of Your modifications, or 353 | for any such Derivative Works as a whole, provided Your use, 354 | reproduction, and distribution of the Work otherwise complies with 355 | the conditions stated in this License. 356 | 357 | 5. Submission of Contributions. Unless You explicitly state otherwise, 358 | any Contribution intentionally submitted for inclusion in the Work 359 | by You to the Licensor shall be under the terms and conditions of 360 | this License, without any additional terms or conditions. 361 | Notwithstanding the above, nothing herein shall supersede or modify 362 | the terms of any separate license agreement you may have executed 363 | with Licensor regarding such Contributions. 364 | 365 | 6. Trademarks. This License does not grant permission to use the trade 366 | names, trademarks, service marks, or product names of the Licensor, 367 | except as required for reasonable and customary use in describing the 368 | origin of the Work and reproducing the content of the NOTICE file. 369 | 370 | 7. Disclaimer of Warranty. Unless required by applicable law or 371 | agreed to in writing, Licensor provides the Work (and each 372 | Contributor provides its Contributions) on an "AS IS" BASIS, 373 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 374 | implied, including, without limitation, any warranties or conditions 375 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 376 | PARTICULAR PURPOSE. You are solely responsible for determining the 377 | appropriateness of using or redistributing the Work and assume any 378 | risks associated with Your exercise of permissions under this License. 379 | 380 | 8. Limitation of Liability. In no event and under no legal theory, 381 | whether in tort (including negligence), contract, or otherwise, 382 | unless required by applicable law (such as deliberate and grossly 383 | negligent acts) or agreed to in writing, shall any Contributor be 384 | liable to You for damages, including any direct, indirect, special, 385 | incidental, or consequential damages of any character arising as a 386 | result of this License or out of the use or inability to use the 387 | Work (including but not limited to damages for loss of goodwill, 388 | work stoppage, computer failure or malfunction, or any and all 389 | other commercial damages or losses), even if such Contributor 390 | has been advised of the possibility of such damages. 391 | 392 | 9. Accepting Warranty or Additional Liability. While redistributing 393 | the Work or Derivative Works thereof, You may choose to offer, 394 | and charge a fee for, acceptance of support, warranty, indemnity, 395 | or other liability obligations and/or rights consistent with this 396 | License. However, in accepting such obligations, You may act only 397 | on Your own behalf and on Your sole responsibility, not on behalf 398 | of any other Contributor, and only if You agree to indemnify, 399 | defend, and hold each Contributor harmless for any liability 400 | incurred by, or claims asserted against, such Contributor by reason 401 | of your accepting any such warranty or additional liability. 402 | 403 | END OF TERMS AND CONDITIONS 404 | 405 | APPENDIX: How to apply the Apache License to your work. 406 | 407 | To apply the Apache License to your work, attach the following 408 | boilerplate notice, with the fields enclosed by brackets "{}" 409 | replaced with your own identifying information. (Don't include 410 | the brackets!) The text should be enclosed in the appropriate 411 | comment syntax for the file format. We also recommend that a 412 | file or class name and description of purpose be included on the 413 | same "printed page" as the copyright notice for easier 414 | identification within third-party archives. 415 | 416 | Copyright 2014, Quick Team 417 | 418 | Licensed under the Apache License, Version 2.0 (the "License"); 419 | you may not use this file except in compliance with the License. 420 | You may obtain a copy of the License at 421 | 422 | http://www.apache.org/licenses/LICENSE-2.0 423 | 424 | Unless required by applicable law or agreed to in writing, software 425 | distributed under the License is distributed on an "AS IS" BASIS, 426 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 427 | See the License for the specific language governing permissions and 428 | limitations under the License. 429 | 430 | License 431 | Apache 2.0 432 | Title 433 | Quick 434 | Type 435 | PSGroupSpecifier 436 | 437 | 438 | FooterText 439 | Generated by CocoaPods - https://cocoapods.org 440 | Title 441 | 442 | Type 443 | PSGroupSpecifier 444 | 445 | 446 | StringsTable 447 | Acknowledgements 448 | Title 449 | Acknowledgements 450 | 451 | 452 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-StoriesLayout_Tests/Pods-StoriesLayout_Tests-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_StoriesLayout_Tests : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_StoriesLayout_Tests 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-StoriesLayout_Tests/Pods-StoriesLayout_Tests-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_StoriesLayout_TestsVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_StoriesLayout_TestsVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-StoriesLayout_Tests/Pods-StoriesLayout_Tests.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" "${PODS_CONFIGURATION_BUILD_DIR}/Nimble" "${PODS_CONFIGURATION_BUILD_DIR}/PowerTools" "${PODS_CONFIGURATION_BUILD_DIR}/Quick" "${PODS_CONFIGURATION_BUILD_DIR}/SafariLayout" "${PODS_CONFIGURATION_BUILD_DIR}/StoriesLayout" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Nimble/Nimble.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/PowerTools/PowerTools.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Quick/Quick.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SafariLayout/SafariLayout.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/StoriesLayout/StoriesLayout.framework/Headers" 5 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 6 | OTHER_LDFLAGS = $(inherited) -framework "CoreGraphics" -framework "Nimble" -framework "PowerTools" -framework "Quick" -framework "SafariLayout" -framework "StoriesLayout" -framework "UIKit" -framework "XCTest" -weak_framework "XCTest" 7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-StoriesLayout_Tests/Pods-StoriesLayout_Tests.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_StoriesLayout_Tests { 2 | umbrella header "Pods-StoriesLayout_Tests-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-StoriesLayout_Tests/Pods-StoriesLayout_Tests.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" "${PODS_CONFIGURATION_BUILD_DIR}/Nimble" "${PODS_CONFIGURATION_BUILD_DIR}/PowerTools" "${PODS_CONFIGURATION_BUILD_DIR}/Quick" "${PODS_CONFIGURATION_BUILD_DIR}/SafariLayout" "${PODS_CONFIGURATION_BUILD_DIR}/StoriesLayout" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Nimble/Nimble.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/PowerTools/PowerTools.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Quick/Quick.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SafariLayout/SafariLayout.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/StoriesLayout/StoriesLayout.framework/Headers" 5 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 6 | OTHER_LDFLAGS = $(inherited) -framework "CoreGraphics" -framework "Nimble" -framework "PowerTools" -framework "Quick" -framework "SafariLayout" -framework "StoriesLayout" -framework "UIKit" -framework "XCTest" -weak_framework "XCTest" 7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/StoriesLayout/StoriesLayout-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 0.1.1 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/StoriesLayout/StoriesLayout-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_StoriesLayout : NSObject 3 | @end 4 | @implementation PodsDummy_StoriesLayout 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/StoriesLayout/StoriesLayout-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/StoriesLayout/StoriesLayout-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double StoriesLayoutVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char StoriesLayoutVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/StoriesLayout/StoriesLayout.modulemap: -------------------------------------------------------------------------------- 1 | framework module StoriesLayout { 2 | umbrella header "StoriesLayout-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/StoriesLayout/StoriesLayout.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/StoriesLayout 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | OTHER_LDFLAGS = $(inherited) -framework "CoreGraphics" -framework "UIKit" 4 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 5 | PODS_BUILD_DIR = ${BUILD_DIR} 6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_ROOT = ${SRCROOT} 8 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 9 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 10 | SKIP_INSTALL = YES 11 | -------------------------------------------------------------------------------- /Example/StoriesLayout.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/StoriesLayout.xcodeproj/xcshareddata/xcschemes/StoriesLayout-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 46 | 47 | 53 | 54 | 60 | 61 | 62 | 63 | 65 | 71 | 72 | 73 | 74 | 75 | 81 | 82 | 83 | 84 | 85 | 86 | 96 | 98 | 104 | 105 | 106 | 107 | 108 | 109 | 115 | 117 | 123 | 124 | 125 | 126 | 128 | 129 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /Example/StoriesLayout.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/StoriesLayout.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/StoriesLayout/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // StoriesLayout 4 | // 5 | // Created by oni.zerone@gmail.com on 05/25/2019. 6 | // Copyright (c) 2019 oni.zerone@gmail.com. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | 19 | let window = UIWindow(frame: UIScreen.main.bounds) 20 | window.rootViewController = UINavigationController(rootViewController: ListViewController()) 21 | self.window = window 22 | window.makeKeyAndVisible() 23 | return true 24 | } 25 | 26 | func applicationWillResignActive(_ application: UIApplication) { 27 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 28 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 29 | } 30 | 31 | func applicationDidEnterBackground(_ application: UIApplication) { 32 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 33 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 34 | } 35 | 36 | func applicationWillEnterForeground(_ application: UIApplication) { 37 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | func applicationDidBecomeActive(_ application: UIApplication) { 41 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 42 | } 43 | 44 | func applicationWillTerminate(_ application: UIApplication) { 45 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 46 | } 47 | 48 | 49 | } 50 | 51 | -------------------------------------------------------------------------------- /Example/StoriesLayout/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Example/StoriesLayout/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Example/StoriesLayout/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/StoriesLayout/Images.xcassets/storiesLayout/001.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "architecture-blue-sky-cliff-248771.jpg" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Example/StoriesLayout/Images.xcassets/storiesLayout/001.imageset/architecture-blue-sky-cliff-248771.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Oni-zerone/CollectionLayouts/faef2a79540f1790916c79bb74edb0eca3689302/Example/StoriesLayout/Images.xcassets/storiesLayout/001.imageset/architecture-blue-sky-cliff-248771.jpg -------------------------------------------------------------------------------- /Example/StoriesLayout/Images.xcassets/storiesLayout/002.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "bird-s-eye-view-curve-drone-photography-1658967.jpg" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Example/StoriesLayout/Images.xcassets/storiesLayout/002.imageset/bird-s-eye-view-curve-drone-photography-1658967.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Oni-zerone/CollectionLayouts/faef2a79540f1790916c79bb74edb0eca3689302/Example/StoriesLayout/Images.xcassets/storiesLayout/002.imageset/bird-s-eye-view-curve-drone-photography-1658967.jpg -------------------------------------------------------------------------------- /Example/StoriesLayout/Images.xcassets/storiesLayout/003.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "bay-beach-boats-919237.jpg" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Example/StoriesLayout/Images.xcassets/storiesLayout/003.imageset/bay-beach-boats-919237.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Oni-zerone/CollectionLayouts/faef2a79540f1790916c79bb74edb0eca3689302/Example/StoriesLayout/Images.xcassets/storiesLayout/003.imageset/bay-beach-boats-919237.jpg -------------------------------------------------------------------------------- /Example/StoriesLayout/Images.xcassets/storiesLayout/004.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "aerial-architecture-blue-731229.jpg" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Example/StoriesLayout/Images.xcassets/storiesLayout/004.imageset/aerial-architecture-blue-731229.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Oni-zerone/CollectionLayouts/faef2a79540f1790916c79bb74edb0eca3689302/Example/StoriesLayout/Images.xcassets/storiesLayout/004.imageset/aerial-architecture-blue-731229.jpg -------------------------------------------------------------------------------- /Example/StoriesLayout/Images.xcassets/storiesLayout/005.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "architecture-building-city-311066.jpg" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Example/StoriesLayout/Images.xcassets/storiesLayout/005.imageset/architecture-building-city-311066.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Oni-zerone/CollectionLayouts/faef2a79540f1790916c79bb74edb0eca3689302/Example/StoriesLayout/Images.xcassets/storiesLayout/005.imageset/architecture-building-city-311066.jpg -------------------------------------------------------------------------------- /Example/StoriesLayout/Images.xcassets/storiesLayout/006.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "aerial-architecture-buildings-434194.jpg" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Example/StoriesLayout/Images.xcassets/storiesLayout/006.imageset/aerial-architecture-buildings-434194.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Oni-zerone/CollectionLayouts/faef2a79540f1790916c79bb74edb0eca3689302/Example/StoriesLayout/Images.xcassets/storiesLayout/006.imageset/aerial-architecture-buildings-434194.jpg -------------------------------------------------------------------------------- /Example/StoriesLayout/Images.xcassets/storiesLayout/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "properties" : { 7 | "provides-namespace" : true 8 | } 9 | } -------------------------------------------------------------------------------- /Example/StoriesLayout/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Example/StoriesLayout/LayoutCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LayoutCollectionViewCell.swift 3 | // StoriesLayout_Example 4 | // 5 | // Created by Andrea Altea on 02/06/2019. 6 | // Copyright © 2019 CocoaPods. All rights reserved. 7 | // 8 | 9 | import PowerTools 10 | 11 | class LayoutCollectionViewCell: UICollectionViewCell { 12 | 13 | @IBOutlet weak var title: UILabel! 14 | 15 | override func awakeFromNib() { 16 | super.awakeFromNib() 17 | self.title.text = nil 18 | } 19 | } 20 | 21 | extension LayoutCollectionViewCell: LayoutCell { 22 | func setupLayoutCell(title: String) { 23 | self.title.text = title 24 | } 25 | } 26 | 27 | extension LayoutCollectionViewCell { 28 | 29 | static let descriptor = String(describing: LayoutCollectionViewCell.self) 30 | 31 | struct Descriptor: ItemViewDescriptor, GridDescriptor { 32 | let reuseIdentifier = LayoutCollectionViewCell.descriptor 33 | 34 | var ratio: ViewRatio { 35 | return ViewRatio(multiplier: 0.0, constant: 60) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Example/StoriesLayout/LayoutCollectionViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Example/StoriesLayout/MySafariCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MySafariCollectionViewCell.swift 3 | // StoriesLayout_Example 4 | // 5 | // Created by Andrea Altea on 27/05/2019. 6 | // Copyright © 2019 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SafariLayout 11 | import PowerTools 12 | 13 | class MySafariCollectionViewCell: SafariCollectionViewCell { 14 | 15 | @IBOutlet weak var imageView: UIImageView! 16 | @IBOutlet weak var label: UILabel! 17 | 18 | override func awakeFromNib() { 19 | super.awakeFromNib() 20 | self.clipsToBounds = true 21 | self.layer.cornerRadius = 8.0 22 | } 23 | 24 | } 25 | 26 | extension MySafariCollectionViewCell { 27 | 28 | static let identifier = String(describing: MySafariCollectionViewCell.self) 29 | 30 | struct Descriptor: ItemViewDescriptor { 31 | var reuseIdentifier: String = MySafariCollectionViewCell.identifier 32 | } 33 | } 34 | 35 | extension MySafariCollectionViewCell: ImageCell { 36 | 37 | func set(image: UIImage) { 38 | self.imageView.image = image 39 | } 40 | 41 | func set(index: Int) { 42 | self.label.text = "\(index)" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Example/StoriesLayout/MySafariCollectionViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /Example/StoriesLayout/MyStoriesCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StoriesCollectionViewCell+Descriptor.swift 3 | // StoriesLayout_Example 4 | // 5 | // Created by Andrea Altea on 26/05/2019. 6 | // Copyright © 2019 CocoaPods. All rights reserved. 7 | // 8 | 9 | import PowerTools 10 | import StoriesLayout 11 | 12 | class MyStoriesCollectionViewCell: StoriesCollectionViewCell { 13 | 14 | static let sIdentifier = String(describing: MyStoriesCollectionViewCell.self) 15 | 16 | struct Descriptor: ItemViewDescriptor { 17 | var reuseIdentifier: String = MyStoriesCollectionViewCell.sIdentifier 18 | } 19 | 20 | @IBOutlet weak var imageView: UIImageView! 21 | @IBOutlet weak var label: UILabel! 22 | 23 | override func prepareForReuse() { 24 | self.imageView.image = nil 25 | } 26 | } 27 | 28 | extension MyStoriesCollectionViewCell: ImageCell { 29 | 30 | func set(image: UIImage) { 31 | self.imageView.image = image 32 | } 33 | 34 | func set(index: Int) { 35 | self.label.text = "\(index)" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Example/StoriesLayout/MyStoriesCollectionViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /Example/StoriesLayout/ViewControllers/DemoController/DemoController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // StoriesLayout 4 | // 5 | // Created by oni.zerone@gmail.com on 05/25/2019. 6 | // Copyright (c) 2019 oni.zerone@gmail.com. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import PowerTools 11 | import StoriesLayout 12 | 13 | class DemoController: UIViewController { 14 | 15 | var defaultImages = ["storiesLayout/001", 16 | "storiesLayout/002", 17 | "storiesLayout/003", 18 | "storiesLayout/004", 19 | "storiesLayout/005", 20 | "storiesLayout/006", 21 | "storiesLayout/001", 22 | "storiesLayout/002", 23 | "storiesLayout/003", 24 | "storiesLayout/004", 25 | "storiesLayout/005", 26 | "storiesLayout/006"] 27 | 28 | var layout: UICollectionViewLayout 29 | var cellDescriptor: ItemViewDescriptor 30 | 31 | weak var collectionView: UICollectionView! 32 | var dataSource: CollectionBinderDataSource! 33 | 34 | init(layout: UICollectionViewLayout, cellDescriptor: ItemViewDescriptor) { 35 | self.layout = layout 36 | self.cellDescriptor = cellDescriptor 37 | super.init(nibName: nil, bundle: nil) 38 | 39 | self.title = String(describing: type(of: layout.self)) 40 | } 41 | 42 | required init?(coder aDecoder: NSCoder) { 43 | fatalError("you should not create from xib") 44 | } 45 | 46 | override func viewDidLoad() { 47 | super.viewDidLoad() 48 | setupCollection() 49 | setupCells() 50 | setupDataSource() 51 | } 52 | 53 | func setupCollection() { 54 | let collection = UICollectionView(frame: .zero, collectionViewLayout: layout) 55 | collection.translatesAutoresizingMaskIntoConstraints = false 56 | view.addSubview(collection) 57 | 58 | view.safeAreaLayoutGuide.topAnchor.constraint(equalTo: collection.topAnchor).isActive = true 59 | view.safeAreaLayoutGuide.leftAnchor.constraint(equalTo: collection.leftAnchor).isActive = true 60 | view.safeAreaLayoutGuide.rightAnchor.constraint(equalTo: collection.rightAnchor).isActive = true 61 | view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: collection.bottomAnchor).isActive = true 62 | collectionView = collection 63 | } 64 | 65 | func setupCells() { 66 | collectionView.register(UINib(nibName: cellDescriptor.reuseIdentifier, 67 | bundle: .main), forCellWithReuseIdentifier: cellDescriptor.reuseIdentifier) 68 | } 69 | 70 | func setupDataSource() { 71 | let section = ConcreteSection(items: 72 | defaultImages.map { ImageViewModel(imageNamed: $0, cellDescriptor: self.cellDescriptor) }) 73 | 74 | dataSource = CollectionBinderDataSource(view: collectionView, model: [section]) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Example/StoriesLayout/ViewControllers/DemoController/DemoRoutable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoRoutable.swift 3 | // StoriesLayout_Example 4 | // 5 | // Created by Andrea Altea on 02/06/2019. 6 | // Copyright © 2019 CocoaPods. All rights reserved. 7 | // 8 | 9 | import PowerTools 10 | 11 | class DemoRoutable: Builder { 12 | 13 | var layout: UICollectionViewLayout 14 | var descriptor: ItemViewDescriptor 15 | 16 | init(layout: UICollectionViewLayout, descriptor: ItemViewDescriptor) { 17 | self.layout = layout 18 | self.descriptor = descriptor 19 | } 20 | 21 | override func build(_ context: UIViewController) -> UIViewController? { 22 | return DemoController(layout: layout, 23 | cellDescriptor: descriptor) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Example/StoriesLayout/ViewControllers/ListViewController/ListViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListViewController.swift 3 | // StoriesLayout_Example 4 | // 5 | // Created by Andrea Altea on 02/06/2019. 6 | // Copyright © 2019 CocoaPods. All rights reserved. 7 | // 8 | 9 | import PowerTools 10 | import StoriesLayout 11 | import SafariLayout 12 | 13 | class ListViewController: UIViewController { 14 | 15 | weak var collectionView: UICollectionView! 16 | var dataSource: GridCollectionDataSource! 17 | 18 | var layouts: [ItemViewModel] = [ 19 | LayoutItemViewModel(descriptor:MyStoriesCollectionViewCell.Descriptor()) { StoriesCollectionViewLayout() }, 20 | LayoutItemViewModel(descriptor:MySafariCollectionViewCell.Descriptor()) { SafariCollectionViewLayout() } 21 | ] 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | 26 | setupCollection() 27 | setupCells() 28 | setupDataSource() 29 | } 30 | 31 | func setupCollection() { 32 | let collection = UICollectionView(frame: .zero, 33 | collectionViewLayout: UICollectionViewFlowLayout()) 34 | collection.translatesAutoresizingMaskIntoConstraints = false 35 | collection.backgroundColor = .white 36 | view.addSubview(collection) 37 | 38 | view.topAnchor.constraint(equalTo: collection.topAnchor).isActive = true 39 | view.bottomAnchor.constraint(equalTo: collection.bottomAnchor).isActive = true 40 | view.leftAnchor.constraint(equalTo: collection.leftAnchor).isActive = true 41 | view.rightAnchor.constraint(equalTo: collection.rightAnchor).isActive = true 42 | self.collectionView = collection 43 | } 44 | 45 | func setupCells() { 46 | layouts.forEach { item in 47 | self.collectionView.register(UINib(nibName: item.descriptor.reuseIdentifier, 48 | bundle: Bundle.main), 49 | forCellWithReuseIdentifier: item.descriptor.reuseIdentifier) 50 | } 51 | } 52 | 53 | func setupDataSource() { 54 | self.dataSource = GridCollectionDataSource(view: collectionView, 55 | model: [ConcreteGridSection(items: layouts)]) 56 | self.dataSource.interactionDelegate = self 57 | } 58 | } 59 | 60 | extension ListViewController: InteractionFactory { 61 | var context: UIViewController { 62 | return self 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Example/StoriesLayout/ViewModels/ImageViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageViewModel.swift 3 | // StoriesLayout_Example 4 | // 5 | // Created by Andrea Altea on 26/05/2019. 6 | // Copyright © 2019 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import PowerTools 11 | import StoriesLayout 12 | 13 | protocol ImageCell { 14 | 15 | func set(image: UIImage) 16 | func set(index: Int) 17 | } 18 | 19 | struct ImageViewModel: ItemViewModel { 20 | 21 | var descriptor: ItemViewDescriptor 22 | var image: UIImage? 23 | 24 | var hashValue: Int { 25 | return image.hashValue 26 | } 27 | 28 | init(imageNamed name: String, cellDescriptor: ItemViewDescriptor) { 29 | self.descriptor = cellDescriptor 30 | self.image = UIImage(named: name) 31 | } 32 | 33 | func setup(_ view: UIView, in containerView: UIView, at indexPath: IndexPath) { 34 | guard let image = image else { return } 35 | (view as? ImageCell)?.set(image: image) 36 | (view as? ImageCell)?.set(index: indexPath.item) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Example/StoriesLayout/ViewModels/LayoutItemViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LayouutItemViewModel.swift 3 | // StoriesLayout_Example 4 | // 5 | // Created by Andrea Altea on 02/06/2019. 6 | // Copyright © 2019 CocoaPods. All rights reserved. 7 | // 8 | 9 | import PowerTools 10 | 11 | protocol LayoutCell { 12 | func setupLayoutCell(title: String) 13 | } 14 | 15 | class LayoutItemViewModel: GridItemViewModel { 16 | 17 | typealias LayoutInitializer = () -> Layout 18 | 19 | var descriptor: ItemViewDescriptor = LayoutCollectionViewCell.Descriptor() 20 | 21 | var layoutInitializer: LayoutInitializer 22 | var layoutCellDescriptor: ItemViewDescriptor 23 | 24 | private(set) var layoutName: String = { 25 | String(describing: Layout.self) 26 | }() 27 | 28 | var hashValue: Int { 29 | return layoutName.hashValue 30 | } 31 | 32 | init(descriptor: ItemViewDescriptor, layoutInitializer: @escaping LayoutInitializer) { 33 | self.layoutInitializer = layoutInitializer 34 | self.layoutCellDescriptor = descriptor 35 | } 36 | 37 | func setup(_ view: UIView, in containerView: UIView, at indexPath: IndexPath) { 38 | (view as? LayoutCell)?.setupLayoutCell(title: layoutName) 39 | } 40 | } 41 | 42 | extension LayoutItemViewModel: BuilderContainer { 43 | 44 | typealias Context = Any 45 | 46 | func getBuilder(_ contextType: Context.Type) -> Builder? { 47 | return DemoRoutable(layout: layoutInitializer(), descriptor: layoutCellDescriptor) as? Builder 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Example/StoriesLayout/ViewModels/Section.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Section.swift 3 | // StoriesLayout_Example 4 | // 5 | // Created by Andrea Altea on 02/06/2019. 6 | // Copyright © 2019 CocoaPods. All rights reserved. 7 | // 8 | 9 | import PowerTools 10 | 11 | struct ConcreteSection: SectionViewModel { 12 | var header: ItemViewModel? 13 | 14 | var items: [ItemViewModel] 15 | 16 | var footer: ItemViewModel? 17 | 18 | init(items: [ItemViewModel]) { 19 | self.header = nil 20 | self.items = items 21 | self.footer = nil 22 | } 23 | } 24 | 25 | 26 | struct ConcreteGridSection: SectionViewModel, GridSection { 27 | 28 | var header: ItemViewModel? 29 | 30 | var items: [ItemViewModel] 31 | 32 | var footer: ItemViewModel? 33 | 34 | init(items: [ItemViewModel]) { 35 | self.header = nil 36 | self.items = items 37 | self.footer = nil 38 | } 39 | 40 | var lineItems: Int? = 1 41 | var sectionInsets: UIEdgeInsets = .zero 42 | var sectionVerticalItemSpacing: CGFloat = 0.0 43 | var sectionHorizontalItemSpacing: CGFloat = 0.0 44 | } 45 | -------------------------------------------------------------------------------- /Example/Tests/Helpers/CATransform3D+Helper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CATransform3D+Helper.swift 3 | // StoriesLayout_Example 4 | // 5 | // Created by Andrea Altea on 06/07/2019. 6 | // Copyright © 2019 CocoaPods. All rights reserved. 7 | // 8 | 9 | import QuartzCore 10 | 11 | extension CATransform3D: Equatable { 12 | 13 | public static func == (lhs: CATransform3D, rhs: CATransform3D) -> Bool { 14 | return lhs.m11 == rhs.m11 15 | && lhs.m12 == rhs.m12 16 | && lhs.m13 == rhs.m13 17 | && lhs.m14 == rhs.m14 18 | 19 | && lhs.m21 == rhs.m21 20 | && lhs.m22 == rhs.m22 21 | && lhs.m23 == rhs.m23 22 | && lhs.m24 == rhs.m24 23 | 24 | && lhs.m31 == rhs.m31 25 | && lhs.m32 == rhs.m32 26 | && lhs.m33 == rhs.m33 27 | && lhs.m34 == rhs.m34 28 | 29 | && lhs.m41 == rhs.m41 30 | && lhs.m42 == rhs.m42 31 | && lhs.m43 == rhs.m43 32 | && lhs.m44 == rhs.m44 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Example/Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/Tests/Mocks/UICollectionDataSourceMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionDataSourceMock.swift 3 | // StoriesLayout_Tests 4 | // 5 | // Created by Andrea Altea on 29/06/2019. 6 | // Copyright © 2019 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class UICollectionViewDataSourceMock: NSObject, UICollectionViewDataSource { 12 | 13 | //MARK: - collectionView 14 | 15 | var collectionViewNumberOfItemsInSectionCallsCount = 0 16 | var collectionViewNumberOfItemsInSectionCalled: Bool { 17 | return collectionViewNumberOfItemsInSectionCallsCount > 0 18 | } 19 | var collectionViewNumberOfItemsInSectionReceivedArguments: (collectionView: UICollectionView, section: Int)? 20 | var collectionViewNumberOfItemsInSectionReceivedInvocations: [(collectionView: UICollectionView, section: Int)] = [] 21 | var collectionViewNumberOfItemsInSectionReturnValue: Int! 22 | var collectionViewNumberOfItemsInSectionClosure: ((UICollectionView, Int) -> Int)? 23 | 24 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 25 | collectionViewNumberOfItemsInSectionCallsCount += 1 26 | collectionViewNumberOfItemsInSectionReceivedArguments = (collectionView: collectionView, section: section) 27 | collectionViewNumberOfItemsInSectionReceivedInvocations.append((collectionView: collectionView, section: section)) 28 | return collectionViewNumberOfItemsInSectionClosure.map({ $0(collectionView, section) }) ?? collectionViewNumberOfItemsInSectionReturnValue 29 | } 30 | 31 | //MARK: - collectionView 32 | 33 | var collectionViewCellForItemAtCallsCount = 0 34 | var collectionViewCellForItemAtCalled: Bool { 35 | return collectionViewCellForItemAtCallsCount > 0 36 | } 37 | var collectionViewCellForItemAtReceivedArguments: (collectionView: UICollectionView, indexPath: IndexPath)? 38 | var collectionViewCellForItemAtReceivedInvocations: [(collectionView: UICollectionView, indexPath: IndexPath)] = [] 39 | var collectionViewCellForItemAtReturnValue: UICollectionViewCell! 40 | var collectionViewCellForItemAtClosure: ((UICollectionView, IndexPath) -> UICollectionViewCell)? 41 | 42 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 43 | collectionViewCellForItemAtCallsCount += 1 44 | collectionViewCellForItemAtReceivedArguments = (collectionView: collectionView, indexPath: indexPath) 45 | collectionViewCellForItemAtReceivedInvocations.append((collectionView: collectionView, indexPath: indexPath)) 46 | return collectionViewCellForItemAtClosure.map({ $0(collectionView, indexPath) }) ?? collectionViewCellForItemAtReturnValue 47 | } 48 | 49 | //MARK: - numberOfSections 50 | 51 | var numberOfSectionsInCallsCount = 0 52 | var numberOfSectionsInCalled: Bool { 53 | return numberOfSectionsInCallsCount > 0 54 | } 55 | var numberOfSectionsInReceivedCollectionView: UICollectionView? 56 | var numberOfSectionsInReceivedInvocations: [UICollectionView] = [] 57 | var numberOfSectionsInReturnValue: Int! 58 | var numberOfSectionsInClosure: ((UICollectionView) -> Int)? 59 | 60 | func numberOfSections(in collectionView: UICollectionView) -> Int { 61 | numberOfSectionsInCallsCount += 1 62 | numberOfSectionsInReceivedCollectionView = collectionView 63 | numberOfSectionsInReceivedInvocations.append(collectionView) 64 | return numberOfSectionsInClosure.map({ $0(collectionView) }) ?? numberOfSectionsInReturnValue 65 | } 66 | 67 | //MARK: - collectionView 68 | 69 | var collectionViewViewForSupplementaryElementOfKindAtCallsCount = 0 70 | var collectionViewViewForSupplementaryElementOfKindAtCalled: Bool { 71 | return collectionViewViewForSupplementaryElementOfKindAtCallsCount > 0 72 | } 73 | var collectionViewViewForSupplementaryElementOfKindAtReceivedArguments: (collectionView: UICollectionView, kind: String, indexPath: IndexPath)? 74 | var collectionViewViewForSupplementaryElementOfKindAtReceivedInvocations: [(collectionView: UICollectionView, kind: String, indexPath: IndexPath)] = [] 75 | var collectionViewViewForSupplementaryElementOfKindAtReturnValue: UICollectionReusableView! 76 | var collectionViewViewForSupplementaryElementOfKindAtClosure: ((UICollectionView, String, IndexPath) -> UICollectionReusableView)? 77 | 78 | func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { 79 | collectionViewViewForSupplementaryElementOfKindAtCallsCount += 1 80 | collectionViewViewForSupplementaryElementOfKindAtReceivedArguments = (collectionView: collectionView, kind: kind, indexPath: indexPath) 81 | collectionViewViewForSupplementaryElementOfKindAtReceivedInvocations.append((collectionView: collectionView, kind: kind, indexPath: indexPath)) 82 | return collectionViewViewForSupplementaryElementOfKindAtClosure.map({ $0(collectionView, kind, indexPath) }) ?? collectionViewViewForSupplementaryElementOfKindAtReturnValue 83 | } 84 | 85 | //MARK: - collectionView 86 | 87 | var collectionViewCanMoveItemAtCallsCount = 0 88 | var collectionViewCanMoveItemAtCalled: Bool { 89 | return collectionViewCanMoveItemAtCallsCount > 0 90 | } 91 | var collectionViewCanMoveItemAtReceivedArguments: (collectionView: UICollectionView, indexPath: IndexPath)? 92 | var collectionViewCanMoveItemAtReceivedInvocations: [(collectionView: UICollectionView, indexPath: IndexPath)] = [] 93 | var collectionViewCanMoveItemAtReturnValue: Bool! 94 | var collectionViewCanMoveItemAtClosure: ((UICollectionView, IndexPath) -> Bool)? 95 | 96 | func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool { 97 | collectionViewCanMoveItemAtCallsCount += 1 98 | collectionViewCanMoveItemAtReceivedArguments = (collectionView: collectionView, indexPath: indexPath) 99 | collectionViewCanMoveItemAtReceivedInvocations.append((collectionView: collectionView, indexPath: indexPath)) 100 | return collectionViewCanMoveItemAtClosure.map({ $0(collectionView, indexPath) }) ?? collectionViewCanMoveItemAtReturnValue 101 | } 102 | 103 | //MARK: - collectionView 104 | 105 | var collectionViewMoveItemAtToCallsCount = 0 106 | var collectionViewMoveItemAtToCalled: Bool { 107 | return collectionViewMoveItemAtToCallsCount > 0 108 | } 109 | var collectionViewMoveItemAtToReceivedArguments: (collectionView: UICollectionView, sourceIndexPath: IndexPath, destinationIndexPath: IndexPath)? 110 | var collectionViewMoveItemAtToReceivedInvocations: [(collectionView: UICollectionView, sourceIndexPath: IndexPath, destinationIndexPath: IndexPath)] = [] 111 | var collectionViewMoveItemAtToClosure: ((UICollectionView, IndexPath, IndexPath) -> Void)? 112 | 113 | func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { 114 | collectionViewMoveItemAtToCallsCount += 1 115 | collectionViewMoveItemAtToReceivedArguments = (collectionView: collectionView, sourceIndexPath: sourceIndexPath, destinationIndexPath: destinationIndexPath) 116 | collectionViewMoveItemAtToReceivedInvocations.append((collectionView: collectionView, sourceIndexPath: sourceIndexPath, destinationIndexPath: destinationIndexPath)) 117 | collectionViewMoveItemAtToClosure?(collectionView, sourceIndexPath, destinationIndexPath) 118 | } 119 | 120 | //MARK: - indexTitles 121 | 122 | var indexTitlesForCallsCount = 0 123 | var indexTitlesForCalled: Bool { 124 | return indexTitlesForCallsCount > 0 125 | } 126 | var indexTitlesForReceivedCollectionView: UICollectionView? 127 | var indexTitlesForReceivedInvocations: [UICollectionView] = [] 128 | var indexTitlesForReturnValue: [String]? 129 | var indexTitlesForClosure: ((UICollectionView) -> [String]?)? 130 | 131 | func indexTitles(for collectionView: UICollectionView) -> [String]? { 132 | indexTitlesForCallsCount += 1 133 | indexTitlesForReceivedCollectionView = collectionView 134 | indexTitlesForReceivedInvocations.append(collectionView) 135 | return indexTitlesForClosure.map({ $0(collectionView) }) ?? indexTitlesForReturnValue 136 | } 137 | 138 | //MARK: - collectionView 139 | 140 | var collectionViewIndexPathForIndexTitleAtCallsCount = 0 141 | var collectionViewIndexPathForIndexTitleAtCalled: Bool { 142 | return collectionViewIndexPathForIndexTitleAtCallsCount > 0 143 | } 144 | var collectionViewIndexPathForIndexTitleAtReceivedArguments: (collectionView: UICollectionView, title: String, index: Int)? 145 | var collectionViewIndexPathForIndexTitleAtReceivedInvocations: [(collectionView: UICollectionView, title: String, index: Int)] = [] 146 | var collectionViewIndexPathForIndexTitleAtReturnValue: IndexPath! 147 | var collectionViewIndexPathForIndexTitleAtClosure: ((UICollectionView, String, Int) -> IndexPath)? 148 | 149 | func collectionView(_ collectionView: UICollectionView, indexPathForIndexTitle title: String, at index: Int) -> IndexPath { 150 | collectionViewIndexPathForIndexTitleAtCallsCount += 1 151 | collectionViewIndexPathForIndexTitleAtReceivedArguments = (collectionView: collectionView, title: title, index: index) 152 | collectionViewIndexPathForIndexTitleAtReceivedInvocations.append((collectionView: collectionView, title: title, index: index)) 153 | return collectionViewIndexPathForIndexTitleAtClosure.map({ $0(collectionView, title, index) }) ?? collectionViewIndexPathForIndexTitleAtReturnValue 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /Example/Tests/StoriesLayout/StoriesCollectionViewLayoutSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StoriesCollectionViewLayoutSpec.swift 3 | // StoriesLayout_Tests 4 | // 5 | // Created by Andrea Altea on 29/06/2019. 6 | // Copyright © 2019 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Quick 10 | import Nimble 11 | import StoriesLayout 12 | 13 | class StoriesCollectionViewLayoutSpec: QuickSpec { 14 | 15 | override func spec() { 16 | let cellIdentifier = "testCell" 17 | 18 | var sut: StoriesCollectionViewLayout! 19 | var collectionViewSize: CGRect! 20 | var collectionView: UICollectionView! 21 | 22 | var dataSource: UICollectionViewDataSourceMock! 23 | 24 | beforeEach { 25 | sut = StoriesCollectionViewLayout() 26 | } 27 | 28 | context("check default values") { 29 | xit("must have base transformation") { 30 | expect(sut.baseTransform.m34).to(equal(1.0 / 1000)) 31 | } 32 | } 33 | 34 | context("collection interaction") { 35 | beforeEach { 36 | collectionViewSize = CGRect(origin: .zero, size: CGSize(width: 320, height: 650)) 37 | collectionView = UICollectionView(frame: collectionViewSize, 38 | collectionViewLayout: sut) 39 | collectionView.register(StoriesCollectionViewCell.self, 40 | forCellWithReuseIdentifier: cellIdentifier) 41 | dataSource = .create(sections: 1, 42 | items: 10, 43 | cellIdentifier: cellIdentifier) 44 | collectionView.dataSource = dataSource 45 | } 46 | 47 | it("it reloads") { 48 | collectionView.reloadData() 49 | } 50 | 51 | it("calculates contentSize") { 52 | let numberOfItems = CGFloat(collectionView.numberOfItems(inSection: 0)) 53 | let contentSize = sut.collectionViewContentSize 54 | expect(contentSize.height).to(equal(collectionView.bounds.height)) 55 | expect(contentSize.width).to(equal(collectionView.bounds.width * numberOfItems)) 56 | } 57 | 58 | it("should always invalidate layout") { 59 | let shouldInvalidate = sut.shouldInvalidateLayout(forBoundsChange: collectionView.bounds) 60 | expect(shouldInvalidate).to(beTrue()) 61 | } 62 | 63 | it("must return first cell in default position") { 64 | collectionView.contentOffset = .zero 65 | let indexPath = IndexPath(item: 0, section: 0) 66 | guard let attributes = sut.layoutAttributesForItem(at: indexPath) as? StoriesLayoutAttributes else { 67 | XCTFail("Should be StoruesLayoutAttributes") 68 | return 69 | } 70 | expect(attributes.indexPath).to(equal(indexPath)) 71 | expect(attributes.frame).to(equal(collectionView.bounds)) 72 | expect(attributes.anchorPoint).to(equal(CGPoint(x: 0.5, y: 0.5))) 73 | expect(attributes.transform3D).to(equal(CATransform3DIdentity)) 74 | } 75 | 76 | it("must return first cell half folded") { 77 | collectionView.contentOffset = CGPoint(x: collectionView.bounds.width / 2, y: 0) 78 | let indexPath = IndexPath(item: 0, section: 0) 79 | guard let attributes = sut.layoutAttributesForItem(at: indexPath) as? StoriesLayoutAttributes else { 80 | XCTFail("Should be StoruesLayoutAttributes") 81 | return 82 | } 83 | expect(attributes.indexPath).to(equal(indexPath)) 84 | expect(attributes.bounds).to(equal(CGRect(origin: .zero, size: collectionView.bounds.size))) 85 | expect(attributes.anchorPoint).to(equal(CGPoint(x: 1, y: 0.5))) 86 | expect(attributes.transform3D).toNot(equal(CATransform3DIdentity)) 87 | } 88 | 89 | it("must return first cell half folded") { 90 | collectionView.contentOffset = CGPoint(x: -collectionView.bounds.width / 2, y: 0) 91 | let indexPath = IndexPath(item: 0, section: 0) 92 | guard let attributes = sut.layoutAttributesForItem(at: indexPath) as? StoriesLayoutAttributes else { 93 | fail("Should be StoruesLayoutAttributes") 94 | return 95 | } 96 | expect(attributes.indexPath).to(equal(indexPath)) 97 | expect(attributes.bounds).to(equal(CGRect(origin: .zero, size: collectionView.bounds.size))) 98 | expect(attributes.anchorPoint).to(equal(CGPoint(x: 0.0, y: 0.5))) 99 | expect(attributes.transform3D).toNot(equal(CATransform3DIdentity)) 100 | } 101 | 102 | it("must return only first item") { 103 | collectionView.contentOffset = .zero 104 | guard let attributes = sut.layoutAttributesForElements(in: collectionView.bounds) as? [StoriesLayoutAttributes] else { 105 | fail("Should be StoriesLayoutAttributes") 106 | return 107 | } 108 | expect(attributes.count).to(equal(1)) 109 | expect(attributes.filter { $0.indexPath == IndexPath(item: 0, section: 0) }.count).to(equal(1)) 110 | } 111 | 112 | it("must return first and second item") { 113 | collectionView.contentOffset = CGPoint(x: 10, y: 0) 114 | guard let attributes = sut.layoutAttributesForElements(in: collectionView.bounds) as? [StoriesLayoutAttributes] else { 115 | fail("Should be StoriesLayoutAttributes") 116 | return 117 | } 118 | expect(attributes.count).to(equal(2)) 119 | expect(attributes.filter { $0.indexPath == IndexPath(item: 0, section: 0) }.count).to(equal(1)) 120 | expect(attributes.filter { $0.indexPath == IndexPath(item: 1, section: 0) }.count).to(equal(1)) 121 | } 122 | 123 | it("must return only first item") { 124 | collectionView.contentOffset = CGPoint(x: -10, y: 0) 125 | guard let attributes = sut.layoutAttributesForElements(in: collectionView.bounds) as? [StoriesLayoutAttributes] else { 126 | fail("Should be StoriesLayoutAttributes") 127 | return 128 | } 129 | expect(attributes.count).to(equal(1)) 130 | expect(attributes.filter { $0.indexPath == IndexPath(item: 0, section: 0) }.count).to(equal(1)) 131 | } 132 | 133 | it("must return only first item") { 134 | collectionView.contentOffset = CGPoint(x: collectionView.bounds.width * 1.5, y: 0) 135 | guard let attributes = sut.layoutAttributesForElements(in: collectionView.bounds) as? [StoriesLayoutAttributes] else { 136 | fail("Should be StoriesLayoutAttributes") 137 | return 138 | } 139 | expect(attributes.count).to(equal(2)) 140 | expect(attributes.filter { $0.indexPath == IndexPath(item: 1, section: 0) }.count).to(equal(1)) 141 | expect(attributes.filter { $0.indexPath == IndexPath(item: 2, section: 0) }.count).to(equal(1)) 142 | } 143 | } 144 | } 145 | } 146 | 147 | extension UICollectionViewDataSourceMock { 148 | 149 | static func create(sections: Int, items: Int, cellIdentifier identifier: String) -> UICollectionViewDataSourceMock { 150 | 151 | let dataSource = UICollectionViewDataSourceMock() 152 | dataSource.numberOfSectionsInClosure = { _ in return sections } 153 | dataSource.collectionViewNumberOfItemsInSectionClosure = { _, _ in return items } 154 | dataSource.collectionViewCellForItemAtClosure = { collection, indexPath in 155 | return collection.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) 156 | } 157 | return dataSource 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /Example/Tests/StoriesLayout/StoriesLayoutAttributesSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StoriesLayoutAttributesSpec.swift 3 | // StoriesLayout_Tests 4 | // 5 | // Created by Andrea Altea on 29/06/2019. 6 | // Copyright © 2019 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Quick 10 | import Nimble 11 | import StoriesLayout 12 | 13 | class StoriesLayoutAttributesSpec: QuickSpec { 14 | 15 | override func spec() { 16 | var sut: StoriesLayoutAttributes! 17 | 18 | beforeEach { 19 | sut = StoriesLayoutAttributes(forCellWith: IndexPath(item: 0, section: 0)) 20 | } 21 | 22 | context("test default values") { 23 | it("should have alpha value") { 24 | expect(sut.anchorPoint).to(equal(CGPoint(x: 0.5, y: 0.5))) 25 | } 26 | 27 | it("should not have gradient value") { 28 | expect(sut.gradient).to(beNil()) 29 | } 30 | } 31 | 32 | context("test parameters NSCopying") { 33 | it("must copy IndexPath") { 34 | let indexPath = IndexPath(item: 1, section: 2) 35 | sut.indexPath = indexPath 36 | 37 | guard let sutCopy = sut.copy() as? StoriesLayoutAttributes else { 38 | return fail("should be copied") 39 | } 40 | expect(sutCopy.indexPath).to(equal(indexPath)) 41 | } 42 | 43 | it("must copy anchor value") { 44 | let anchor = CGPoint(x: 0.7, y: 0.3) 45 | sut.anchorPoint = anchor 46 | 47 | guard let sutCopy = sut.copy() as? StoriesLayoutAttributes else { 48 | return fail("should be copied") 49 | } 50 | expect(sutCopy.anchorPoint).to(equal(anchor)) 51 | } 52 | 53 | it("must copy anchor value") { 54 | let gradient = StoriesLayoutAttributes.Gradient.left(percent: 0.6) 55 | sut.gradient = gradient 56 | 57 | guard let sutCopy = sut.copy() as? StoriesLayoutAttributes else { 58 | return fail("should be copied") 59 | } 60 | expect(sutCopy.gradient).to(equal(gradient)) 61 | } 62 | } 63 | 64 | context("test equality between attributes") { 65 | it("copied attributes without gradient should be equal") { 66 | guard let sutCopy = sut.copy() as? StoriesLayoutAttributes else { 67 | return fail("should be copied") 68 | } 69 | expect(sutCopy).to(equal(sut)) 70 | } 71 | 72 | it("copied attributes with gradient should be equal") { 73 | sut.gradient = .left(percent: 0.4) 74 | guard let sutCopy = sut.copy() as? StoriesLayoutAttributes else { 75 | return fail("should be copied") 76 | } 77 | expect(sutCopy).to(equal(sut)) 78 | } 79 | 80 | it("copied attributes with different gradient should not be equal") { 81 | guard let sutCopy = sut.copy() as? StoriesLayoutAttributes else { 82 | return fail("should be copied") 83 | } 84 | sut.gradient = .left(percent: 0.4) 85 | expect(sutCopy).toNot(equal(sut)) 86 | } 87 | 88 | it("copied attributes with different anchorPoint should not be equal") { 89 | guard let sutCopy = sut.copy() as? StoriesLayoutAttributes else { 90 | return fail("should be copied") 91 | } 92 | sut.anchorPoint = CGPoint(x: 0.3, y: 0.1) 93 | expect(sutCopy).toNot(equal(sut)) 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Example/fastlane/Appfile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Oni-zerone/CollectionLayouts/faef2a79540f1790916c79bb74edb0eca3689302/Example/fastlane/Appfile -------------------------------------------------------------------------------- /Example/fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | # This file contains the fastlane.tools configuration 2 | # You can find the documentation at https://docs.fastlane.tools 3 | # 4 | # For a list of all available actions, check out 5 | # 6 | # https://docs.fastlane.tools/actions 7 | # 8 | # For a list of all available plugins, check out 9 | # 10 | # https://docs.fastlane.tools/plugins/available-plugins 11 | # 12 | 13 | update_fastlane 14 | 15 | default_platform(:ios) 16 | 17 | platform :ios do 18 | 19 | desc "Default CI lane" 20 | lane :ci do 21 | update_dependencies 22 | pod_lint 23 | test 24 | coverage 25 | end 26 | 27 | desc "Update dependencies" 28 | lane :update_dependencies do 29 | cocoapods( 30 | repo_update: true 31 | ) 32 | end 33 | 34 | desc "SwiftLint linting" 35 | lane :lint do 36 | swiftlint( 37 | mode: :lint, 38 | ignore_exit_status: false 39 | ) 40 | end 41 | 42 | desc "Pod lib lint" 43 | lane :pod_lint do 44 | Dir.chdir("../..") do 45 | sh("pod lib lint") 46 | end 47 | end 48 | 49 | desc "Runs all the tests" 50 | lane :test do 51 | scan( 52 | scheme: "StoriesLayout-Example", 53 | device: "iPhone XS", 54 | code_coverage: true 55 | ) 56 | end 57 | 58 | desc "Slather sends coverage to Coveralls" 59 | lane :coverage do 60 | slather( 61 | travis: true, 62 | workspace: "StoriesLayout.xcworkspace", 63 | proj: "StoriesLayout.xcodeproj", 64 | scheme: "StoriesLayout-Example", 65 | binary_basename: "StoriesLayout", 66 | source_directory: "../StoriesLayout/*", 67 | simple_output: true, 68 | coveralls: true 69 | ) 70 | end 71 | 72 | desc "Clean environment after all operations" 73 | lane :clean do 74 | clean_build_artifacts 75 | end 76 | 77 | after_all do |lane| 78 | clean 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /Example/fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ================ 3 | # Installation 4 | 5 | Make sure you have the latest version of the Xcode command line tools installed: 6 | 7 | ``` 8 | xcode-select --install 9 | ``` 10 | 11 | Install _fastlane_ using 12 | ``` 13 | [sudo] gem install fastlane -NV 14 | ``` 15 | or alternatively using `brew cask install fastlane` 16 | 17 | # Available Actions 18 | ## iOS 19 | ### ios ci 20 | ``` 21 | fastlane ios ci 22 | ``` 23 | Default CI lane 24 | ### ios lint 25 | ``` 26 | fastlane ios lint 27 | ``` 28 | SwiftLint linting 29 | ### ios pod_lint 30 | ``` 31 | fastlane ios pod_lint 32 | ``` 33 | Pod lib lint 34 | ### ios test 35 | ``` 36 | fastlane ios test 37 | ``` 38 | Runs all the tests 39 | ### ios coverage 40 | ``` 41 | fastlane ios coverage 42 | ``` 43 | Slather sends coverage to Coveralls 44 | ### ios clean 45 | ``` 46 | fastlane ios clean 47 | ``` 48 | Clean environment after all operations 49 | 50 | ---- 51 | 52 | This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. 53 | More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). 54 | The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 55 | -------------------------------------------------------------------------------- /Example/fastlane/report.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 oni.zerone@gmail.com 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CollectionLayouts 2 | [![Build Status](https://travis-ci.com/Oni-zerone/CollectionLayouts.svg?branch=develop)](https://travis-ci.com/Oni-zerone/CollectionLayouts) 3 | [![codebeat badge](https://codebeat.co/badges/69b7abb0-6470-4247-ab1f-5281e4ca0aa6)](https://codebeat.co/projects/github-com-oni-zerone-collectionlayouts-develop) 4 | [![Coverage Status](https://coveralls.io/repos/github/Oni-zerone/CollectionLayouts/badge.svg?branch=develop)](https://coveralls.io/github/Oni-zerone/CollectionLayouts?branch=develop) 5 | 6 | ![SafariLayout](http://studiout.it/github/SafariLayout.png) 7 | 8 | [![Version](https://img.shields.io/cocoapods/v/StoriesLayout.svg?style=flat)](https://cocoapods.org/pods/SafariLayout) 9 | [![License](https://img.shields.io/cocoapods/l/StoriesLayout.svg?style=flat)](https://cocoapods.org/pods/SafariLayout) 10 | [![Platform](https://img.shields.io/cocoapods/p/StoriesLayout.svg?style=flat)](https://cocoapods.org/pods/SafariLayout) 11 | 12 | This is a UICollectionViewLayout that reproduce the iOS Safari tabs experience, 13 | just use SafariCollectionViewLayout in your UICollectionView and subclass your cells from SafariCollectionViewCell! 14 | 15 | ### Installation 16 | * Via cocoapods: 17 | ```ruby 18 | pod 'SafariLayout' 19 | ``` 20 | 21 | ![StoriesLayout](http://studiout.it/github/StoriesLayout.png) 22 | 23 | [![Version](https://img.shields.io/cocoapods/v/StoriesLayout.svg?style=flat)](https://cocoapods.org/pods/StoriesLayout) 24 | [![License](https://img.shields.io/cocoapods/l/StoriesLayout.svg?style=flat)](https://cocoapods.org/pods/StoriesLayout) 25 | [![Platform](https://img.shields.io/cocoapods/p/StoriesLayout.svg?style=flat)](https://cocoapods.org/pods/StoriesLayout) 26 | 27 | This is a UICollectionViewLayout that reproduce the Instagram Stories experience, 28 | just use StoriesCollectionViewLayout in your UICollectionView and subclass your cells from StoriesCollectionViewCell! 29 | 30 | ### Installation 31 | * Via cocoapods: 32 | ```ruby 33 | pod 'StoriesLayout' 34 | ``` 35 | 36 | ### Usage 37 | You can find more info about usage in the [wiki](https://github.com/Oni-zerone/CollectionLayouts/wiki/StoriesLayout) 38 | -------------------------------------------------------------------------------- /SafariLayout.podspec: -------------------------------------------------------------------------------- 1 | 2 | Pod::Spec.new do |s| 3 | s.name = 'SafariLayout' 4 | s.version = '0.1.1' 5 | s.summary = 'A Safari tabs like UICollectionViewLayout' 6 | s.swift_version = '5.0' 7 | s.description = <<-DESC 8 | This is a UICollectionViewLayout that reproduce the iOS Safari tabs experience, 9 | just use SafariCollectionViewLayout in your UICollectionView and subclass your cells from SafariCollectionViewCell! 10 | DESC 11 | 12 | s.homepage = 'https://github.com/Oni-zerone/CollectionLayouts' 13 | s.license = { :type => 'MIT', :file => 'LICENSE' } 14 | s.author = { 'Andrea Altea' => 'oni.zerone@gmail.com' } 15 | s.source = { :git => 'https://github.com/Oni-zerone/CollectionLayouts.git', :tag => 'SafariLayout-' + s.version.to_s } 16 | s.social_media_url = 'https://twitter.com/Oni_zerone' 17 | 18 | s.ios.deployment_target = '10.0' 19 | 20 | s.source_files = 'SafariLayout/Classes/**/*' 21 | s.frameworks = 'UIKit', 'CoreGraphics' 22 | end 23 | -------------------------------------------------------------------------------- /SafariLayout/Classes/SafariCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SafariCollectionViewCell.swift 3 | // SafariLayout 4 | // 5 | // Created by Andrea Altea on 27/05/2019. 6 | // 7 | 8 | import UIKit 9 | 10 | /** 11 | SafariCollectionViewCell is a base class that you could use to create your custom cells with the tilted tab experience. 12 | */ 13 | open class SafariCollectionViewCell: UICollectionViewCell { 14 | 15 | override open func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) { 16 | super.apply(layoutAttributes) 17 | guard let storiesAttributes = layoutAttributes as? SafariCollectionViewLayoutAttributes else { return } 18 | self.layer.anchorPoint = storiesAttributes.anchorPoint 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /SafariLayout/Classes/SafariCollectionViewLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SafariCollectionViewLayout.swift 3 | // Pods 4 | // 5 | // Created by Andrea Altea on 27/05/2019. 6 | // 7 | 8 | import UIKit 9 | 10 | /** 11 | A concrete layout object that reproduce the look and feel of the Safari tab bars. 12 | 13 | The items will be shown in a single column aligned as cards with the same dimension of the screen tilted in relation to the position in the collection. 14 | 15 | Actually there isn't a specific delegate to customize layout behviors of the cells. 16 | */ 17 | @IBDesignable 18 | public class SafariCollectionViewLayout: UICollectionViewLayout { 19 | 20 | private var _tabsCount: Int = 4 21 | 22 | /** 23 | The maximum number of tabs shown on the screen. 24 | 25 | Line spacing between the cells will be calculated based on this parameter. 26 | 27 | Default value of this property is 4 28 | */ 29 | @IBInspectable public var tabsCount: Int { 30 | set { 31 | _tabsCount = max(newValue, 1) 32 | collectionView?.reloadData() 33 | } 34 | get { 35 | return _tabsCount 36 | } 37 | } 38 | 39 | /** 40 | The angle that the cell assumes at the center of the collection. 41 | 42 | This is the main value used to tune the cell position in the collection. 43 | Define the overall orientation of the cell and the appearance in the collection. 44 | 45 | Default value of this property is 114 degrees. 46 | */ 47 | var defaultAngle: CGFloat = CGFloat.pi * 0.2 48 | 49 | /** 50 | The angle variation between the top and the center of the collection. 51 | 52 | This is the value used to realize the tilting effect when the cell moves in the collection. 53 | Define the overall orientation of the cell and the appearance in the collection. 54 | 55 | Default value of this property is 180 degrees. 56 | */ 57 | var variationAngle: CGFloat = CGFloat.pi * 0.2 58 | 59 | /** 60 | Defines the contentSize of the collectionView 61 | 62 | Actually SafariCollectionViewLayout can handle a single section and the cell size is determined as the same of the collection, but tilted. 63 | 64 | Line spacing between the cells is defined by the tabs count. 65 | */ 66 | override public var collectionViewContentSize: CGSize { 67 | guard let collection = collectionView, 68 | let dataSource = collection.dataSource else { 69 | return .zero 70 | } 71 | 72 | let cellsCount = dataSource.collectionView(collection, numberOfItemsInSection: 0) 73 | if cellsCount < 1 { 74 | return .zero 75 | } 76 | 77 | if cellsCount == 1 { 78 | return collection.bounds.size 79 | } 80 | 81 | return CGSize(width: collection.bounds.width, 82 | height: CGFloat(cellsCount) * (collection.bounds.height / CGFloat(_tabsCount))) 83 | } 84 | 85 | override public func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { 86 | 87 | let firstVisibleItem = self.firstVisibleItem 88 | let lastVisibleItem = min(firstVisibleItem + tabsCount, itemsCount - 1) 89 | if lastVisibleItem < firstVisibleItem { 90 | let indexPath = IndexPath(item: lastVisibleItem, section: 0) 91 | guard let cellAttributes = layoutAttributesForItem(at: indexPath) else { return nil } 92 | return [cellAttributes] 93 | } 94 | 95 | var attributes = [UICollectionViewLayoutAttributes]() 96 | (firstVisibleItem ... lastVisibleItem).forEach { index in 97 | let indexPath = IndexPath(item: index, section: 0) 98 | guard let cellAttributes = layoutAttributesForItem(at: indexPath) else { return } 99 | attributes.append(cellAttributes) 100 | } 101 | return attributes 102 | } 103 | 104 | override public func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { 105 | let collectionBounds = self.collectionBounds 106 | let collectionOffset = self.collectionOffset 107 | 108 | let attributes = SafariCollectionViewLayoutAttributes(forCellWith: indexPath) 109 | attributes.frame = frameForItem(at: indexPath) 110 | 111 | let tilt = ((collectionOffset.y - attributes.frame.minY) / collectionBounds.height) 112 | var perspective = CATransform3DIdentity 113 | perspective.m34 = -1/1000 114 | 115 | let rotation = CATransform3DRotate(perspective, (tilt * variationAngle) - defaultAngle, 1.0, 0.0, 0.0) 116 | let translation = CATransform3DMakeTranslation(0.0, collectionBounds.height / -2, 0.0) 117 | attributes.transform3D = CATransform3DConcat(rotation, translation) 118 | attributes.anchorPoint = CGPoint(x: 0.5, y: 0.0) 119 | return attributes 120 | } 121 | 122 | public override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { 123 | return true 124 | } 125 | } 126 | 127 | extension SafariCollectionViewLayout { 128 | 129 | var itemsCount: Int { 130 | guard let collection = collectionView, 131 | let dataSource = collection.dataSource else { 132 | return 0 133 | } 134 | return dataSource.collectionView(collection, numberOfItemsInSection: 0) 135 | } 136 | 137 | var firstVisibleItem: Int { 138 | guard let collection = collectionView else { 139 | return 0 140 | } 141 | return max(Int(collection.contentOffset.y / (collection.bounds.height / CGFloat(tabsCount))), 0) 142 | } 143 | 144 | var collectionBounds: CGRect { 145 | return self.collectionView?.bounds ?? .zero 146 | } 147 | 148 | var collectionOffset: CGPoint { 149 | return self.collectionView?.contentOffset ?? .zero 150 | } 151 | 152 | func sectionInset(for section: Int) -> UIEdgeInsets { 153 | guard let collection = collectionView, 154 | let insets = (collection.delegate as? UICollectionViewDelegateFlowLayout)? 155 | .collectionView?(collection, layout: self, insetForSectionAt: section) else { 156 | return collectionView?.contentInset ?? .zero 157 | } 158 | return insets 159 | } 160 | 161 | func frameForItem(at indexPath: IndexPath) -> CGRect { 162 | let insets = sectionInset(for: indexPath.section) 163 | return CGRect(x: insets.left, 164 | y: CGFloat(indexPath.item) * (collectionBounds.height / CGFloat(tabsCount)) + insets.top, 165 | width: collectionBounds.width - (insets.left + insets.right), 166 | height: collectionBounds.height - (insets.top + insets.bottom)) 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /SafariLayout/Classes/SafariCollectionViewLayoutAttributes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SafariCollectionViewLayoutAttributes.swift 3 | // SafariLayout 4 | // 5 | // Created by Andrea Altea on 27/05/2019. 6 | // 7 | 8 | import UIKit 9 | /** 10 | The custom `UICollectionViewLayoutAttributes` subclass used by `SafariCollectionViewLayout` 11 | */ 12 | class SafariCollectionViewLayoutAttributes: UICollectionViewLayoutAttributes { 13 | 14 | /** 15 | Anchor point is the realtive position used to calculate the tilt transformation of the cell. 16 | 17 | If you won't use the `SafariCollectionViewCell` as your base cell class remember to override 18 | 19 | `func apply(_ layoutAttributes: UICollectionViewLayoutAttributes)` method to pass the anchorPoint property 20 | to the cell layer. 21 | */ 22 | var anchorPoint = CGPoint(x: 0.5, y: 0.5) 23 | 24 | /// Overridden for NSCopying compatibility 25 | override func copy(with zone: NSZone? = nil) -> Any { 26 | let attribute = super.copy(with: zone) as! SafariCollectionViewLayoutAttributes 27 | attribute.anchorPoint = self.anchorPoint 28 | return attribute 29 | 30 | } 31 | 32 | /// Overridden for Equatable compatibility 33 | override func isEqual(_ object: Any?) -> Bool { 34 | guard let object = object as? SafariCollectionViewLayoutAttributes, 35 | object.anchorPoint == self.anchorPoint else { 36 | return false 37 | } 38 | return super.isEqual(object) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /StoriesLayout.podspec: -------------------------------------------------------------------------------- 1 | 2 | Pod::Spec.new do |s| 3 | s.name = 'StoriesLayout' 4 | s.version = '0.1.1' 5 | s.summary = 'An Instagram Stories like UICollectionViewLayout' 6 | s.swift_version = '5.0' 7 | s.description = <<-DESC 8 | This is a UICollectionViewLayout that reproduce the Instagram Stories experience, 9 | just use StoriesCollectionViewLayout in your UICollectionView and subclass your cells from StoriesCollectionViewCell! 10 | DESC 11 | 12 | s.homepage = 'https://github.com/Oni-zerone/CollectionLayouts' 13 | s.license = { :type => 'MIT', :file => 'LICENSE' } 14 | s.author = { 'Andrea Altea' => 'oni.zerone@gmail.com' } 15 | s.source = { :git => 'https://github.com/Oni-zerone/CollectionLayouts.git', :tag => 'StoriesLayout-' + s.version.to_s } 16 | s.social_media_url = 'https://twitter.com/Oni_zerone' 17 | 18 | s.ios.deployment_target = '10.0' 19 | 20 | s.source_files = 'StoriesLayout/Classes/**/*' 21 | s.frameworks = 'UIKit', 'CoreGraphics' 22 | end 23 | -------------------------------------------------------------------------------- /StoriesLayout/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Oni-zerone/CollectionLayouts/faef2a79540f1790916c79bb74edb0eca3689302/StoriesLayout/Assets/.gitkeep -------------------------------------------------------------------------------- /StoriesLayout/Classes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Oni-zerone/CollectionLayouts/faef2a79540f1790916c79bb74edb0eca3689302/StoriesLayout/Classes/.gitkeep -------------------------------------------------------------------------------- /StoriesLayout/Classes/StoriesCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StoriesCollectionViewCell.swift 3 | // StoriesLayout 4 | // 5 | // Created by Andrea Altea on 25/05/2019. 6 | // 7 | 8 | import UIKit 9 | import CoreGraphics 10 | 11 | /** 12 | StoriesCollectionViewCell is a base class that you could use to create your custom cells with the Instagram Stories experience. 13 | */ 14 | open class StoriesCollectionViewCell: UICollectionViewCell { 15 | 16 | public weak var gradientLayer: CAGradientLayer? 17 | 18 | internal func getGradientLayer() -> CAGradientLayer { 19 | 20 | if let gradientLayer = gradientLayer { 21 | return gradientLayer 22 | } 23 | 24 | let gradientLayer = CAGradientLayer() 25 | gradientLayer.opacity = 0.0 26 | gradientLayer.colors = [UIColor.black.cgColor, 27 | UIColor.black.withAlphaComponent(0.2).cgColor] 28 | gradientLayer.actions = ["opacity": NSNull()] 29 | self.layer.addSublayer(gradientLayer) 30 | self.gradientLayer = gradientLayer 31 | return gradientLayer 32 | } 33 | 34 | override open func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) { 35 | super.apply(layoutAttributes) 36 | guard let storiesAttributes = layoutAttributes as? StoriesLayoutAttributes else { return } 37 | self.layer.anchorPoint = storiesAttributes.anchorPoint 38 | guard let gradient = storiesAttributes.gradient else { 39 | self.gradientLayer?.opacity = 0.0 40 | return 41 | } 42 | let gradientLayer = getGradientLayer() 43 | gradientLayer.frame = self.bounds 44 | switch gradient { 45 | case .left(let percent): 46 | gradientLayer.frame = self.bounds 47 | gradientLayer.startPoint = CGPoint(x: 1.0, y: 0.5) 48 | gradientLayer.endPoint = CGPoint(x: 0.0, y: 0.5) 49 | gradientLayer.opacity = percent 50 | case .right(let percent): 51 | gradientLayer.frame = self.bounds 52 | gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5) 53 | gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5) 54 | gradientLayer.opacity = percent 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /StoriesLayout/Classes/StoriesCollectionViewLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StoriesCollectionViewLayout.swift 3 | // StoriesLayout 4 | // 5 | // Created by Andrea Altea on 23/05/2019. 6 | // 7 | 8 | import UIKit 9 | 10 | /** 11 | A concrete layout object that reproduce the look and feel of the Instagram Stories layout. 12 | 13 | The items will be shown in a single row, one by one with the same dimension of the screen. 14 | 15 | Actually there isn't a specific delegate to customize layout behviors of the cells. 16 | */ 17 | public class StoriesCollectionViewLayout: UICollectionViewLayout { 18 | 19 | public var baseTransform: CATransform3D = { 20 | var transform = CATransform3DIdentity 21 | transform.m34 = 1.0 / 1000 22 | return transform 23 | }() 24 | 25 | /** 26 | Defines the contentSize of the collectionView 27 | 28 | Actually StoriesCollectionViewLayout can handle a single section and the cell size is determined as the same of the collection. 29 | */ 30 | override public var collectionViewContentSize: CGSize { 31 | guard let collectionView = collectionView, 32 | let dataSource = collectionView.dataSource else { return .zero } 33 | 34 | return CGSize(width: collectionView.bounds.size.width * CGFloat(dataSource.collectionView(collectionView, numberOfItemsInSection: 0)), 35 | height: collectionView.bounds.size.height) 36 | } 37 | 38 | override public func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { 39 | guard let collectionView = collectionView, 40 | let dataSource = collectionView.dataSource else { return nil } 41 | let rect = CGRect(origin: collectionView.contentOffset, size: collectionView.bounds.size) 42 | 43 | let itemIndex = Int(rect.origin.x == 0 ? 0 : rect.origin.x / rect.width) 44 | var items: [UICollectionViewLayoutAttributes] = [] 45 | if dataSource.collectionView(collectionView, numberOfItemsInSection: 0) > itemIndex, 46 | let firstItem = layoutAttributesForItem(at: IndexPath(item: itemIndex, section: 0)) { 47 | items.append(firstItem) 48 | } 49 | 50 | if let lastBound = items.last?.bounds, 51 | lastBound.width + lastBound.minX >= collectionView.bounds.width + collectionView.contentOffset.x { 52 | return items 53 | } 54 | 55 | if dataSource.collectionView(collectionView, numberOfItemsInSection: 0) > (itemIndex + 1), 56 | let secondItem = layoutAttributesForItem(at: IndexPath(item: itemIndex + 1, section: 0) ) { 57 | secondItem.indexPath = IndexPath(item: itemIndex + 1, section: 0) 58 | secondItem.zIndex = 1000 59 | items.append(secondItem) 60 | } 61 | return items 62 | } 63 | 64 | override public func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { 65 | let attributes = StoriesLayoutAttributes(forCellWith: indexPath) 66 | guard let collection = collectionView else { return nil } 67 | 68 | attributes.frame = CGRect(x: collection.bounds.width * CGFloat(indexPath.item), 69 | y: 0, 70 | width: collection.bounds.width, 71 | height: collection.bounds.height) 72 | 73 | if attributes.frame.minX > collection.contentOffset.x && 74 | attributes.frame.maxX > (collection.contentOffset.x + collection.bounds.width) { 75 | let rightSideOriginalTransform = -90 * CGFloat.pi / 180 76 | var rightAnimationPercentComplete = (collection.contentOffset.x / collection.bounds.width).truncatingRemainder(dividingBy: 1) 77 | rightAnimationPercentComplete = (1 - rightAnimationPercentComplete).truncatingRemainder(dividingBy: 1) 78 | let rotation = CATransform3DRotate(baseTransform, rightSideOriginalTransform * rightAnimationPercentComplete, 0.0, 1.0, 0.0) 79 | let translation = CATransform3DMakeTranslation(attributes.frame.width / -2.0, 0.0, 0.0) 80 | attributes.transform3D = CATransform3DConcat(rotation, translation) 81 | attributes.anchorPoint = CGPoint(x: 0.0, y: 0.5) 82 | attributes.gradient = .left(percent: Float(rightAnimationPercentComplete)) 83 | return attributes 84 | } 85 | 86 | if attributes.frame.minX < collection.contentOffset.x && 87 | attributes.frame.maxX < (collection.contentOffset.x + collection.bounds.width) { 88 | let leftSideOriginalTransform = 90 * CGFloat.pi / 180 89 | let leftAnimationPercentageComplete = (collection.contentOffset.x / collection.bounds.width).truncatingRemainder(dividingBy: 1) 90 | let rotation = CATransform3DRotate(baseTransform, leftSideOriginalTransform * leftAnimationPercentageComplete, 0.0, 1.0, 0.0) 91 | let translation = CATransform3DMakeTranslation(attributes.frame.width / 2.0, 0.0, 0.0) 92 | attributes.transform3D = CATransform3DConcat(rotation, translation) 93 | attributes.anchorPoint = CGPoint(x: 1.0, y: 0.5) 94 | attributes.gradient = .right(percent: Float(leftAnimationPercentageComplete)) 95 | return attributes 96 | } 97 | 98 | attributes.transform3D = CATransform3DIdentity 99 | attributes.anchorPoint = CGPoint(x: 0.5, y: 0.5) 100 | return attributes 101 | } 102 | 103 | override public func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { 104 | return true 105 | } 106 | } 107 | 108 | -------------------------------------------------------------------------------- /StoriesLayout/Classes/StoriesLayoutAttributes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StoriesLayoutAttributes.swift 3 | // StoriesLayout 4 | // 5 | // Created by Andrea Altea on 25/05/2019. 6 | // 7 | 8 | import UIKit 9 | /** 10 | The custom `UICollectionViewLayoutAttributes` subclass used by `StoriesCollectionViewLayout` 11 | */ 12 | public class StoriesLayoutAttributes: UICollectionViewLayoutAttributes { 13 | 14 | /** 15 | The gradient informations used by the `StoriesCollectionViewCell` 16 | */ 17 | public enum Gradient: Equatable { 18 | case left(percent: Float) 19 | case right(percent: Float) 20 | } 21 | 22 | /** 23 | Anchor point is the realtive position used to calculate the rotation transformation of the cell. 24 | 25 | If you won't use the `StoriesCollectionViewCell` as your base cell class remember to override 26 | 27 | `func apply(_ layoutAttributes: UICollectionViewLayoutAttributes)` method to pass the anchorPoint property 28 | to the cell layer. 29 | */ 30 | public var anchorPoint: CGPoint = CGPoint(x: 0.5, y: 0.5) 31 | 32 | /** 33 | Gradient informations to render the `CAGradientLayer` over the `UICollectionViewCell`. 34 | 35 | If you won't use the `StoriesCollectionViewCell` as your base cell class remember to override 36 | 37 | `func apply(_ layoutAttributes: UICollectionViewLayoutAttributes)` method to draw the `CAGradientLayer` if you want to show it. 38 | */ 39 | public var gradient: Gradient? 40 | 41 | override public func copy(with zone: NSZone? = nil) -> Any { 42 | let attribute = super.copy(with: zone) as! StoriesLayoutAttributes 43 | attribute.anchorPoint = self.anchorPoint 44 | attribute.gradient = gradient 45 | return attribute 46 | } 47 | 48 | override public func isEqual(_ object: Any?) -> Bool { 49 | guard let object = object as? StoriesLayoutAttributes, 50 | object.anchorPoint == self.anchorPoint, 51 | object.gradient == self.gradient else { return false } 52 | return super.isEqual(object) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj --------------------------------------------------------------------------------