├── .github ├── ISSUE_TEMPLATE.md └── contributing.md ├── .gitignore ├── .travis.yml ├── Assets └── animation.gif ├── Example ├── Example.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── Example.xccheckout │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── Example.xcscheme │ └── xcuserdata │ │ └── zen.xcuserdatad │ │ └── xcschemes │ │ ├── Example.xcscheme │ │ └── xcschememanagement.plist └── Example │ ├── AppDelegate.swift │ ├── Controllers │ ├── CollectionViewCell.swift │ ├── CollectionViewController.swift │ └── TableViewController.swift │ ├── Example-Bridging-Header.h │ ├── External │ └── UIImage+Autoresize │ │ ├── Classes │ │ ├── UIImage+Autoresize.h │ │ └── UIImage+Autoresize.m │ │ ├── LICENSE │ │ └── README.md │ ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Content Type │ │ ├── Contents.json │ │ ├── contents_films.imageset │ │ │ ├── Contents.json │ │ │ ├── content_films.png │ │ │ ├── content_films@2x.png │ │ │ └── content_films@3x.png │ │ └── contents_music.imageset │ │ │ ├── 2image.png │ │ │ ├── 2image@2x.png │ │ │ ├── 2image@3x.png │ │ │ └── Contents.json │ └── Icon │ │ ├── ic-big-hospitals.imageset │ │ ├── Contents.json │ │ ├── ic-big-hospitals.png │ │ ├── ic-big-hospitals@2x.png │ │ └── ic-big-hospitals@3x.png │ │ ├── ic-big-pharmacies.imageset │ │ ├── Contents.json │ │ ├── ic-big-pharmacies.png │ │ ├── ic-big-pharmacies@2x.png │ │ └── ic-big-pharmacies@3x.png │ │ ├── ic-big-shoes.imageset │ │ ├── Contents.json │ │ ├── ic-big-shoes.png │ │ ├── ic-big-shoes@2x.png │ │ └── ic-big-shoes@3x.png │ │ ├── ic-big-suits.imageset │ │ ├── Contents.json │ │ ├── ic-big-suits.png │ │ ├── ic-big-suits@2x.png │ │ └── ic-big-suits@3x.png │ │ ├── icon_menu.imageset │ │ ├── Contents.json │ │ ├── menu.png │ │ └── menu@2x.png │ │ ├── icon_search.imageset │ │ ├── Contents.json │ │ ├── search.png │ │ └── search@2x.png │ │ ├── menu_icon_0.imageset │ │ ├── Contents.json │ │ ├── ic-small-1.png │ │ ├── ic-small-1@2x.png │ │ └── ic-small-1@3x.png │ │ ├── menu_icon_1.imageset │ │ ├── Contents.json │ │ ├── ic-small-2.png │ │ ├── ic-small-2@2x.png │ │ └── ic-small-2@3x.png │ │ ├── menu_icon_2.imageset │ │ ├── Contents.json │ │ ├── ic-small-3.png │ │ ├── ic-small-3@2x.png │ │ └── ic-small-3@3x.png │ │ ├── menu_icon_3.imageset │ │ ├── Contents.json │ │ ├── ic-small-4.png │ │ ├── ic-small-4@2x.png │ │ └── ic-small-4@3x.png │ │ ├── menu_icon_4.imageset │ │ ├── Contents.json │ │ ├── ic-small-5.png │ │ ├── ic-small-5@2x.png │ │ └── ic-small-5@3x.png │ │ ├── menu_icon_5.imageset │ │ ├── Contents.json │ │ ├── ic-small-6.png │ │ ├── ic-small-6@2x.png │ │ └── ic-small-6@3x.png │ │ └── menu_icon_6.imageset │ │ ├── Contents.json │ │ ├── ic-small-7.png │ │ ├── ic-small-7@2x.png │ │ └── ic-small-7@3x.png │ ├── Info.plist │ ├── Misc │ ├── Animation │ │ └── CircularRevealTransition.swift │ └── Extension │ │ └── CGRect+Extension.swift │ ├── Model │ └── ContentType.swift │ └── Resources │ ├── Fonts │ └── TrendSansOne.otf │ └── UI │ ├── LaunchScreen.xib │ └── Main.storyboard ├── LICENSE ├── Persei.podspec ├── Persei.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── Persei.xccheckout ├── xcshareddata │ └── xcschemes │ │ └── Persei.xcscheme └── xcuserdata │ └── zen.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── Persei ├── Info.plist ├── Persei.h └── Source │ ├── Core │ ├── HeaderShadowView.swift │ └── StickyHeaderView.swift │ ├── Extension │ ├── UIEdgeInsets+Equatable.swift │ └── UIScrollView+Extension.swift │ └── Menu │ ├── MenuCell.swift │ ├── MenuItem.swift │ ├── MenuView.swift │ └── MenuViewDelegate.swift └── README.md /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Report 2 | 3 | > The more information you provide, the faster we can help you. 4 | 5 | ⚠️ Select what you want - **a feature request** or **report a bug**. Please remove the section you aren't interested in. 6 | 7 | ## A feature request 8 | 9 | ### What do you want to add? 10 | 11 | > Please describe what you want to add to the component. 12 | 13 | ### How should it look like? 14 | 15 | > Please add images. 16 | 17 | ## Report a bug 18 | 19 | ### What did you do? 20 | 21 | > Please replace this with what you did. 22 | 23 | ### What did you expect to happen? 24 | 25 | > Please replace this with what you expected to happen. 26 | 27 | ### What happened instead? 28 | 29 | > Please replace this with what happened instead. 30 | 31 | ### Your Environment 32 | 33 | - Version of the component: _insert here_ 34 | - Swift version: _insert here_ 35 | - iOS version: _insert here_ 36 | - Device: _insert here_ 37 | - Xcode version: _insert here_ 38 | - If you use Cocoapods: _run `pod env | pbcopy` and insert here_ 39 | - If you use Carthage: _run `carthage version | pbcopy` and insert here_ 40 | 41 | ### Project that demonstrates the bug 42 | 43 | > Please add a link to a project we can download that reproduces the bug. 44 | -------------------------------------------------------------------------------- /.github/contributing.md: -------------------------------------------------------------------------------- 1 | ## How to contribute to Persei 2 | 3 | #### **Did you find a bug?** 4 | 5 | * **Ensure the bug was not already reported** by searching under [Issues](https://github.com/Yalantis/Persei/issues). 6 | 7 | * If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/Yalantis/Persei/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **example project** demonstrating the expected behavior that is not occurring. 8 | 9 | * Fill appropriate section in issue template and remove the section you aren't interested in. 10 | 11 | #### **Did you write a patch that fixes a bug?** 12 | 13 | * Open a new GitHub pull request with the patch. 14 | 15 | * Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable. 16 | 17 | * Ensure the PR doesn't extend the number of existing issues. 18 | 19 | #### **Did you fix whitespace, format code, or make a purely cosmetic patch?** 20 | 21 | * Changes that are **cosmetic** in nature and **do not add anything substantial** to the stability or functionality of Persei will generally **not be accepted**. 22 | 23 | #### **Did you write patch that extends functionality?** 24 | 25 | * Ensure the functionality you trying to add needed not only for your case. 26 | 27 | #### Each issue will be labeled by it's `type`, `priority` and `status`. 28 | 29 | **Issue types:** 30 | * Bug 31 | * Enhancement 32 | 33 | **These are the available priority labels:** 34 | * Critical 35 | * High 36 | * Medium 37 | * Low 38 | 39 | **Status label will be assigned to your issue to keep the issue tracker easy to follow:** 40 | * Queued (will be reviewed soon) 41 | * Reviewed (assignee has read it) 42 | * Pending (will work on it soon) 43 | * Work in progress (is working on it now) 44 | * On hold 45 | * Invalid (if bug it's not reproducible) 46 | * Need feedback (signal to get people to read and comment or provide help) 47 | 48 | #### **Coding Style** 49 | 50 | * Most importantly, match the existing code style as much as possible. 51 | 52 | #### **Do you have a question?** 53 | 54 | For any usage questions that are not specific to the project itself, please ask on [Stack Overflow](https://stackoverflow.com/). By doing so, you'll be more likely to quickly solve your problem, and you'll allow anyone else with the same question to find the answer. This also allows maintainers to focus on improving the project for others. 55 | 56 | ## Thank you! 57 | 58 | #### [![Yalantis](https://raw.githubusercontent.com/Yalantis/PullToMakeSoup/master/PullToMakeSoupDemo/Resouces/badge_dark.png)](https://Yalantis.com/?utm_source=github) 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.swp 3 | *~.nib 4 | 5 | build/ 6 | 7 | *.pbxuser 8 | !default.pbxuser 9 | *.perspective 10 | *.perspectivev3 11 | !default.perspectivev3 12 | 13 | *.mode1v3 14 | !default.mode1v3 15 | *.mode2v3 16 | !default.mode2v3 17 | 18 | xcuserdata 19 | *.xcuserdata 20 | *.xcuserstate 21 | *.xcworkspace 22 | !default.xcworkspace 23 | 24 | profile 25 | *.moved-aside 26 | DerivedData 27 | 28 | # AppCode 29 | .idea/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: swift 2 | osx_image: xcode9.1 3 | -------------------------------------------------------------------------------- /Assets/animation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Assets/animation.gif -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1D6A3A3B096EF8DCC086DD32 /* CGRect+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D6A3CBE4088EC6CBC44FFD2 /* CGRect+Extension.swift */; }; 11 | 1D6A3EA611666C3A6FC56869 /* CircularRevealTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D6A3ED334BF5B2840F84CD1 /* CircularRevealTransition.swift */; }; 12 | 1D6A3F0B8C4CF9E0E86F6132 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D6A3409797E6A1CF29E9E2E /* AppDelegate.swift */; }; 13 | 44A778262450453B00E46D0B /* CollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A778232450453B00E46D0B /* CollectionViewCell.swift */; }; 14 | 44A778272450453B00E46D0B /* CollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A778242450453B00E46D0B /* CollectionViewController.swift */; }; 15 | 44A778282450453B00E46D0B /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A778252450453B00E46D0B /* TableViewController.swift */; }; 16 | 44A7782A2450456500E46D0B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 44A778292450456500E46D0B /* Main.storyboard */; }; 17 | AD1F5DCD1A7F94F900BDC0CE /* ContentType.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD1F5DCC1A7F94F900BDC0CE /* ContentType.swift */; }; 18 | AD472E321AA9B17F00250E50 /* TrendSansOne.otf in Resources */ = {isa = PBXBuildFile; fileRef = AD472E311AA9B17F00250E50 /* TrendSansOne.otf */; }; 19 | ADD8149A1BF625F3009F1FCA /* UIImage+Autoresize.m in Sources */ = {isa = PBXBuildFile; fileRef = ADD814971BF625F3009F1FCA /* UIImage+Autoresize.m */; }; 20 | ADE9742C1A78F8500040B7C1 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = ADE9742B1A78F8500040B7C1 /* Images.xcassets */; }; 21 | ADE9744F1A78F88C0040B7C1 /* Persei.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ADE9744A1A78F8600040B7C1 /* Persei.framework */; }; 22 | ADE974511A78F8D80040B7C1 /* Persei.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = ADE9744A1A78F8600040B7C1 /* Persei.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 23 | E831A9C41FA111BA00B28873 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = E831A9C21FA111BA00B28873 /* LaunchScreen.xib */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXContainerItemProxy section */ 27 | ADE974491A78F8600040B7C1 /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = ADE974441A78F8600040B7C1 /* Persei.xcodeproj */; 30 | proxyType = 2; 31 | remoteGlobalIDString = ADE973FA1A78F7AA0040B7C1; 32 | remoteInfo = Persei; 33 | }; 34 | ADE9744D1A78F86A0040B7C1 /* PBXContainerItemProxy */ = { 35 | isa = PBXContainerItemProxy; 36 | containerPortal = ADE974441A78F8600040B7C1 /* Persei.xcodeproj */; 37 | proxyType = 1; 38 | remoteGlobalIDString = ADE973F91A78F7AA0040B7C1; 39 | remoteInfo = Persei; 40 | }; 41 | /* End PBXContainerItemProxy section */ 42 | 43 | /* Begin PBXCopyFilesBuildPhase section */ 44 | ADE974501A78F8CB0040B7C1 /* Copy Frameworks */ = { 45 | isa = PBXCopyFilesBuildPhase; 46 | buildActionMask = 2147483647; 47 | dstPath = ""; 48 | dstSubfolderSpec = 10; 49 | files = ( 50 | ADE974511A78F8D80040B7C1 /* Persei.framework in Copy Frameworks */, 51 | ); 52 | name = "Copy Frameworks"; 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXCopyFilesBuildPhase section */ 56 | 57 | /* Begin PBXFileReference section */ 58 | 1D6A3409797E6A1CF29E9E2E /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 59 | 1D6A3CBE4088EC6CBC44FFD2 /* CGRect+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGRect+Extension.swift"; sourceTree = ""; }; 60 | 1D6A3ED334BF5B2840F84CD1 /* CircularRevealTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircularRevealTransition.swift; sourceTree = ""; }; 61 | 44A778232450453B00E46D0B /* CollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewCell.swift; sourceTree = ""; }; 62 | 44A778242450453B00E46D0B /* CollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewController.swift; sourceTree = ""; }; 63 | 44A778252450453B00E46D0B /* TableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = ""; }; 64 | 44A778292450456500E46D0B /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 65 | AD1F5DCC1A7F94F900BDC0CE /* ContentType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentType.swift; sourceTree = ""; }; 66 | AD472E311AA9B17F00250E50 /* TrendSansOne.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = TrendSansOne.otf; sourceTree = ""; }; 67 | AD472E421AA9B71200250E50 /* Example-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Example-Bridging-Header.h"; sourceTree = ""; }; 68 | ADD814961BF625F3009F1FCA /* UIImage+Autoresize.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+Autoresize.h"; sourceTree = ""; }; 69 | ADD814971BF625F3009F1FCA /* UIImage+Autoresize.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+Autoresize.m"; sourceTree = ""; }; 70 | ADE9741F1A78F8500040B7C1 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 71 | ADE974231A78F8500040B7C1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 72 | ADE9742B1A78F8500040B7C1 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = ../Images.xcassets; sourceTree = ""; }; 73 | ADE974441A78F8600040B7C1 /* Persei.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Persei.xcodeproj; path = ../Persei.xcodeproj; sourceTree = ""; }; 74 | E831A9C21FA111BA00B28873 /* LaunchScreen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LaunchScreen.xib; sourceTree = ""; }; 75 | /* End PBXFileReference section */ 76 | 77 | /* Begin PBXFrameworksBuildPhase section */ 78 | ADE9741C1A78F8500040B7C1 /* Frameworks */ = { 79 | isa = PBXFrameworksBuildPhase; 80 | buildActionMask = 2147483647; 81 | files = ( 82 | ADE9744F1A78F88C0040B7C1 /* Persei.framework in Frameworks */, 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | /* End PBXFrameworksBuildPhase section */ 87 | 88 | /* Begin PBXGroup section */ 89 | 0873BC494D5EFB1D46ADCC5A /* Frameworks */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | ); 93 | name = Frameworks; 94 | sourceTree = ""; 95 | }; 96 | 1D6A36F0E53AF23E93A6FF5F /* Controllers */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | 44A778232450453B00E46D0B /* CollectionViewCell.swift */, 100 | 44A778242450453B00E46D0B /* CollectionViewController.swift */, 101 | 44A778252450453B00E46D0B /* TableViewController.swift */, 102 | ); 103 | path = Controllers; 104 | sourceTree = ""; 105 | }; 106 | 1D6A384799617AE602C0694F /* Resources */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | E831A9C01FA1118100B28873 /* UI */, 110 | AD472E301AA9B17F00250E50 /* Fonts */, 111 | ADE9742B1A78F8500040B7C1 /* Images.xcassets */, 112 | ); 113 | path = Resources; 114 | sourceTree = ""; 115 | }; 116 | 1D6A3BDFD5B44767795905CF /* Animation */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | 1D6A3ED334BF5B2840F84CD1 /* CircularRevealTransition.swift */, 120 | ); 121 | path = Animation; 122 | sourceTree = ""; 123 | }; 124 | 1D6A3C50ACE0728E98FBAE36 /* Extension */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | 1D6A3CBE4088EC6CBC44FFD2 /* CGRect+Extension.swift */, 128 | ); 129 | path = Extension; 130 | sourceTree = ""; 131 | }; 132 | 1D6A3C81FE5A4D4F0AB1B026 /* Model */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | AD1F5DCC1A7F94F900BDC0CE /* ContentType.swift */, 136 | ); 137 | path = Model; 138 | sourceTree = ""; 139 | }; 140 | 1D6A3EC651FFFAB8BA7E3459 /* Misc */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | 1D6A3C50ACE0728E98FBAE36 /* Extension */, 144 | 1D6A3BDFD5B44767795905CF /* Animation */, 145 | ); 146 | path = Misc; 147 | sourceTree = ""; 148 | }; 149 | AD472E301AA9B17F00250E50 /* Fonts */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | AD472E311AA9B17F00250E50 /* TrendSansOne.otf */, 153 | ); 154 | path = Fonts; 155 | sourceTree = ""; 156 | }; 157 | ADD814931BF625F3009F1FCA /* External */ = { 158 | isa = PBXGroup; 159 | children = ( 160 | ADD814941BF625F3009F1FCA /* UIImage+Autoresize */, 161 | ); 162 | path = External; 163 | sourceTree = ""; 164 | }; 165 | ADD814941BF625F3009F1FCA /* UIImage+Autoresize */ = { 166 | isa = PBXGroup; 167 | children = ( 168 | ADD814951BF625F3009F1FCA /* Classes */, 169 | ); 170 | path = "UIImage+Autoresize"; 171 | sourceTree = ""; 172 | }; 173 | ADD814951BF625F3009F1FCA /* Classes */ = { 174 | isa = PBXGroup; 175 | children = ( 176 | ADD814961BF625F3009F1FCA /* UIImage+Autoresize.h */, 177 | ADD814971BF625F3009F1FCA /* UIImage+Autoresize.m */, 178 | ); 179 | path = Classes; 180 | sourceTree = ""; 181 | }; 182 | ADE974161A78F8500040B7C1 = { 183 | isa = PBXGroup; 184 | children = ( 185 | ADE974211A78F8500040B7C1 /* Example */, 186 | ADE974201A78F8500040B7C1 /* Products */, 187 | ADE974441A78F8600040B7C1 /* Persei.xcodeproj */, 188 | 0873BC494D5EFB1D46ADCC5A /* Frameworks */, 189 | ); 190 | sourceTree = ""; 191 | }; 192 | ADE974201A78F8500040B7C1 /* Products */ = { 193 | isa = PBXGroup; 194 | children = ( 195 | ADE9741F1A78F8500040B7C1 /* Example.app */, 196 | ); 197 | name = Products; 198 | sourceTree = ""; 199 | }; 200 | ADE974211A78F8500040B7C1 /* Example */ = { 201 | isa = PBXGroup; 202 | children = ( 203 | ADD814931BF625F3009F1FCA /* External */, 204 | ADE974221A78F8500040B7C1 /* Supporting Files */, 205 | 1D6A3C81FE5A4D4F0AB1B026 /* Model */, 206 | 1D6A384799617AE602C0694F /* Resources */, 207 | 1D6A3EC651FFFAB8BA7E3459 /* Misc */, 208 | 1D6A3409797E6A1CF29E9E2E /* AppDelegate.swift */, 209 | 1D6A36F0E53AF23E93A6FF5F /* Controllers */, 210 | ); 211 | path = Example; 212 | sourceTree = ""; 213 | }; 214 | ADE974221A78F8500040B7C1 /* Supporting Files */ = { 215 | isa = PBXGroup; 216 | children = ( 217 | ADE974231A78F8500040B7C1 /* Info.plist */, 218 | AD472E421AA9B71200250E50 /* Example-Bridging-Header.h */, 219 | ); 220 | name = "Supporting Files"; 221 | sourceTree = ""; 222 | }; 223 | ADE974451A78F8600040B7C1 /* Products */ = { 224 | isa = PBXGroup; 225 | children = ( 226 | ADE9744A1A78F8600040B7C1 /* Persei.framework */, 227 | ); 228 | name = Products; 229 | sourceTree = ""; 230 | }; 231 | E831A9C01FA1118100B28873 /* UI */ = { 232 | isa = PBXGroup; 233 | children = ( 234 | 44A778292450456500E46D0B /* Main.storyboard */, 235 | E831A9C21FA111BA00B28873 /* LaunchScreen.xib */, 236 | ); 237 | path = UI; 238 | sourceTree = ""; 239 | }; 240 | /* End PBXGroup section */ 241 | 242 | /* Begin PBXNativeTarget section */ 243 | ADE9741E1A78F8500040B7C1 /* Example */ = { 244 | isa = PBXNativeTarget; 245 | buildConfigurationList = ADE9743E1A78F8500040B7C1 /* Build configuration list for PBXNativeTarget "Example" */; 246 | buildPhases = ( 247 | ADE9741B1A78F8500040B7C1 /* Sources */, 248 | ADE9741C1A78F8500040B7C1 /* Frameworks */, 249 | ADE9741D1A78F8500040B7C1 /* Resources */, 250 | ADE974501A78F8CB0040B7C1 /* Copy Frameworks */, 251 | ); 252 | buildRules = ( 253 | ); 254 | dependencies = ( 255 | ADE9744E1A78F86A0040B7C1 /* PBXTargetDependency */, 256 | ); 257 | name = Example; 258 | productName = Example; 259 | productReference = ADE9741F1A78F8500040B7C1 /* Example.app */; 260 | productType = "com.apple.product-type.application"; 261 | }; 262 | /* End PBXNativeTarget section */ 263 | 264 | /* Begin PBXProject section */ 265 | ADE974171A78F8500040B7C1 /* Project object */ = { 266 | isa = PBXProject; 267 | attributes = { 268 | LastSwiftUpdateCheck = 0710; 269 | LastUpgradeCheck = 1140; 270 | ORGANIZATIONNAME = Yalantis; 271 | TargetAttributes = { 272 | ADE9741E1A78F8500040B7C1 = { 273 | CreatedOnToolsVersion = 6.1.1; 274 | LastSwiftMigration = 1140; 275 | }; 276 | }; 277 | }; 278 | buildConfigurationList = ADE9741A1A78F8500040B7C1 /* Build configuration list for PBXProject "Example" */; 279 | compatibilityVersion = "Xcode 3.2"; 280 | developmentRegion = en; 281 | hasScannedForEncodings = 0; 282 | knownRegions = ( 283 | English, 284 | en, 285 | Base, 286 | ); 287 | mainGroup = ADE974161A78F8500040B7C1; 288 | productRefGroup = ADE974201A78F8500040B7C1 /* Products */; 289 | projectDirPath = ""; 290 | projectReferences = ( 291 | { 292 | ProductGroup = ADE974451A78F8600040B7C1 /* Products */; 293 | ProjectRef = ADE974441A78F8600040B7C1 /* Persei.xcodeproj */; 294 | }, 295 | ); 296 | projectRoot = ""; 297 | targets = ( 298 | ADE9741E1A78F8500040B7C1 /* Example */, 299 | ); 300 | }; 301 | /* End PBXProject section */ 302 | 303 | /* Begin PBXReferenceProxy section */ 304 | ADE9744A1A78F8600040B7C1 /* Persei.framework */ = { 305 | isa = PBXReferenceProxy; 306 | fileType = wrapper.framework; 307 | path = Persei.framework; 308 | remoteRef = ADE974491A78F8600040B7C1 /* PBXContainerItemProxy */; 309 | sourceTree = BUILT_PRODUCTS_DIR; 310 | }; 311 | /* End PBXReferenceProxy section */ 312 | 313 | /* Begin PBXResourcesBuildPhase section */ 314 | ADE9741D1A78F8500040B7C1 /* Resources */ = { 315 | isa = PBXResourcesBuildPhase; 316 | buildActionMask = 2147483647; 317 | files = ( 318 | ADE9742C1A78F8500040B7C1 /* Images.xcassets in Resources */, 319 | 44A7782A2450456500E46D0B /* Main.storyboard in Resources */, 320 | E831A9C41FA111BA00B28873 /* LaunchScreen.xib in Resources */, 321 | AD472E321AA9B17F00250E50 /* TrendSansOne.otf in Resources */, 322 | ); 323 | runOnlyForDeploymentPostprocessing = 0; 324 | }; 325 | /* End PBXResourcesBuildPhase section */ 326 | 327 | /* Begin PBXSourcesBuildPhase section */ 328 | ADE9741B1A78F8500040B7C1 /* Sources */ = { 329 | isa = PBXSourcesBuildPhase; 330 | buildActionMask = 2147483647; 331 | files = ( 332 | AD1F5DCD1A7F94F900BDC0CE /* ContentType.swift in Sources */, 333 | 1D6A3EA611666C3A6FC56869 /* CircularRevealTransition.swift in Sources */, 334 | 1D6A3A3B096EF8DCC086DD32 /* CGRect+Extension.swift in Sources */, 335 | 44A778262450453B00E46D0B /* CollectionViewCell.swift in Sources */, 336 | 44A778272450453B00E46D0B /* CollectionViewController.swift in Sources */, 337 | 1D6A3F0B8C4CF9E0E86F6132 /* AppDelegate.swift in Sources */, 338 | ADD8149A1BF625F3009F1FCA /* UIImage+Autoresize.m in Sources */, 339 | 44A778282450453B00E46D0B /* TableViewController.swift in Sources */, 340 | ); 341 | runOnlyForDeploymentPostprocessing = 0; 342 | }; 343 | /* End PBXSourcesBuildPhase section */ 344 | 345 | /* Begin PBXTargetDependency section */ 346 | ADE9744E1A78F86A0040B7C1 /* PBXTargetDependency */ = { 347 | isa = PBXTargetDependency; 348 | name = Persei; 349 | targetProxy = ADE9744D1A78F86A0040B7C1 /* PBXContainerItemProxy */; 350 | }; 351 | /* End PBXTargetDependency section */ 352 | 353 | /* Begin XCBuildConfiguration section */ 354 | ADE9743C1A78F8500040B7C1 /* Debug */ = { 355 | isa = XCBuildConfiguration; 356 | buildSettings = { 357 | ALWAYS_SEARCH_USER_PATHS = NO; 358 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 359 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 360 | CLANG_CXX_LIBRARY = "libc++"; 361 | CLANG_ENABLE_MODULES = YES; 362 | CLANG_ENABLE_OBJC_ARC = YES; 363 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 364 | CLANG_WARN_BOOL_CONVERSION = YES; 365 | CLANG_WARN_COMMA = YES; 366 | CLANG_WARN_CONSTANT_CONVERSION = YES; 367 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 368 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 369 | CLANG_WARN_EMPTY_BODY = YES; 370 | CLANG_WARN_ENUM_CONVERSION = YES; 371 | CLANG_WARN_INFINITE_RECURSION = YES; 372 | CLANG_WARN_INT_CONVERSION = YES; 373 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 374 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 375 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 376 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 377 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 378 | CLANG_WARN_STRICT_PROTOTYPES = YES; 379 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 380 | CLANG_WARN_UNREACHABLE_CODE = YES; 381 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 382 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 383 | COPY_PHASE_STRIP = NO; 384 | ENABLE_STRICT_OBJC_MSGSEND = YES; 385 | ENABLE_TESTABILITY = YES; 386 | GCC_C_LANGUAGE_STANDARD = gnu99; 387 | GCC_DYNAMIC_NO_PIC = NO; 388 | GCC_NO_COMMON_BLOCKS = YES; 389 | GCC_OPTIMIZATION_LEVEL = 0; 390 | GCC_PREPROCESSOR_DEFINITIONS = ( 391 | "DEBUG=1", 392 | "$(inherited)", 393 | ); 394 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 395 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 396 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 397 | GCC_WARN_UNDECLARED_SELECTOR = YES; 398 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 399 | GCC_WARN_UNUSED_FUNCTION = YES; 400 | GCC_WARN_UNUSED_VARIABLE = YES; 401 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 402 | MTL_ENABLE_DEBUG_INFO = YES; 403 | ONLY_ACTIVE_ARCH = YES; 404 | SDKROOT = iphoneos; 405 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 406 | SWIFT_VERSION = 5.0; 407 | }; 408 | name = Debug; 409 | }; 410 | ADE9743D1A78F8500040B7C1 /* Release */ = { 411 | isa = XCBuildConfiguration; 412 | buildSettings = { 413 | ALWAYS_SEARCH_USER_PATHS = NO; 414 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 415 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 416 | CLANG_CXX_LIBRARY = "libc++"; 417 | CLANG_ENABLE_MODULES = YES; 418 | CLANG_ENABLE_OBJC_ARC = YES; 419 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 420 | CLANG_WARN_BOOL_CONVERSION = YES; 421 | CLANG_WARN_COMMA = YES; 422 | CLANG_WARN_CONSTANT_CONVERSION = YES; 423 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 424 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 425 | CLANG_WARN_EMPTY_BODY = YES; 426 | CLANG_WARN_ENUM_CONVERSION = YES; 427 | CLANG_WARN_INFINITE_RECURSION = YES; 428 | CLANG_WARN_INT_CONVERSION = YES; 429 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 430 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 431 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 432 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 433 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 434 | CLANG_WARN_STRICT_PROTOTYPES = YES; 435 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 436 | CLANG_WARN_UNREACHABLE_CODE = YES; 437 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 438 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 439 | COPY_PHASE_STRIP = YES; 440 | ENABLE_NS_ASSERTIONS = NO; 441 | ENABLE_STRICT_OBJC_MSGSEND = YES; 442 | GCC_C_LANGUAGE_STANDARD = gnu99; 443 | GCC_NO_COMMON_BLOCKS = YES; 444 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 445 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 446 | GCC_WARN_UNDECLARED_SELECTOR = YES; 447 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 448 | GCC_WARN_UNUSED_FUNCTION = YES; 449 | GCC_WARN_UNUSED_VARIABLE = YES; 450 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 451 | MTL_ENABLE_DEBUG_INFO = NO; 452 | SDKROOT = iphoneos; 453 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 454 | SWIFT_VERSION = 5.0; 455 | VALIDATE_PRODUCT = YES; 456 | }; 457 | name = Release; 458 | }; 459 | ADE9743F1A78F8500040B7C1 /* Debug */ = { 460 | isa = XCBuildConfiguration; 461 | buildSettings = { 462 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 463 | CLANG_ENABLE_MODULES = YES; 464 | DEFINES_MODULE = YES; 465 | INFOPLIST_FILE = Example/Info.plist; 466 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 467 | PRODUCT_BUNDLE_IDENTIFIER = "com.yalantis.persei.$(PRODUCT_NAME:rfc1034identifier)"; 468 | PRODUCT_NAME = "$(TARGET_NAME)"; 469 | SWIFT_OBJC_BRIDGING_HEADER = "Example/Example-Bridging-Header.h"; 470 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 471 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 472 | SWIFT_VERSION = 5.0; 473 | }; 474 | name = Debug; 475 | }; 476 | ADE974401A78F8500040B7C1 /* Release */ = { 477 | isa = XCBuildConfiguration; 478 | buildSettings = { 479 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 480 | CLANG_ENABLE_MODULES = YES; 481 | DEFINES_MODULE = YES; 482 | INFOPLIST_FILE = Example/Info.plist; 483 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 484 | PRODUCT_BUNDLE_IDENTIFIER = "com.yalantis.persei.$(PRODUCT_NAME:rfc1034identifier)"; 485 | PRODUCT_NAME = "$(TARGET_NAME)"; 486 | SWIFT_OBJC_BRIDGING_HEADER = "Example/Example-Bridging-Header.h"; 487 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 488 | SWIFT_VERSION = 5.0; 489 | }; 490 | name = Release; 491 | }; 492 | /* End XCBuildConfiguration section */ 493 | 494 | /* Begin XCConfigurationList section */ 495 | ADE9741A1A78F8500040B7C1 /* Build configuration list for PBXProject "Example" */ = { 496 | isa = XCConfigurationList; 497 | buildConfigurations = ( 498 | ADE9743C1A78F8500040B7C1 /* Debug */, 499 | ADE9743D1A78F8500040B7C1 /* Release */, 500 | ); 501 | defaultConfigurationIsVisible = 0; 502 | defaultConfigurationName = Release; 503 | }; 504 | ADE9743E1A78F8500040B7C1 /* Build configuration list for PBXNativeTarget "Example" */ = { 505 | isa = XCConfigurationList; 506 | buildConfigurations = ( 507 | ADE9743F1A78F8500040B7C1 /* Debug */, 508 | ADE974401A78F8500040B7C1 /* Release */, 509 | ); 510 | defaultConfigurationIsVisible = 0; 511 | defaultConfigurationName = Release; 512 | }; 513 | /* End XCConfigurationList section */ 514 | }; 515 | rootObject = ADE974171A78F8500040B7C1 /* Project object */; 516 | } 517 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/xcshareddata/Example.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 3B24D223-AC6C-40F3-A972-3F3FC6874F8C 9 | IDESourceControlProjectName 10 | Example 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 9ABB8A76455D95FFACC41E7493C22AA75CBF94FA 14 | git.yalantis.com:dmitriy.shemet/persei.git 15 | 16 | IDESourceControlProjectPath 17 | Example/Example.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 9ABB8A76455D95FFACC41E7493C22AA75CBF94FA 21 | ../../.. 22 | 23 | IDESourceControlProjectURL 24 | git.yalantis.com:dmitriy.shemet/persei.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | 9ABB8A76455D95FFACC41E7493C22AA75CBF94FA 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 9ABB8A76455D95FFACC41E7493C22AA75CBF94FA 36 | IDESourceControlWCCName 37 | Persei 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/xcuserdata/zen.xcuserdatad/xcschemes/Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/xcuserdata/zen.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Example.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | ADE9741E1A78F8500040B7C1 16 | 17 | primary 18 | 19 | 20 | ADE974331A78F8500040B7C1 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Example/Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // For License please refer to LICENSE file in the root of Persei project 2 | 3 | import UIKit 4 | 5 | @UIApplicationMain 6 | class AppDelegate: UIResponder, UIApplicationDelegate { 7 | 8 | var window: UIWindow? 9 | 10 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 11 | let appearance = UINavigationBar.appearance() 12 | appearance.titleTextAttributes = [ 13 | .font: UIFont(name: "TrendSansOne", size: 20)!, 14 | .foregroundColor: UIColor(red: 44 / 255, green: 49 / 255, blue: 51 / 255, alpha: 1) 15 | ] 16 | 17 | return true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Example/Example/Controllers/CollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionViewCell.swift 3 | // Example 4 | // 5 | // Created by Eugene on 22.04.2020. 6 | // Copyright © 2020 Yalantis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class CollectionViewCell: UICollectionViewCell { 12 | 13 | @IBOutlet weak var imageView: UIImageView! 14 | 15 | } 16 | -------------------------------------------------------------------------------- /Example/Example/Controllers/CollectionViewController.swift: -------------------------------------------------------------------------------- 1 | // For License please refer to LICENSE file in the root of Persei project 2 | 3 | import UIKit 4 | import Persei 5 | 6 | class CollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout { 7 | 8 | fileprivate var menu: MenuView! 9 | 10 | // MARK: - View Lifecycle 11 | override func viewDidLoad() { 12 | super.viewDidLoad() 13 | 14 | loadMenu() 15 | 16 | title = model.description 17 | //imageView.image = model.image 18 | } 19 | 20 | fileprivate func loadMenu() { 21 | menu = { 22 | let menu = MenuView.init() 23 | menu.delegate = self 24 | menu.items = items 25 | 26 | return menu 27 | }() 28 | 29 | collectionView.addSubview(menu) 30 | } 31 | 32 | // MARK: - Items 33 | fileprivate let items = (0..<7).map { 34 | MenuItem(image: UIImage(named: "menu_icon_\($0)")!) 35 | } 36 | 37 | // MARK: - Model 38 | fileprivate var model: ContentType = .films { 39 | didSet { 40 | title = model.description 41 | collectionView.reloadData() 42 | } 43 | } 44 | 45 | // MARK: - Actions 46 | @IBAction fileprivate func switchMenu() { 47 | menu.setRevealed(!menu.revealed, animated: true) 48 | 49 | } 50 | 51 | override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 52 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionCell", for: indexPath) as! CollectionViewCell 53 | cell.imageView.image = model.image 54 | 55 | return cell 56 | } 57 | 58 | override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 59 | return 1 60 | } 61 | 62 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 63 | 64 | return collectionView.frame.size 65 | } 66 | 67 | } 68 | 69 | // MARK: - MenuViewDelegate 70 | 71 | extension CollectionViewController: MenuViewDelegate { 72 | 73 | func menu(_ menu: MenuView, didSelectItemAt index: Int) { 74 | model = model.next() 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Example/Example/Controllers/TableViewController.swift: -------------------------------------------------------------------------------- 1 | // For License please refer to LICENSE file in the root of Persei project 2 | 3 | import UIKit 4 | import Persei 5 | 6 | class TableViewController: UITableViewController { 7 | 8 | @IBOutlet fileprivate weak var imageView: UIImageView! 9 | fileprivate var menu: MenuView! 10 | 11 | // MARK: - View Lifecycle 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | loadMenu() 16 | 17 | title = model.description 18 | imageView.image = model.image 19 | } 20 | 21 | fileprivate func loadMenu() { 22 | menu = { 23 | let menu = MenuView() 24 | menu.delegate = self 25 | menu.items = items 26 | return menu 27 | }() 28 | 29 | tableView.addSubview(menu) 30 | } 31 | 32 | // MARK: - Items 33 | fileprivate let items = (0..<7).map { 34 | MenuItem(image: UIImage(named: "menu_icon_\($0)")!) 35 | } 36 | 37 | // MARK: - Model 38 | fileprivate var model: ContentType = .films { 39 | didSet { 40 | title = model.description 41 | 42 | if isViewLoaded { 43 | let center: CGPoint = { 44 | let itemFrame = menu.frameOfItem(at: menu.selectedIndex!) 45 | let itemCenter = CGPoint(x: itemFrame.midX, y: itemFrame.midY) 46 | var convertedCenter = imageView.convert(itemCenter, from: menu) 47 | convertedCenter.y = 0 48 | 49 | return convertedCenter 50 | }() 51 | 52 | let transition = CircularRevealTransition(layer: imageView.layer, center: center) 53 | transition.start() 54 | 55 | imageView.image = model.image 56 | } 57 | } 58 | } 59 | 60 | // MARK: - Actions 61 | @IBAction fileprivate func switchMenu() { 62 | menu.setRevealed(!menu.revealed, animated: true) 63 | } 64 | } 65 | 66 | // MARK: - MenuViewDelegate 67 | 68 | extension TableViewController: MenuViewDelegate { 69 | 70 | func menu(_ menu: MenuView, didSelectItemAt index: Int) { 71 | model = model.next() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Example/Example/Example-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "UIImage+Autoresize.h" -------------------------------------------------------------------------------- /Example/Example/External/UIImage+Autoresize/Classes/UIImage+Autoresize.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Autoresize.h 3 | // UIImage+Autoresize 4 | // 5 | // Created by kevin delord on 24/04/14. 6 | // Copyright (c) 2014 Kevin Delord. All rights reserved. 7 | // 8 | 9 | #ifndef UIImage_Autoresize_h__ 10 | #define UIImage_Autoresize_h__ 11 | 12 | #import 13 | 14 | /** 15 | * A categorized class of UIImage to add a naming convetion to deal with different image files: 16 | * 17 | * **none** if @1x small old phones 18 | * **@2x** for iPhone 4 19 | * **-568h@2x** for iPhone 5 20 | * **-667h@2x** for iPhone 6 21 | * **@3x** for iPhone 6 Plus 22 | */ 23 | @interface UIImage (Autoresize) 24 | 25 | #pragma mark - UIImage Initializer 26 | 27 | /** 28 | * Method to override the UIImage::imageNamed: method with the retina4ImageNamed: one. 29 | * The new method and its implementation will be executed instead of the default UIImage::imageNamed: 30 | * The user don't need to do anything. 31 | */ 32 | + (void)load; 33 | 34 | /** 35 | * Returns a new UIImage object created from a filename. 36 | * 37 | * @discussion If needed, this method will automatically add the needed image suffix for the current device: 38 | * - "@2x" 39 | * - "-568h@2x" 40 | * - "-667h@2x" 41 | * - "@3x" 42 | * Important: the given filename should NOT contain any size-extension, only a name and its file type. 43 | * 44 | * @param imageName The NSString object representing the filename of the image. 45 | * @return An UIImage created from a given string. 46 | */ 47 | + (UIImage *)dynamicImageNamed:(NSString *)imageName; 48 | 49 | @end 50 | 51 | #endif -------------------------------------------------------------------------------- /Example/Example/External/UIImage+Autoresize/Classes/UIImage+Autoresize.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Autoresize.m 3 | // UIImage+Autoresize 4 | // 5 | // Created by kevin delord on 24/04/14. 6 | // Copyright (c) 2014 Kevin Delord. All rights reserved. 7 | // 8 | 9 | #import "UIImage+Autoresize.h" 10 | #import 11 | 12 | #define __K_DEBUG_LOG_UIIMAGE_AUTORESIZE_ENABLED__ false 13 | 14 | @implementation UIImage (Autoresize) 15 | 16 | #pragma mark - UIImage Initializer 17 | 18 | + (void)load { 19 | static dispatch_once_t onceToken; 20 | dispatch_once(&onceToken, ^{ 21 | Method origImageNamedMethod = class_getClassMethod(self.class, @selector(imageNamed:)); 22 | method_exchangeImplementations(origImageNamedMethod, class_getClassMethod(self.class, @selector(dynamicImageNamed:))); 23 | }); 24 | } 25 | 26 | + (NSString *)verticalExtensionForScale:(CGFloat)scale height:(CGFloat)h width:(CGFloat)w { 27 | 28 | if (__K_DEBUG_LOG_UIIMAGE_AUTORESIZE_ENABLED__) { 29 | NSLog(@"-------------------------------------"); 30 | NSLog(@"h: %f", h); 31 | NSLog(@"w: %f", w); 32 | NSLog(@"scale: %f", scale); 33 | NSLog(@"-------------------------------------"); 34 | } 35 | 36 | // generate the current valid file extension depending on the current device screen size. 37 | NSString *extension = @""; 38 | if (scale == 3.f) { 39 | extension = @"@3x"; // iPhone 6+ 40 | } else if (scale == 2.f && h == 568.0f && w == 320.0f) { 41 | extension = @"-568h@2x"; // iPhone 5, 5S, 5C 42 | } else if (scale == 2.f && h == 667.0f && w == 375.0f) { 43 | extension = @"-667h@2x"; // iPhone 6 44 | } else if (scale == 2.f && h == 480.0f && w == 320.0f) { 45 | extension = @"@2x"; // iPhone 4, 4S 46 | } else if (scale == 1.f && h == 1024.0f && w == 768.0f) { 47 | extension = @"-512h"; // iPad Mini, iPad 2, iPad 1 48 | } else if (scale == 2.f && h == 1024.0f && w == 768.0f) { 49 | extension = @"-1024h@2x"; // iPad Mini 3, iPad Mini 2, iPad Air, iPad Air 2 50 | } 51 | return extension; 52 | } 53 | 54 | + (UIImage *)dynamicImageNamed:(NSString *)imageName { 55 | 56 | // only change the name if no '@2x' or '@3x' are specified 57 | if ([imageName rangeOfString:@"@"].location == NSNotFound) { 58 | 59 | CGFloat h = [UIScreen mainScreen].bounds.size.height; 60 | CGFloat w = [UIScreen mainScreen].bounds.size.width; 61 | CGFloat scale = [UIScreen mainScreen].scale; 62 | 63 | // generate the current valid file extension depending on the current device screen size. 64 | NSString *extension = [self verticalExtensionForScale:scale height:h width:w]; 65 | 66 | // add the extension to the image name 67 | NSRange dot = [imageName rangeOfString:@"."]; 68 | NSMutableString *imageNameMutable = [imageName mutableCopy]; 69 | if (dot.location != NSNotFound) 70 | [imageNameMutable insertString:extension atIndex:dot.location]; 71 | else 72 | [imageNameMutable appendString:extension]; 73 | 74 | // if exist returns the corresponding UIImage 75 | if ([[NSBundle mainBundle] pathForResource:imageNameMutable ofType:@""]) { 76 | return [UIImage dynamicImageNamed:imageNameMutable]; 77 | } 78 | } 79 | // otherwise returns an UIImage with the original filename. 80 | return [UIImage dynamicImageNamed:imageName]; 81 | } 82 | 83 | @end 84 | -------------------------------------------------------------------------------- /Example/Example/External/UIImage+Autoresize/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 kevindelord delord.kevin@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 | -------------------------------------------------------------------------------- /Example/Example/External/UIImage+Autoresize/README.md: -------------------------------------------------------------------------------- 1 | # UIImage+Autoresize 2 | 3 | [![Version](https://img.shields.io/cocoapods/v/UIImage+Autoresize.svg?style=flat)](http://cocoadocs.org/docsets/UIImage+Autoresize) 4 | [![License](https://img.shields.io/cocoapods/l/UIImage+Autoresize.svg?style=flat)](http://cocoadocs.org/docsets/UIImage+Autoresize) 5 | [![Platform](https://img.shields.io/cocoapods/p/UIImage+Autoresize.svg?style=flat)](http://cocoadocs.org/docsets/UIImage+Autoresize) 6 | 7 | ## Usage 8 | 9 | To run the example project, clone the repo, and run `pod install` from the Example directory first. 10 | 11 | The class is also available through this [Gist](https://gist.github.com/kevindelord/fe2e691d06ab745fbb00). 12 | 13 | ## Requirements 14 | 15 | Through the categorised class `UIImage+Autoresize`, a naming convention is applied to deal with different image files. 16 | The following suffixes are expected: 17 | 18 | * **none** for 3GS and earlier 19 | * **@2x** for iPhone 4, 4S 20 | * **-568h@2x** for iPhone 5, 5C, 5S 21 | * **-667h@2x** for iPhone 6 22 | * **@3x** for iPhone 6 Plus 23 | * **-512h** for iPad Mini, iPad 2, iPad 1 24 | * **-1024h@2x** for iPad Mini 3, iPad Mini 2, iPad Air, iPad Air 2 25 | 26 | When this class is integrated into your project, you have nothing else to do. 27 | You can now instantiate an image in your code like this: 28 | 29 | [UIImage imageNamed: @"background.png" ]; 30 | 31 | The code will `automatically` load an image corresponding to the current device. 32 | 33 | ## Documentation 34 | 35 | This pod is also documented on [CocoaDocs](http://cocoadocs.org/docsets/UIImage+Autoresize) 36 | 37 | ## Installation 38 | 39 | UIImage+Autoresize is available through [CocoaPods](http://cocoapods.org). To install 40 | it, simply add the following line to your Podfile: 41 | 42 | pod "UIImage+Autoresize" 43 | 44 | ## TODO: 45 | * landscape logic 46 | * Make it work with Storyboard 47 | 48 | ## Special thanks to 49 | Dennis Rieth for the assets, dennis@ieth.de 50 | 51 | ## Author 52 | 53 | kevindelord, delord.kevin@gmail.com 54 | 55 | ## License 56 | 57 | UIImage+Autoresize is available under the MIT license. See the LICENSE file for more info. 58 | -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "scale" : "1x", 46 | "size" : "1024x1024" 47 | } 48 | ], 49 | "info" : { 50 | "author" : "xcode", 51 | "version" : 1 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Content Type/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Content Type/contents_films.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "content_films.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "content_films@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "content_films@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Content Type/contents_films.imageset/content_films.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Content Type/contents_films.imageset/content_films.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Content Type/contents_films.imageset/content_films@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Content Type/contents_films.imageset/content_films@2x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Content Type/contents_films.imageset/content_films@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Content Type/contents_films.imageset/content_films@3x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Content Type/contents_music.imageset/2image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Content Type/contents_music.imageset/2image.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Content Type/contents_music.imageset/2image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Content Type/contents_music.imageset/2image@2x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Content Type/contents_music.imageset/2image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Content Type/contents_music.imageset/2image@3x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Content Type/contents_music.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "2image.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "2image@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "2image@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/ic-big-hospitals.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "ic-big-hospitals.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "ic-big-hospitals@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "ic-big-hospitals@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/ic-big-hospitals.imageset/ic-big-hospitals.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/ic-big-hospitals.imageset/ic-big-hospitals.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/ic-big-hospitals.imageset/ic-big-hospitals@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/ic-big-hospitals.imageset/ic-big-hospitals@2x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/ic-big-hospitals.imageset/ic-big-hospitals@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/ic-big-hospitals.imageset/ic-big-hospitals@3x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/ic-big-pharmacies.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "ic-big-pharmacies.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "ic-big-pharmacies@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "ic-big-pharmacies@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/ic-big-pharmacies.imageset/ic-big-pharmacies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/ic-big-pharmacies.imageset/ic-big-pharmacies.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/ic-big-pharmacies.imageset/ic-big-pharmacies@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/ic-big-pharmacies.imageset/ic-big-pharmacies@2x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/ic-big-pharmacies.imageset/ic-big-pharmacies@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/ic-big-pharmacies.imageset/ic-big-pharmacies@3x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/ic-big-shoes.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "ic-big-shoes.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "ic-big-shoes@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "ic-big-shoes@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/ic-big-shoes.imageset/ic-big-shoes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/ic-big-shoes.imageset/ic-big-shoes.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/ic-big-shoes.imageset/ic-big-shoes@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/ic-big-shoes.imageset/ic-big-shoes@2x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/ic-big-shoes.imageset/ic-big-shoes@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/ic-big-shoes.imageset/ic-big-shoes@3x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/ic-big-suits.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "ic-big-suits.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "ic-big-suits@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "ic-big-suits@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/ic-big-suits.imageset/ic-big-suits.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/ic-big-suits.imageset/ic-big-suits.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/ic-big-suits.imageset/ic-big-suits@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/ic-big-suits.imageset/ic-big-suits@2x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/ic-big-suits.imageset/ic-big-suits@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/ic-big-suits.imageset/ic-big-suits@3x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/icon_menu.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "menu.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "menu@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/icon_menu.imageset/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/icon_menu.imageset/menu.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/icon_menu.imageset/menu@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/icon_menu.imageset/menu@2x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/icon_search.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "search.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "search@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/icon_search.imageset/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/icon_search.imageset/search.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/icon_search.imageset/search@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/icon_search.imageset/search@2x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/menu_icon_0.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "ic-small-1.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "ic-small-1@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "ic-small-1@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/menu_icon_0.imageset/ic-small-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/menu_icon_0.imageset/ic-small-1.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/menu_icon_0.imageset/ic-small-1@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/menu_icon_0.imageset/ic-small-1@2x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/menu_icon_0.imageset/ic-small-1@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/menu_icon_0.imageset/ic-small-1@3x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/menu_icon_1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "ic-small-2.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "ic-small-2@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "ic-small-2@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/menu_icon_1.imageset/ic-small-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/menu_icon_1.imageset/ic-small-2.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/menu_icon_1.imageset/ic-small-2@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/menu_icon_1.imageset/ic-small-2@2x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/menu_icon_1.imageset/ic-small-2@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/menu_icon_1.imageset/ic-small-2@3x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/menu_icon_2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "ic-small-3.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "ic-small-3@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "ic-small-3@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/menu_icon_2.imageset/ic-small-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/menu_icon_2.imageset/ic-small-3.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/menu_icon_2.imageset/ic-small-3@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/menu_icon_2.imageset/ic-small-3@2x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/menu_icon_2.imageset/ic-small-3@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/menu_icon_2.imageset/ic-small-3@3x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/menu_icon_3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "ic-small-4.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "ic-small-4@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "ic-small-4@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/menu_icon_3.imageset/ic-small-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/menu_icon_3.imageset/ic-small-4.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/menu_icon_3.imageset/ic-small-4@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/menu_icon_3.imageset/ic-small-4@2x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/menu_icon_3.imageset/ic-small-4@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/menu_icon_3.imageset/ic-small-4@3x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/menu_icon_4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "ic-small-5.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "ic-small-5@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "ic-small-5@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/menu_icon_4.imageset/ic-small-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/menu_icon_4.imageset/ic-small-5.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/menu_icon_4.imageset/ic-small-5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/menu_icon_4.imageset/ic-small-5@2x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/menu_icon_4.imageset/ic-small-5@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/menu_icon_4.imageset/ic-small-5@3x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/menu_icon_5.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "ic-small-6.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "ic-small-6@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "ic-small-6@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/menu_icon_5.imageset/ic-small-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/menu_icon_5.imageset/ic-small-6.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/menu_icon_5.imageset/ic-small-6@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/menu_icon_5.imageset/ic-small-6@2x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/menu_icon_5.imageset/ic-small-6@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/menu_icon_5.imageset/ic-small-6@3x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/menu_icon_6.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "ic-small-7.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "ic-small-7@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "ic-small-7@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/menu_icon_6.imageset/ic-small-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/menu_icon_6.imageset/ic-small-7.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/menu_icon_6.imageset/ic-small-7@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/menu_icon_6.imageset/ic-small-7@2x.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/Icon/menu_icon_6.imageset/ic-small-7@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Images.xcassets/Icon/menu_icon_6.imageset/ic-small-7@3x.png -------------------------------------------------------------------------------- /Example/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 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIAppFonts 26 | 27 | TrendSansOne.otf 28 | 29 | UILaunchStoryboardName 30 | LaunchScreen 31 | UIMainStoryboardFile 32 | Main 33 | UIRequiredDeviceCapabilities 34 | 35 | armv7 36 | 37 | UISupportedInterfaceOrientations 38 | 39 | UIInterfaceOrientationPortrait 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Example/Example/Misc/Animation/CircularRevealTransition.swift: -------------------------------------------------------------------------------- 1 | // For License please refer to LICENSE file in the root of Persei project 2 | 3 | import QuartzCore 4 | 5 | class CircularRevealTransition: NSObject { 6 | 7 | var completion: (() -> Void)? 8 | 9 | fileprivate let layer: CALayer 10 | fileprivate let snapshotLayer: CALayer 11 | fileprivate let mask: CAShapeLayer 12 | fileprivate let animation: CABasicAnimation 13 | 14 | // MARK: - Init 15 | init(layer: CALayer, center: CGPoint, startRadius: CGFloat, endRadius: CGFloat) { 16 | let startPath = CGPath(ellipseIn: CGRect(boundingCenter: center, radius: startRadius), transform: nil) 17 | let endPath = CGPath(ellipseIn: CGRect(boundingCenter: center, radius: endRadius), transform: nil) 18 | 19 | self.layer = layer 20 | snapshotLayer = CALayer() 21 | snapshotLayer.contents = layer.contents 22 | 23 | mask = CAShapeLayer() 24 | mask.path = endPath 25 | 26 | animation = CABasicAnimation(keyPath: "path") 27 | animation.duration = 0.6 28 | animation.fromValue = startPath 29 | animation.toValue = endPath 30 | 31 | super.init() 32 | animation.delegate = self 33 | } 34 | 35 | convenience init(layer: CALayer, center: CGPoint) { 36 | let radius: CGFloat = { 37 | let frame = layer.frame 38 | let x = max(center.x, frame.width - center.x) 39 | let y = max(center.y, frame.height - center.y) 40 | return hypot(x, y) 41 | }() 42 | 43 | self.init(layer: layer, center: center, startRadius: 0, endRadius: radius) 44 | } 45 | 46 | func start() { 47 | layer.superlayer!.insertSublayer(snapshotLayer, below: layer) 48 | snapshotLayer.frame = layer.frame 49 | 50 | layer.mask = mask 51 | mask.frame = layer.bounds 52 | 53 | mask.add(animation, forKey: "reveal") 54 | } 55 | } 56 | 57 | extension CircularRevealTransition: CAAnimationDelegate { 58 | 59 | func animationDidStop(_: CAAnimation, finished: Bool) { 60 | layer.mask = nil 61 | snapshotLayer.removeFromSuperlayer() 62 | 63 | completion?() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Example/Example/Misc/Extension/CGRect+Extension.swift: -------------------------------------------------------------------------------- 1 | // For License please refer to LICENSE file in the root of Persei project 2 | 3 | import Foundation 4 | import CoreGraphics 5 | 6 | extension CGRect { 7 | 8 | init(boundingCenter center: CGPoint, radius: CGFloat) { 9 | assert(0 <= radius, "radius must be a positive value") 10 | 11 | self = CGRect(origin: center, size: .zero).insetBy(dx: -radius, dy: -radius) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Example/Example/Model/ContentType.swift: -------------------------------------------------------------------------------- 1 | // For License please refer to LICENSE file in the root of Persei project 2 | 3 | import UIKit 4 | 5 | enum ContentType: String, CustomStringConvertible { 6 | 7 | case music = "contents_music" 8 | case films = "contents_films" 9 | 10 | func next() -> ContentType { 11 | switch self { 12 | case .music: 13 | return .films 14 | case .films: 15 | return .music 16 | } 17 | } 18 | 19 | var image: UIImage { 20 | let image = UIImage(named: rawValue)! 21 | return image 22 | } 23 | 24 | var description: String { 25 | switch self { 26 | case .music: 27 | return "Music" 28 | case .films: 29 | return "Films" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Example/Example/Resources/Fonts/TrendSansOne.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/Persei/287dbc343d9edba506b39a8f01d0582a31daf4f2/Example/Example/Resources/Fonts/TrendSansOne.otf -------------------------------------------------------------------------------- /Example/Example/Resources/UI/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/Example/Resources/UI/Main.storyboard: -------------------------------------------------------------------------------- 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 | 29 | 30 | 31 | 32 | 33 | 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 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Yalantis 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /Persei.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = "Persei" 3 | spec.version = '4.0.0' 4 | spec.swift_version = '5.0' 5 | spec.homepage = "https://github.com/Yalantis/Persei" 6 | spec.summary = "Animated top menu for UITableView / UICollectionView / UIScrollView in Swift!" 7 | spec.screenshot = "https://raw.githubusercontent.com/Yalantis/Persei/master/Assets/animation.gif" 8 | 9 | spec.author = "Yalantis" 10 | spec.license = { :type => "MIT", :file => "LICENSE" } 11 | spec.social_media_url = "https://twitter.com/yalantis" 12 | 13 | spec.platform = :ios, '11.0' 14 | spec.ios.deployment_target = '11.0' 15 | 16 | spec.source = { :git => "https://github.com/Yalantis/Persei.git", :tag => spec.version } 17 | 18 | spec.source_files = 'Persei/Source/**/*.swift' 19 | spec.module_name = 'Persei' 20 | spec.requires_arc = true 21 | end 22 | -------------------------------------------------------------------------------- /Persei.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1D6A31F32FA60B85AFFBFEA6 /* HeaderShadowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D6A3D95E981D3587CE94F93 /* HeaderShadowView.swift */; }; 11 | 1D6A352A94FB0A9C06CAB4B3 /* StickyHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D6A3CCC7431809B0969BB3B /* StickyHeaderView.swift */; }; 12 | 1D6A3C51811AA14A61C7CC65 /* MenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D6A38A07AF8C12F62EDC848 /* MenuView.swift */; }; 13 | 1D6A3DD395290EA071B0E7C1 /* MenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D6A340873B6C8AD9F99F7D7 /* MenuItem.swift */; }; 14 | 1D6A3ED1B00544DC67794CC2 /* UIScrollView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D6A3BC30B40A0A0DE3C9F28 /* UIScrollView+Extension.swift */; }; 15 | 1D6A3F484D3DA3CAD2764B63 /* MenuViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D6A3E4901C9474F565CAEFB /* MenuViewDelegate.swift */; }; 16 | 1D6A3F8BA02B98114BC15192 /* UIEdgeInsets+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D6A35C2524EEA21EAAC2CA3 /* UIEdgeInsets+Equatable.swift */; }; 17 | AD4578B01A7D396800359F4F /* MenuCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD4578AF1A7D396800359F4F /* MenuCell.swift */; }; 18 | ADE974001A78F7AA0040B7C1 /* Persei.h in Headers */ = {isa = PBXBuildFile; fileRef = ADE973FF1A78F7AA0040B7C1 /* Persei.h */; settings = {ATTRIBUTES = (Public, ); }; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | 1D6A340873B6C8AD9F99F7D7 /* MenuItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuItem.swift; sourceTree = ""; }; 23 | 1D6A35C2524EEA21EAAC2CA3 /* UIEdgeInsets+Equatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIEdgeInsets+Equatable.swift"; sourceTree = ""; }; 24 | 1D6A38A07AF8C12F62EDC848 /* MenuView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuView.swift; sourceTree = ""; }; 25 | 1D6A3BC30B40A0A0DE3C9F28 /* UIScrollView+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIScrollView+Extension.swift"; sourceTree = ""; }; 26 | 1D6A3CCC7431809B0969BB3B /* StickyHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickyHeaderView.swift; sourceTree = ""; }; 27 | 1D6A3D95E981D3587CE94F93 /* HeaderShadowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderShadowView.swift; sourceTree = ""; }; 28 | 1D6A3E4901C9474F565CAEFB /* MenuViewDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuViewDelegate.swift; sourceTree = ""; }; 29 | AD4578AF1A7D396800359F4F /* MenuCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuCell.swift; sourceTree = ""; }; 30 | ADE973FA1A78F7AA0040B7C1 /* Persei.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Persei.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | ADE973FE1A78F7AA0040B7C1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 32 | ADE973FF1A78F7AA0040B7C1 /* Persei.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Persei.h; sourceTree = ""; }; 33 | /* End PBXFileReference section */ 34 | 35 | /* Begin PBXFrameworksBuildPhase section */ 36 | ADE973F61A78F7AA0040B7C1 /* Frameworks */ = { 37 | isa = PBXFrameworksBuildPhase; 38 | buildActionMask = 2147483647; 39 | files = ( 40 | ); 41 | runOnlyForDeploymentPostprocessing = 0; 42 | }; 43 | /* End PBXFrameworksBuildPhase section */ 44 | 45 | /* Begin PBXGroup section */ 46 | 1D6A37C2AFE62841C52C1CBD /* Core */ = { 47 | isa = PBXGroup; 48 | children = ( 49 | 1D6A3CCC7431809B0969BB3B /* StickyHeaderView.swift */, 50 | 1D6A3D95E981D3587CE94F93 /* HeaderShadowView.swift */, 51 | ); 52 | path = Core; 53 | sourceTree = ""; 54 | }; 55 | 1D6A383447C1FFA053441EDD /* Extension */ = { 56 | isa = PBXGroup; 57 | children = ( 58 | 1D6A35C2524EEA21EAAC2CA3 /* UIEdgeInsets+Equatable.swift */, 59 | 1D6A3BC30B40A0A0DE3C9F28 /* UIScrollView+Extension.swift */, 60 | ); 61 | path = Extension; 62 | sourceTree = ""; 63 | }; 64 | 1D6A3BF043EABDF432FDD050 /* Menu */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 1D6A38A07AF8C12F62EDC848 /* MenuView.swift */, 68 | 1D6A3E4901C9474F565CAEFB /* MenuViewDelegate.swift */, 69 | AD4578AF1A7D396800359F4F /* MenuCell.swift */, 70 | 1D6A340873B6C8AD9F99F7D7 /* MenuItem.swift */, 71 | ); 72 | path = Menu; 73 | sourceTree = ""; 74 | }; 75 | 1D6A3F7224D6775CF50B54C2 /* Source */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 1D6A37C2AFE62841C52C1CBD /* Core */, 79 | 1D6A383447C1FFA053441EDD /* Extension */, 80 | 1D6A3BF043EABDF432FDD050 /* Menu */, 81 | ); 82 | path = Source; 83 | sourceTree = ""; 84 | }; 85 | ADE973F01A78F7AA0040B7C1 = { 86 | isa = PBXGroup; 87 | children = ( 88 | ADE973FC1A78F7AA0040B7C1 /* Persei */, 89 | ADE973FB1A78F7AA0040B7C1 /* Products */, 90 | ); 91 | sourceTree = ""; 92 | }; 93 | ADE973FB1A78F7AA0040B7C1 /* Products */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | ADE973FA1A78F7AA0040B7C1 /* Persei.framework */, 97 | ); 98 | name = Products; 99 | sourceTree = ""; 100 | }; 101 | ADE973FC1A78F7AA0040B7C1 /* Persei */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | ADE973FF1A78F7AA0040B7C1 /* Persei.h */, 105 | ADE973FD1A78F7AA0040B7C1 /* Supporting Files */, 106 | 1D6A3F7224D6775CF50B54C2 /* Source */, 107 | ); 108 | path = Persei; 109 | sourceTree = ""; 110 | }; 111 | ADE973FD1A78F7AA0040B7C1 /* Supporting Files */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | ADE973FE1A78F7AA0040B7C1 /* Info.plist */, 115 | ); 116 | name = "Supporting Files"; 117 | sourceTree = ""; 118 | }; 119 | /* End PBXGroup section */ 120 | 121 | /* Begin PBXHeadersBuildPhase section */ 122 | ADE973F71A78F7AA0040B7C1 /* Headers */ = { 123 | isa = PBXHeadersBuildPhase; 124 | buildActionMask = 2147483647; 125 | files = ( 126 | ADE974001A78F7AA0040B7C1 /* Persei.h in Headers */, 127 | ); 128 | runOnlyForDeploymentPostprocessing = 0; 129 | }; 130 | /* End PBXHeadersBuildPhase section */ 131 | 132 | /* Begin PBXNativeTarget section */ 133 | ADE973F91A78F7AA0040B7C1 /* Persei */ = { 134 | isa = PBXNativeTarget; 135 | buildConfigurationList = ADE974101A78F7AA0040B7C1 /* Build configuration list for PBXNativeTarget "Persei" */; 136 | buildPhases = ( 137 | ADE973F51A78F7AA0040B7C1 /* Sources */, 138 | ADE973F61A78F7AA0040B7C1 /* Frameworks */, 139 | ADE973F71A78F7AA0040B7C1 /* Headers */, 140 | ADE973F81A78F7AA0040B7C1 /* Resources */, 141 | ); 142 | buildRules = ( 143 | ); 144 | dependencies = ( 145 | ); 146 | name = Persei; 147 | productName = Persei; 148 | productReference = ADE973FA1A78F7AA0040B7C1 /* Persei.framework */; 149 | productType = "com.apple.product-type.framework"; 150 | }; 151 | /* End PBXNativeTarget section */ 152 | 153 | /* Begin PBXProject section */ 154 | ADE973F11A78F7AA0040B7C1 /* Project object */ = { 155 | isa = PBXProject; 156 | attributes = { 157 | LastSwiftMigration = 0710; 158 | LastSwiftUpdateCheck = 0710; 159 | LastUpgradeCheck = 1140; 160 | ORGANIZATIONNAME = Yalantis; 161 | TargetAttributes = { 162 | ADE973F91A78F7AA0040B7C1 = { 163 | CreatedOnToolsVersion = 6.1.1; 164 | LastSwiftMigration = 1140; 165 | }; 166 | }; 167 | }; 168 | buildConfigurationList = ADE973F41A78F7AA0040B7C1 /* Build configuration list for PBXProject "Persei" */; 169 | compatibilityVersion = "Xcode 3.2"; 170 | developmentRegion = en; 171 | hasScannedForEncodings = 0; 172 | knownRegions = ( 173 | en, 174 | Base, 175 | ); 176 | mainGroup = ADE973F01A78F7AA0040B7C1; 177 | productRefGroup = ADE973FB1A78F7AA0040B7C1 /* Products */; 178 | projectDirPath = ""; 179 | projectRoot = ""; 180 | targets = ( 181 | ADE973F91A78F7AA0040B7C1 /* Persei */, 182 | ); 183 | }; 184 | /* End PBXProject section */ 185 | 186 | /* Begin PBXResourcesBuildPhase section */ 187 | ADE973F81A78F7AA0040B7C1 /* Resources */ = { 188 | isa = PBXResourcesBuildPhase; 189 | buildActionMask = 2147483647; 190 | files = ( 191 | ); 192 | runOnlyForDeploymentPostprocessing = 0; 193 | }; 194 | /* End PBXResourcesBuildPhase section */ 195 | 196 | /* Begin PBXSourcesBuildPhase section */ 197 | ADE973F51A78F7AA0040B7C1 /* Sources */ = { 198 | isa = PBXSourcesBuildPhase; 199 | buildActionMask = 2147483647; 200 | files = ( 201 | AD4578B01A7D396800359F4F /* MenuCell.swift in Sources */, 202 | 1D6A352A94FB0A9C06CAB4B3 /* StickyHeaderView.swift in Sources */, 203 | 1D6A3F8BA02B98114BC15192 /* UIEdgeInsets+Equatable.swift in Sources */, 204 | 1D6A3ED1B00544DC67794CC2 /* UIScrollView+Extension.swift in Sources */, 205 | 1D6A3C51811AA14A61C7CC65 /* MenuView.swift in Sources */, 206 | 1D6A3DD395290EA071B0E7C1 /* MenuItem.swift in Sources */, 207 | 1D6A3F484D3DA3CAD2764B63 /* MenuViewDelegate.swift in Sources */, 208 | 1D6A31F32FA60B85AFFBFEA6 /* HeaderShadowView.swift in Sources */, 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | }; 212 | /* End PBXSourcesBuildPhase section */ 213 | 214 | /* Begin XCBuildConfiguration section */ 215 | ADE9740E1A78F7AA0040B7C1 /* Debug */ = { 216 | isa = XCBuildConfiguration; 217 | buildSettings = { 218 | ALWAYS_SEARCH_USER_PATHS = NO; 219 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 220 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 221 | CLANG_CXX_LIBRARY = "libc++"; 222 | CLANG_ENABLE_MODULES = YES; 223 | CLANG_ENABLE_OBJC_ARC = YES; 224 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 225 | CLANG_WARN_BOOL_CONVERSION = YES; 226 | CLANG_WARN_COMMA = YES; 227 | CLANG_WARN_CONSTANT_CONVERSION = YES; 228 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 229 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 230 | CLANG_WARN_EMPTY_BODY = YES; 231 | CLANG_WARN_ENUM_CONVERSION = YES; 232 | CLANG_WARN_INFINITE_RECURSION = YES; 233 | CLANG_WARN_INT_CONVERSION = YES; 234 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 235 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 236 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 237 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 238 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 239 | CLANG_WARN_STRICT_PROTOTYPES = YES; 240 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 241 | CLANG_WARN_UNREACHABLE_CODE = YES; 242 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 243 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 244 | COPY_PHASE_STRIP = NO; 245 | CURRENT_PROJECT_VERSION = 1; 246 | ENABLE_STRICT_OBJC_MSGSEND = YES; 247 | ENABLE_TESTABILITY = YES; 248 | GCC_C_LANGUAGE_STANDARD = gnu99; 249 | GCC_DYNAMIC_NO_PIC = NO; 250 | GCC_NO_COMMON_BLOCKS = YES; 251 | GCC_OPTIMIZATION_LEVEL = 0; 252 | GCC_PREPROCESSOR_DEFINITIONS = ( 253 | "DEBUG=1", 254 | "$(inherited)", 255 | ); 256 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 257 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 258 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 259 | GCC_WARN_UNDECLARED_SELECTOR = YES; 260 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 261 | GCC_WARN_UNUSED_FUNCTION = YES; 262 | GCC_WARN_UNUSED_VARIABLE = YES; 263 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 264 | MTL_ENABLE_DEBUG_INFO = YES; 265 | ONLY_ACTIVE_ARCH = YES; 266 | SDKROOT = iphoneos; 267 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 268 | SWIFT_VERSION = 5.0; 269 | TARGETED_DEVICE_FAMILY = "1,2"; 270 | VERSIONING_SYSTEM = "apple-generic"; 271 | VERSION_INFO_PREFIX = ""; 272 | }; 273 | name = Debug; 274 | }; 275 | ADE9740F1A78F7AA0040B7C1 /* Release */ = { 276 | isa = XCBuildConfiguration; 277 | buildSettings = { 278 | ALWAYS_SEARCH_USER_PATHS = NO; 279 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 280 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 281 | CLANG_CXX_LIBRARY = "libc++"; 282 | CLANG_ENABLE_MODULES = YES; 283 | CLANG_ENABLE_OBJC_ARC = YES; 284 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 285 | CLANG_WARN_BOOL_CONVERSION = YES; 286 | CLANG_WARN_COMMA = YES; 287 | CLANG_WARN_CONSTANT_CONVERSION = YES; 288 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 289 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 290 | CLANG_WARN_EMPTY_BODY = YES; 291 | CLANG_WARN_ENUM_CONVERSION = YES; 292 | CLANG_WARN_INFINITE_RECURSION = YES; 293 | CLANG_WARN_INT_CONVERSION = YES; 294 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 295 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 296 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 297 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 298 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 299 | CLANG_WARN_STRICT_PROTOTYPES = YES; 300 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 301 | CLANG_WARN_UNREACHABLE_CODE = YES; 302 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 303 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 304 | COPY_PHASE_STRIP = YES; 305 | CURRENT_PROJECT_VERSION = 1; 306 | ENABLE_NS_ASSERTIONS = NO; 307 | ENABLE_STRICT_OBJC_MSGSEND = YES; 308 | GCC_C_LANGUAGE_STANDARD = gnu99; 309 | GCC_NO_COMMON_BLOCKS = YES; 310 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 311 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 312 | GCC_WARN_UNDECLARED_SELECTOR = YES; 313 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 314 | GCC_WARN_UNUSED_FUNCTION = YES; 315 | GCC_WARN_UNUSED_VARIABLE = YES; 316 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 317 | MTL_ENABLE_DEBUG_INFO = NO; 318 | SDKROOT = iphoneos; 319 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 320 | SWIFT_VERSION = 5.0; 321 | TARGETED_DEVICE_FAMILY = "1,2"; 322 | VALIDATE_PRODUCT = YES; 323 | VERSIONING_SYSTEM = "apple-generic"; 324 | VERSION_INFO_PREFIX = ""; 325 | }; 326 | name = Release; 327 | }; 328 | ADE974111A78F7AA0040B7C1 /* Debug */ = { 329 | isa = XCBuildConfiguration; 330 | buildSettings = { 331 | CLANG_ENABLE_MODULES = YES; 332 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 333 | DEFINES_MODULE = YES; 334 | DYLIB_COMPATIBILITY_VERSION = 1; 335 | DYLIB_CURRENT_VERSION = 1; 336 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 337 | INFOPLIST_FILE = Persei/Info.plist; 338 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 339 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 340 | PRODUCT_BUNDLE_IDENTIFIER = "com.yalantis.$(PRODUCT_NAME:rfc1034identifier)"; 341 | PRODUCT_NAME = "$(TARGET_NAME)"; 342 | SKIP_INSTALL = YES; 343 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 344 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 345 | SWIFT_VERSION = 5.0; 346 | }; 347 | name = Debug; 348 | }; 349 | ADE974121A78F7AA0040B7C1 /* Release */ = { 350 | isa = XCBuildConfiguration; 351 | buildSettings = { 352 | CLANG_ENABLE_MODULES = YES; 353 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 354 | DEFINES_MODULE = YES; 355 | DYLIB_COMPATIBILITY_VERSION = 1; 356 | DYLIB_CURRENT_VERSION = 1; 357 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 358 | INFOPLIST_FILE = Persei/Info.plist; 359 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 360 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 361 | PRODUCT_BUNDLE_IDENTIFIER = "com.yalantis.$(PRODUCT_NAME:rfc1034identifier)"; 362 | PRODUCT_NAME = "$(TARGET_NAME)"; 363 | SKIP_INSTALL = YES; 364 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 365 | SWIFT_VERSION = 5.0; 366 | }; 367 | name = Release; 368 | }; 369 | /* End XCBuildConfiguration section */ 370 | 371 | /* Begin XCConfigurationList section */ 372 | ADE973F41A78F7AA0040B7C1 /* Build configuration list for PBXProject "Persei" */ = { 373 | isa = XCConfigurationList; 374 | buildConfigurations = ( 375 | ADE9740E1A78F7AA0040B7C1 /* Debug */, 376 | ADE9740F1A78F7AA0040B7C1 /* Release */, 377 | ); 378 | defaultConfigurationIsVisible = 0; 379 | defaultConfigurationName = Release; 380 | }; 381 | ADE974101A78F7AA0040B7C1 /* Build configuration list for PBXNativeTarget "Persei" */ = { 382 | isa = XCConfigurationList; 383 | buildConfigurations = ( 384 | ADE974111A78F7AA0040B7C1 /* Debug */, 385 | ADE974121A78F7AA0040B7C1 /* Release */, 386 | ); 387 | defaultConfigurationIsVisible = 0; 388 | defaultConfigurationName = Release; 389 | }; 390 | /* End XCConfigurationList section */ 391 | }; 392 | rootObject = ADE973F11A78F7AA0040B7C1 /* Project object */; 393 | } 394 | -------------------------------------------------------------------------------- /Persei.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Persei.xcodeproj/project.xcworkspace/xcshareddata/Persei.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | C0911BFD-DA02-4356-BF18-392EF456808F 9 | IDESourceControlProjectName 10 | Persei 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 9ABB8A76455D95FFACC41E7493C22AA75CBF94FA 14 | git.yalantis.com:dmitriy.shemet/persei.git 15 | 16 | IDESourceControlProjectPath 17 | Persei.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 9ABB8A76455D95FFACC41E7493C22AA75CBF94FA 21 | ../.. 22 | 23 | IDESourceControlProjectURL 24 | git.yalantis.com:dmitriy.shemet/persei.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | 9ABB8A76455D95FFACC41E7493C22AA75CBF94FA 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 9ABB8A76455D95FFACC41E7493C22AA75CBF94FA 36 | IDESourceControlWCCName 37 | Persei 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Persei.xcodeproj/xcshareddata/xcschemes/Persei.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 51 | 52 | 53 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 76 | 77 | 83 | 84 | 85 | 86 | 92 | 93 | 99 | 100 | 101 | 102 | 104 | 105 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /Persei.xcodeproj/xcuserdata/zen.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Persei.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | ADE973F91A78F7AA0040B7C1 16 | 17 | primary 18 | 19 | 20 | ADE974041A78F7AA0040B7C1 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Persei/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 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Persei/Persei.h: -------------------------------------------------------------------------------- 1 | // For License please refer to LICENSE file in the root of Persei project 2 | 3 | @import Foundation; 4 | 5 | //! Project version number for Persei. 6 | FOUNDATION_EXPORT double PerseiVersionNumber; 7 | 8 | //! Project version string for Persei. 9 | FOUNDATION_EXPORT const unsigned char PerseiVersionString[]; -------------------------------------------------------------------------------- /Persei/Source/Core/HeaderShadowView.swift: -------------------------------------------------------------------------------- 1 | // For License please refer to LICENSE file in the root of Persei project 2 | 3 | import UIKit 4 | 5 | class HeaderShadowView: UIView { 6 | 7 | override class var layerClass: AnyClass { 8 | return CAGradientLayer.self 9 | } 10 | 11 | override init(frame: CGRect) { 12 | super.init(frame: frame) 13 | 14 | setupLayer() 15 | } 16 | 17 | required init?(coder aDecoder: NSCoder) { 18 | super.init(coder: aDecoder) 19 | 20 | setupLayer() 21 | } 22 | 23 | private func setupLayer() { 24 | if let gradientLayer = layer as? CAGradientLayer { 25 | gradientLayer.colors = [UIColor(white: 0, alpha: 0.5).cgColor, UIColor.clear.cgColor] 26 | gradientLayer.startPoint = CGPoint(x: 0.5, y: 1) 27 | gradientLayer.endPoint = CGPoint(x: 0.5, y: 0) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Persei/Source/Core/StickyHeaderView.swift: -------------------------------------------------------------------------------- 1 | // For License please refer to LICENSE file in the root of Persei project 2 | 3 | import UIKit 4 | 5 | private var ContentOffsetContext = 0 6 | private let DefaultContentHeight: CGFloat = 64 7 | 8 | open class StickyHeaderView: UIView { 9 | 10 | // MARK: - Init 11 | 12 | func commonInit() { 13 | translatesAutoresizingMaskIntoConstraints = false 14 | 15 | addSubview(backgroundImageView) 16 | addSubview(contentContainer) 17 | 18 | contentContainer.addSubview(shadowView) 19 | 20 | clipsToBounds = true 21 | } 22 | 23 | public override init(frame: CGRect) { 24 | super.init(frame: frame) 25 | 26 | commonInit() 27 | } 28 | 29 | public convenience init() { 30 | self.init(frame: CGRect(x: 0, y: 0, width: 320, height: DefaultContentHeight)) 31 | } 32 | 33 | required public init?(coder aDecoder: NSCoder) { 34 | super.init(coder: aDecoder) 35 | commonInit() 36 | } 37 | 38 | // MARK: - View lifecycle 39 | 40 | open override func willMove(toSuperview newSuperview: UIView?) { 41 | super.willMove(toSuperview: newSuperview) 42 | 43 | if newSuperview == nil, let view = superview as? UIScrollView { 44 | view.removeObserver(self, forKeyPath:#keyPath(UIScrollView.contentOffset), context: &ContentOffsetContext) 45 | view.panGestureRecognizer.removeTarget(self, action: #selector(handlePan)) 46 | 47 | if insetsApplied { 48 | removeInsets() 49 | } 50 | } 51 | } 52 | 53 | open override func didMoveToSuperview() { 54 | super.didMoveToSuperview() 55 | 56 | if let view = superview as? UIScrollView { 57 | view.addObserver(self, forKeyPath: #keyPath(UIScrollView.contentOffset), options: [.initial, .new], context: &ContentOffsetContext) 58 | view.panGestureRecognizer.addTarget(self, action: #selector(StickyHeaderView.handlePan)) 59 | view.sendSubviewToBack(self) 60 | 61 | if needRevealed && !insetsApplied { 62 | addInsets() 63 | } else if insetsApplied { 64 | removeInsets() 65 | } 66 | } 67 | } 68 | 69 | private let contentContainer: UIView = { 70 | let view = UIView() 71 | view.layer.anchorPoint = CGPoint(x: 0.5, y: 1) 72 | view.backgroundColor = .clear 73 | 74 | return view 75 | }() 76 | 77 | private let shadowView = HeaderShadowView(frame: .zero) 78 | 79 | @IBOutlet open var contentView: UIView? { 80 | didSet { 81 | oldValue?.removeFromSuperview() 82 | if let view = contentView { 83 | view.frame = contentContainer.bounds 84 | view.autoresizingMask = [.flexibleWidth, .flexibleHeight] 85 | contentContainer.addSubview(view) 86 | contentContainer.sendSubviewToBack(view) 87 | } 88 | } 89 | } 90 | 91 | public enum ContentViewGravity { 92 | case top, center, bottom 93 | } 94 | 95 | /** 96 | Affects on `contentView` sticking position during view stretching: 97 | 98 | - Top: `contentView` sticked to the top position of the view 99 | - Center: `contentView` is aligned to the middle of the streched view 100 | - Bottom: `contentView` sticked to the bottom 101 | 102 | Default value is `Center` 103 | **/ 104 | open var contentViewGravity: ContentViewGravity = .center 105 | 106 | // MARK: - Background Image 107 | private let backgroundImageView = UIImageView() 108 | 109 | @IBInspectable 110 | open var backgroundImage: UIImage? { 111 | didSet { 112 | backgroundImageView.image = backgroundImage 113 | backgroundImageView.isHidden = backgroundImage == nil 114 | } 115 | } 116 | 117 | // MARK: - ScrollView 118 | 119 | private var scrollView: UIScrollView { 120 | guard 121 | let scrollView = superview as? UIScrollView 122 | else { fatalError("superview is not UIScrollView") } 123 | 124 | return scrollView 125 | } 126 | 127 | // MARK: - KVO 128 | 129 | open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 130 | if context == &ContentOffsetContext { 131 | didScroll() 132 | } else { 133 | super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) 134 | } 135 | } 136 | 137 | // MARK: - State 138 | 139 | fileprivate var needRevealed = false 140 | 141 | open var revealed: Bool = false { 142 | didSet { 143 | if oldValue != revealed { 144 | if superview == nil { 145 | needRevealed = revealed 146 | } else if revealed { 147 | addInsets() 148 | } else { 149 | removeInsets() 150 | } 151 | } 152 | } 153 | } 154 | 155 | private func setRevealed(_ revealed: Bool, animated: Bool, adjustContentOffset adjust: Bool) { 156 | if animated { 157 | UIView.animate(withDuration: 0.2, delay: 0, options: .beginFromCurrentState, animations: { 158 | self.revealed = revealed 159 | }, completion: { completed in 160 | if adjust { 161 | UIView.animate(withDuration: 0.2, animations: { 162 | self.scrollView.contentOffset.y = -self.scrollView.effectiveContentInset.top 163 | }) 164 | } 165 | }) 166 | } else { 167 | self.revealed = revealed 168 | 169 | if adjust { 170 | scrollView.contentOffset.y = -scrollView.effectiveContentInset.top 171 | } 172 | } 173 | } 174 | 175 | open func setRevealed(_ revealed: Bool, animated: Bool) { 176 | setRevealed(revealed, animated: animated, adjustContentOffset: true) 177 | } 178 | 179 | private func fractionRevealed() -> CGFloat { 180 | return min(bounds.height / contentHeight, 1) 181 | } 182 | 183 | // MARK: - Applyied Insets 184 | 185 | private var appliedInsets: UIEdgeInsets = .zero 186 | private var insetsApplied: Bool { 187 | return appliedInsets != .zero 188 | } 189 | 190 | private func applyInsets(_ insets: UIEdgeInsets) { 191 | let originalInset = scrollView.effectiveContentInset - appliedInsets 192 | let targetInset = originalInset + insets 193 | 194 | appliedInsets = insets 195 | scrollView.effectiveContentInset = targetInset 196 | } 197 | 198 | private func addInsets() { 199 | assert(!insetsApplied, "Internal inconsistency") 200 | applyInsets(UIEdgeInsets(top: contentHeight, left: 0, bottom: 0, right: 0)) 201 | } 202 | 203 | private func removeInsets() { 204 | assert(insetsApplied, "Internal inconsistency") 205 | applyInsets(.zero) 206 | } 207 | 208 | // MARK: - ContentHeight 209 | 210 | @IBInspectable open var contentHeight: CGFloat = DefaultContentHeight { 211 | didSet { 212 | if superview != nil { 213 | layoutToFit() 214 | } 215 | } 216 | } 217 | 218 | // MARK: - Threshold 219 | 220 | @IBInspectable open var threshold: CGFloat = 0.3 221 | 222 | // MARK: - Content Offset Hanlding 223 | 224 | private func applyContentContainerTransform(_ progress: CGFloat) { 225 | var transform = CATransform3DIdentity 226 | transform.m34 = -1 / 500 227 | 228 | let angle = (1 - progress) * (.pi / 2) 229 | transform = CATransform3DRotate(transform, angle, 1, 0, 0) 230 | 231 | contentContainer.layer.transform = transform 232 | } 233 | 234 | private func didScroll() { 235 | layoutToFit() 236 | layoutIfNeeded() 237 | 238 | let progress = fractionRevealed() 239 | shadowView.alpha = 1 - progress 240 | 241 | applyContentContainerTransform(progress) 242 | } 243 | 244 | @objc private func handlePan(_ recognizer: UIPanGestureRecognizer) { 245 | if recognizer.state == .ended { 246 | let value = scrollView.normalizedContentOffset.y * (revealed ? 1 : -1) 247 | let triggeringValue = contentHeight * threshold 248 | let velocity = recognizer.velocity(in: scrollView).y 249 | 250 | if triggeringValue < value { 251 | let adjust = !revealed || velocity < 0 && -velocity < contentHeight 252 | setRevealed(!revealed, animated: true, adjustContentOffset: adjust) 253 | } else if 0 < bounds.height && bounds.height < contentHeight { 254 | UIView.animate(withDuration: 0.3, animations: { 255 | self.scrollView.contentOffset.y = -self.scrollView.effectiveContentInset.top 256 | }) 257 | } 258 | } 259 | } 260 | 261 | // MARK: - Layout 262 | 263 | open override func layoutSubviews() { 264 | super.layoutSubviews() 265 | 266 | backgroundImageView.frame = bounds 267 | 268 | let containerY: CGFloat 269 | switch contentViewGravity { 270 | case .top: 271 | containerY = min(bounds.height - contentHeight, bounds.minY) 272 | 273 | case .center: 274 | containerY = min(bounds.height - contentHeight, bounds.midY - contentHeight / 2) 275 | 276 | case .bottom: 277 | containerY = bounds.height - contentHeight 278 | } 279 | 280 | contentContainer.frame = CGRect(x: 0, y: containerY, width: bounds.width, height: contentHeight) 281 | // shadow should be visible outside of bounds during rotation 282 | shadowView.frame = contentContainer.bounds.insetBy(dx: -round(contentContainer.bounds.width / 16), dy: 0) 283 | } 284 | 285 | private func layoutToFit() { 286 | let origin = scrollView.contentOffset.y + scrollView.effectiveContentInset.top - appliedInsets.top 287 | frame.origin.y = origin 288 | 289 | sizeToFit() 290 | } 291 | 292 | open override func sizeThatFits(_: CGSize) -> CGSize { 293 | let revealedHeight: CGFloat = appliedInsets.top - scrollView.normalizedContentOffset.y 294 | let collapsedHeight: CGFloat = scrollView.normalizedContentOffset.y * -1 295 | let height: CGFloat = revealed ? revealedHeight : collapsedHeight 296 | let output = CGSize(width: scrollView.bounds.width, height: max(height, 0)) 297 | 298 | return output 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /Persei/Source/Extension/UIEdgeInsets+Equatable.swift: -------------------------------------------------------------------------------- 1 | // For License please refer to LICENSE file in the root of Persei project 2 | 3 | import UIKit 4 | 5 | public func + (lhs: UIEdgeInsets, rhs: UIEdgeInsets) -> UIEdgeInsets { 6 | return UIEdgeInsets( 7 | top: lhs.top + rhs.top, 8 | left: lhs.left + rhs.left, 9 | bottom: lhs.bottom + rhs.bottom, 10 | right: lhs.right + rhs.right 11 | ) 12 | } 13 | 14 | public func - (lhs: UIEdgeInsets, rhs: UIEdgeInsets) -> UIEdgeInsets { 15 | return UIEdgeInsets( 16 | top: lhs.top - rhs.top, 17 | left: lhs.left - rhs.left, 18 | bottom: lhs.bottom - rhs.bottom, 19 | right: lhs.right - rhs.right 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /Persei/Source/Extension/UIScrollView+Extension.swift: -------------------------------------------------------------------------------- 1 | // For License please refer to LICENSE file in the root of Persei project 2 | 3 | import UIKit 4 | 5 | extension UIScrollView { 6 | 7 | /// Content offset that incorporates content offset to align default value to (0, 0) instead of a adjusted one. 8 | var normalizedContentOffset: CGPoint { 9 | get { 10 | let contentOffset = self.contentOffset 11 | let contentInset = self.effectiveContentInset 12 | 13 | let output = CGPoint(x: contentOffset.x + contentInset.left, y: contentOffset.y + contentInset.top) 14 | return output 15 | } 16 | } 17 | 18 | /// Effective content inset used by a scroll view for both iOS 10 / 11. iOS 11 introduced new `contentInsetAdjustmentBehavior` that may 19 | /// include a new `UIView.safeAreaInsets` or not depending on the mode. `effectiveContentInsets` makes hides this details, so you may think of using 20 | /// `UIScrollView.contentInset` iOS 10-like behaviour. 21 | var effectiveContentInset: UIEdgeInsets { 22 | get { 23 | if #available(iOS 11, *) { 24 | return adjustedContentInset 25 | } else { 26 | return contentInset 27 | } 28 | } 29 | 30 | set { 31 | if #available(iOS 11.0, *), contentInsetAdjustmentBehavior != .never { 32 | contentInset = newValue - safeAreaInsets 33 | } else { 34 | contentInset = newValue 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Persei/Source/Menu/MenuCell.swift: -------------------------------------------------------------------------------- 1 | // For License please refer to LICENSE file in the root of Persei project 2 | 3 | import UIKit 4 | 5 | class MenuCell: UICollectionViewCell { 6 | 7 | private let shadowView = UIView() 8 | private let imageView = UIImageView() 9 | private var value: MenuItem! 10 | 11 | // MARK: - Init 12 | private func commonInit() { 13 | backgroundView = UIView() 14 | 15 | if #available(iOS 11.0, *) { 16 | insetsLayoutMarginsFromSafeArea = false 17 | } 18 | 19 | imageView.contentMode = .scaleAspectFit 20 | imageView.translatesAutoresizingMaskIntoConstraints = false 21 | contentView.addSubview(imageView) 22 | 23 | imageView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true 24 | imageView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor).isActive = true 25 | imageView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true 26 | imageView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor).isActive = true 27 | 28 | shadowView.translatesAutoresizingMaskIntoConstraints = false 29 | contentView.addSubview(shadowView) 30 | 31 | shadowView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true 32 | shadowView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true 33 | shadowView.heightAnchor.constraint(equalToConstant: 2).isActive = true 34 | shadowView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true 35 | } 36 | 37 | override init(frame: CGRect) { 38 | super.init(frame: frame) 39 | commonInit() 40 | } 41 | 42 | required init?(coder aDecoder: NSCoder) { 43 | super.init(coder: aDecoder) 44 | commonInit() 45 | } 46 | 47 | // MARK: - Reuse 48 | override func prepareForReuse() { 49 | super.prepareForReuse() 50 | 51 | value = nil 52 | } 53 | 54 | func apply(_ value: MenuItem) { 55 | self.value = value 56 | 57 | imageView.image = value.image 58 | imageView.highlightedImage = value.highlightedImage 59 | shadowView.backgroundColor = value.shadowColor 60 | 61 | updateSelectionVisibility() 62 | } 63 | 64 | // MARK: - Selection 65 | private func updateSelectionVisibility() { 66 | imageView.isHighlighted = isSelected 67 | backgroundView?.backgroundColor = isSelected ? value?.highlightedBackgroundColor : value?.backgroundColor 68 | } 69 | 70 | override var isSelected: Bool { 71 | didSet { 72 | updateSelectionVisibility() 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Persei/Source/Menu/MenuItem.swift: -------------------------------------------------------------------------------- 1 | // For License please refer to LICENSE file in the root of Persei project 2 | 3 | import UIKit 4 | 5 | public struct MenuItem { 6 | 7 | public var image: UIImage 8 | public var highlightedImage: UIImage? 9 | 10 | public var backgroundColor = UIColor(red: 50.0 / 255.0, green: 49.0 / 255.0, blue: 73.0 / 255.0, alpha: 1.0) 11 | public var highlightedBackgroundColor = UIColor(red: 1.0, green: 61.0 / 255.0, blue: 130.0 / 255.0, alpha: 1.0) 12 | 13 | public var shadowColor = UIColor(white: 0.1, alpha: 0.3) 14 | 15 | // MARK: - Init 16 | public init(image: UIImage, highlightedImage: UIImage? = nil) { 17 | self.image = image 18 | self.highlightedImage = highlightedImage 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Persei/Source/Menu/MenuView.swift: -------------------------------------------------------------------------------- 1 | // For License please refer to LICENSE file in the root of Persei project 2 | 3 | import UIKit 4 | 5 | private let CellIdentifier = "MenuCell" 6 | private let DefaultContentHeight: CGFloat = 112.0 7 | 8 | open class MenuView: StickyHeaderView { 9 | 10 | // MARK: - Init 11 | override func commonInit() { 12 | super.commonInit() 13 | 14 | if backgroundColor == nil { 15 | backgroundColor = UIColor(red: 51 / 255, green: 51 / 255, blue: 76 / 255, alpha: 1) 16 | } 17 | 18 | contentHeight = DefaultContentHeight 19 | 20 | updateContentLayout() 21 | } 22 | 23 | // MARK: - FlowLayout 24 | private lazy var collectionLayout: UICollectionViewFlowLayout = { 25 | let layout = UICollectionViewFlowLayout() 26 | layout.scrollDirection = .horizontal 27 | 28 | return layout 29 | }() 30 | 31 | // MARK: - CollectionView 32 | private lazy var collectionView: UICollectionView = { 33 | let view = UICollectionView(frame: .zero, collectionViewLayout: self.collectionLayout) 34 | view.clipsToBounds = false 35 | view.backgroundColor = .clear 36 | view.showsHorizontalScrollIndicator = false 37 | view.register(MenuCell.self, forCellWithReuseIdentifier: CellIdentifier) 38 | 39 | view.delegate = self 40 | view.dataSource = self 41 | 42 | self.contentView = view 43 | 44 | return view 45 | }() 46 | 47 | // MARK: - Delegate 48 | @IBOutlet open weak var delegate: MenuViewDelegate? 49 | 50 | // TODO: remove explicit type declaration when compiler error will be fixed 51 | open var items: [MenuItem] = [] { 52 | didSet { 53 | collectionView.reloadData() 54 | selectedIndex = items.count > 0 ? 0 : nil 55 | } 56 | } 57 | 58 | open var selectedIndex: Int? = 0 { 59 | didSet { 60 | var indexPath: IndexPath? 61 | if let index = selectedIndex { 62 | indexPath = IndexPath(item: index, section: 0) 63 | } 64 | 65 | self.collectionView.selectItem(at: indexPath, 66 | animated: revealed, 67 | scrollPosition: .centeredHorizontally 68 | ) 69 | } 70 | } 71 | 72 | // MARK: - ContentHeight & Layout 73 | open override var contentHeight: CGFloat { 74 | didSet { 75 | updateContentLayout() 76 | } 77 | } 78 | 79 | private func updateContentLayout() { 80 | let inset = ceil(contentHeight / 6.0) 81 | let spacing = floor(inset / 2.0) 82 | 83 | collectionLayout.minimumLineSpacing = spacing 84 | collectionLayout.minimumInteritemSpacing = spacing 85 | collectionView.contentInset = UIEdgeInsets(top: 0.0, left: inset, bottom: 0.0, right: inset) 86 | 87 | collectionLayout.itemSize = CGSize(width: contentHeight - inset * 2, height: contentHeight - inset * 2) 88 | } 89 | 90 | public func frameOfItem(at index: Int) -> CGRect { 91 | let indexPath = IndexPath(item: index, section: 0) 92 | let layoutAttributes = collectionLayout.layoutAttributesForItem(at: indexPath)! 93 | 94 | return convert(layoutAttributes.frame, from: collectionLayout.collectionView) 95 | } 96 | } 97 | 98 | extension MenuView: UICollectionViewDataSource { 99 | 100 | public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 101 | return items.count 102 | } 103 | 104 | public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 105 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CellIdentifier,for: indexPath) as! MenuCell 106 | cell.apply(items[indexPath.item]) 107 | 108 | return cell 109 | } 110 | } 111 | 112 | extension MenuView: UICollectionViewDelegate { 113 | 114 | public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 115 | selectedIndex = indexPath.item 116 | delegate?.menu(self, didSelectItemAt: selectedIndex!) 117 | 118 | UIView.animate(withDuration: 0.2, delay: 0.4, options: [], animations: { 119 | self.revealed = false 120 | }, completion: nil) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Persei/Source/Menu/MenuViewDelegate.swift: -------------------------------------------------------------------------------- 1 | // For License please refer to LICENSE file in the root of Persei project 2 | 3 | import Foundation 4 | 5 | @objc public protocol MenuViewDelegate { 6 | 7 | func menu(_ menu: MenuView, didSelectItemAt index: Int) 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Persei 2 | [![Build Status](https://travis-ci.org/Yalantis/Persei.svg)](https://travis-ci.org/Yalantis/Persei) 3 | [![License](http://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://github.com/Yalantis/Persei/blob/master/LICENSE) 4 | 5 | ![Preview](https://github.com/Yalantis/Persei/blob/master/Assets/animation.gif) 6 | 7 | Animated top menu for UITableView / UICollectionView / UIScrollView written in Swift! 8 | 9 | Made in [Yalantis](https://yalantis.com/?utm_source=github). 10 | 11 | Check this [project on Dribbble](https://dribbble.com/shots/1706861-Top-Menu-Animation?list=users&offset=23) 12 | 13 | Check this [project on Behance](https://www.behance.net/gallery/20411445/Mobile-Animations-Interactions%20) 14 | 15 | ## Supported Swift versions 16 | 17 | | Swift Version | Persei | 18 | |:---:|:---:| 19 | | 1.x | 1.1 | 20 | | 2.x | 2.0 | 21 | | 3.x | 3.0 | 22 | | 4.x | 3.1| 23 | | **5.x** | **4.0** | 24 | 25 | ## Installation 26 | 27 | ### [CocoaPods](http://cocoapods.org) 28 | 29 | ```ruby 30 | use_frameworks! 31 | 32 | pod 'Persei', '~> 4.0' 33 | ``` 34 | 35 | ### [Carthage](http://github.com/Carthage/Carthage) 36 | 37 | ```ruby 38 | github "Yalantis/Persei" ~> 4.0 39 | ``` 40 | 41 | ### Manual Installation 42 | > For application targets that do not support embedded frameworks, such as iOS 7, Persei can be integrated by including source files from the Persei folder directly, optionally wrapping the top-level types into `struct Persei` to simulate a namespace. Yes, this sucks. 43 | 44 | 1. Add Persei as a [submodule](http://git-scm.com/docs/git-submodule) by opening the Terminal, `cd`-ing into your top-level project directory, and entering the command `git submodule add https://github.com/yalantis/Persei.git` 45 | 2. Open the `Persei` folder, and drag `Persei.xcodeproj` into the file navigator of your app project. 46 | 3. In Xcode, navigate to the target configuration window by clicking on the blue project icon, and selecting the application target under the "Targets" heading in the sidebar. 47 | 4. Ensure that the deployment target of `Persei.framework` matches that of the application target. 48 | 5. In the tab bar at the top of that window, open the "Build Phases" panel. 49 | 6. Expand the "Target Dependencies" group, and add `Persei.framework`. 50 | 7. Expand the "Link Binary With Libraries" group, and add `SideMenu.framework` 51 | 8. Click on the `+` button at the top left of the panel and select "New Copy Files Phase". Rename this new phase to "Copy Frameworks", set the "Destination" to "Frameworks", and add `Persei.framework`. 52 | 53 | ## Usage 54 | #### Import `Persei` module 55 | ```swift 56 | import Persei 57 | ``` 58 | 59 | #### Init 60 | ```swift 61 | let menu = MenuView() 62 | tableView.addSubview(menu) 63 | ``` 64 | 65 | #### Configuring items 66 | In order to set items you need to instantiate array of `MenuItem`: 67 | 68 | ```swift 69 | let items = feedModes.map { mode: SomeYourCustomFeedMode -> MenuItem in 70 | return MenuItem(image: mode.image) 71 | } 72 | 73 | menu.items = items 74 | ``` 75 | 76 | #### Handling selection 77 | You can specify selected item manually: 78 | ```swift 79 | menu.selectedIndex = 3 80 | ``` 81 | 82 | Note, that selectedIndex declared as `Int?` and will be `nil` in case of `menu.items = nil`. 83 | 84 | Also, you can implement `MenuViewDelegate` to be notified about selection change: 85 | ```swift 86 | // during init 87 | menu.delegate = self 88 | 89 | // actual implementation 90 | extension FeedViewController: MenuViewDelegate { 91 | func menu(menu: MenuView, didSelectItemAt index: Int) { 92 | dataSource.mode = feedModes[index] // alter mode of dataSource 93 | 94 | tableView.reload() // update tableView 95 | } 96 | } 97 | ``` 98 | 99 | #### Reveal menu manually 100 | Menu can be reveal as a result of button tap: 101 | ```swift 102 | func menuButtonSelected(sender: UIControl) { 103 | menu.revealed = !menu.revealed 104 | 105 | // or animated 106 | menu.setRevealed(true, animated: true) 107 | } 108 | ``` 109 | 110 | #### Content Gravity 111 | Use `contentViewGravity` to control sticking behavior. There are 3 available options: 112 | 113 | - Top: `contentView` sticked to the top position of the view 114 | - Center: `contentView` is aligned to the middle of the streched view 115 | - Bottom: `contentView` sticked to the bottom 116 | 117 | #### Customization 118 | `MenuItem` declares set of attributes, that allow you to customize appearance of items: 119 | ```swift 120 | struct MenuItem { 121 | var image: UIImage // default image 122 | var highlightedImage: UIImage? // image used during selection 123 | 124 | var backgroundColor: UIColor // default background color 125 | var highlightedBackgroundColor: UIColor // background color used during selection 126 | 127 | var shadowColor: UIColor // color of bottom 2px shadow line 128 | } 129 | ``` 130 | 131 | Also you're free to configure background of `MenuView` by utilizing `backgroundColor` or `backgroundImage`. Note, that image should be resizeable: 132 | ```swift 133 | let menu = MenuView() 134 | menu.backgroundImage = UIImage(named: "top_menu_background") 135 | ``` 136 | 137 | #### Advanced customization 138 | - Can I place the UIImageView instead? 139 | - Sure! Just subclass / use `StickyHeaderView` directly. It offers layout, positioning and reveal control. All you have to do is to assign your custom view (animated nian-cat UIImageView) to `contentView`: 140 | 141 | ```swift 142 | let headerView = StickyHeaderView() 143 | let imageView = UIImageView(...) 144 | 145 | headerView.contentView = imageView 146 | ``` 147 | 148 | Obviously, your custom view can have heigh different from default: 149 | ```swift 150 | headerView.contentHeight = image.size.height 151 | ``` 152 | 153 | As well as control distance to trigger open/close of the header: 154 | ```swift 155 | headerView.threshold = 0.5 156 | ``` 157 | Threshold is a float value from 0 to 1, specifies how much user needs to drag header for reveal. 158 | 159 | #### Let us know! 160 | 161 | We’d be really happy if you sent us links to your projects where you use our component. Just send an email to github@yalantis.com And do let us know if you have any questions or suggestion regarding the animation. 162 | 163 | P.S. We’re going to publish more awesomeness wrapped in code and a tutorial on how to make UI for iOS (Android) better than better. Stay tuned! 164 | 165 | ## License 166 | 167 | The MIT License (MIT) 168 | 169 | Copyright © 2017 Yalantis 170 | 171 | Permission is hereby granted, free of charge, to any person obtaining a copy 172 | of this software and associated documentation files (the "Software"), to deal 173 | in the Software without restriction, including without limitation the rights 174 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 175 | copies of the Software, and to permit persons to whom the Software is 176 | furnished to do so, subject to the following conditions: 177 | 178 | The above copyright notice and this permission notice shall be included in 179 | all copies or substantial portions of the Software. 180 | 181 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 182 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 183 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 184 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 185 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 186 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 187 | THE SOFTWARE. 188 | --------------------------------------------------------------------------------