├── .gitignore ├── FlowSlideMenu.podspec ├── FlowSlideMenu.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── FlowSlideMenu ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── LeftVC.swift └── MainVC.swift ├── FlowSlideMenuCore ├── LLFlowCurveProtocol.swift ├── LLFlowCurveView.swift ├── LLFlowLayer.swift ├── LLFlowSlideMenuConfig.swift ├── LLFlowSlideMenuEnum.swift └── LLFlowSlideMenuVC.swift ├── FlowSlideMenuTests ├── FlowSlideMenuTests.swift └── Info.plist ├── FlowSlideMenuUITests ├── FlowSlideMenuUITests.swift └── Info.plist ├── LICENSE ├── README.md └── effect.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | #Pods/ 27 | -------------------------------------------------------------------------------- /FlowSlideMenu.podspec: -------------------------------------------------------------------------------- 1 | #FlowSlideMenu.podspec 2 | Pod::Spec.new do |s| 3 | s.name = "FlowSlideMenu" 4 | s.version = "0.1.8" 5 | s.summary = "A flowing SlideMenu is writen in swift" 6 | 7 | s.homepage = "https://github.com/MatrixHero/FlowSlideMenu" 8 | s.license = 'MIT' 9 | s.author = { "MatrixHero" => "matrixhero211@gmail.com" } 10 | s.platform = :ios, "8.0" 11 | s.ios.deployment_target = "8.0" 12 | s.source = { :git => "https://github.com/MatrixHero/FlowSlideMenu.git", :tag => s.version} 13 | s.source_files = 'FlowSlideMenuCore/*.{swift}' 14 | s.requires_arc = true 15 | end -------------------------------------------------------------------------------- /FlowSlideMenu.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6A8663051BEB59C500EC7AFB /* LLFlowCurveProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A8662FF1BEB59C500EC7AFB /* LLFlowCurveProtocol.swift */; }; 11 | 6A8663061BEB59C500EC7AFB /* LLFlowCurveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A8663001BEB59C500EC7AFB /* LLFlowCurveView.swift */; }; 12 | 6A8663071BEB59C500EC7AFB /* LLFlowLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A8663011BEB59C500EC7AFB /* LLFlowLayer.swift */; }; 13 | 6A8663081BEB59C500EC7AFB /* LLFlowSlideMenuConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A8663021BEB59C500EC7AFB /* LLFlowSlideMenuConfig.swift */; }; 14 | 6A8663091BEB59C500EC7AFB /* LLFlowSlideMenuEnum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A8663031BEB59C500EC7AFB /* LLFlowSlideMenuEnum.swift */; }; 15 | 6A86630A1BEB59C500EC7AFB /* LLFlowSlideMenuVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A8663041BEB59C500EC7AFB /* LLFlowSlideMenuVC.swift */; }; 16 | 6ABD1EF81BE9FF0600FCFDF3 /* MainVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ABD1EF71BE9FF0600FCFDF3 /* MainVC.swift */; }; 17 | 6ADAE7E31BE8F6C200641C89 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ADAE7E21BE8F6C200641C89 /* AppDelegate.swift */; }; 18 | 6ADAE7E81BE8F6C200641C89 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6ADAE7E61BE8F6C200641C89 /* Main.storyboard */; }; 19 | 6ADAE7EA1BE8F6C300641C89 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6ADAE7E91BE8F6C300641C89 /* Assets.xcassets */; }; 20 | 6ADAE7ED1BE8F6C300641C89 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6ADAE7EB1BE8F6C300641C89 /* LaunchScreen.storyboard */; }; 21 | 6ADAE7F81BE8F6C300641C89 /* FlowSlideMenuTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ADAE7F71BE8F6C300641C89 /* FlowSlideMenuTests.swift */; }; 22 | 6ADAE8031BE8F6C300641C89 /* FlowSlideMenuUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ADAE8021BE8F6C300641C89 /* FlowSlideMenuUITests.swift */; }; 23 | 6ADAE8181BE8F72900641C89 /* LeftVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ADAE8171BE8F72900641C89 /* LeftVC.swift */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXContainerItemProxy section */ 27 | 6ADAE7F41BE8F6C300641C89 /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = 6ADAE7D71BE8F6C200641C89 /* Project object */; 30 | proxyType = 1; 31 | remoteGlobalIDString = 6ADAE7DE1BE8F6C200641C89; 32 | remoteInfo = FlowSlideMenu; 33 | }; 34 | 6ADAE7FF1BE8F6C300641C89 /* PBXContainerItemProxy */ = { 35 | isa = PBXContainerItemProxy; 36 | containerPortal = 6ADAE7D71BE8F6C200641C89 /* Project object */; 37 | proxyType = 1; 38 | remoteGlobalIDString = 6ADAE7DE1BE8F6C200641C89; 39 | remoteInfo = FlowSlideMenu; 40 | }; 41 | /* End PBXContainerItemProxy section */ 42 | 43 | /* Begin PBXFileReference section */ 44 | 6A8662FF1BEB59C500EC7AFB /* LLFlowCurveProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LLFlowCurveProtocol.swift; sourceTree = ""; }; 45 | 6A8663001BEB59C500EC7AFB /* LLFlowCurveView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LLFlowCurveView.swift; sourceTree = ""; }; 46 | 6A8663011BEB59C500EC7AFB /* LLFlowLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LLFlowLayer.swift; sourceTree = ""; }; 47 | 6A8663021BEB59C500EC7AFB /* LLFlowSlideMenuConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LLFlowSlideMenuConfig.swift; sourceTree = ""; }; 48 | 6A8663031BEB59C500EC7AFB /* LLFlowSlideMenuEnum.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LLFlowSlideMenuEnum.swift; sourceTree = ""; }; 49 | 6A8663041BEB59C500EC7AFB /* LLFlowSlideMenuVC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LLFlowSlideMenuVC.swift; sourceTree = ""; }; 50 | 6ABD1EF71BE9FF0600FCFDF3 /* MainVC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainVC.swift; sourceTree = ""; }; 51 | 6ADAE7DF1BE8F6C200641C89 /* FlowSlideMenu.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FlowSlideMenu.app; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | 6ADAE7E21BE8F6C200641C89 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 53 | 6ADAE7E71BE8F6C200641C89 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 54 | 6ADAE7E91BE8F6C300641C89 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 55 | 6ADAE7EC1BE8F6C300641C89 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 56 | 6ADAE7EE1BE8F6C300641C89 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 57 | 6ADAE7F31BE8F6C300641C89 /* FlowSlideMenuTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FlowSlideMenuTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | 6ADAE7F71BE8F6C300641C89 /* FlowSlideMenuTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowSlideMenuTests.swift; sourceTree = ""; }; 59 | 6ADAE7F91BE8F6C300641C89 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 60 | 6ADAE7FE1BE8F6C300641C89 /* FlowSlideMenuUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FlowSlideMenuUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 61 | 6ADAE8021BE8F6C300641C89 /* FlowSlideMenuUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowSlideMenuUITests.swift; sourceTree = ""; }; 62 | 6ADAE8041BE8F6C300641C89 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 63 | 6ADAE8171BE8F72900641C89 /* LeftVC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LeftVC.swift; sourceTree = ""; }; 64 | /* End PBXFileReference section */ 65 | 66 | /* Begin PBXFrameworksBuildPhase section */ 67 | 6ADAE7DC1BE8F6C200641C89 /* Frameworks */ = { 68 | isa = PBXFrameworksBuildPhase; 69 | buildActionMask = 2147483647; 70 | files = ( 71 | ); 72 | runOnlyForDeploymentPostprocessing = 0; 73 | }; 74 | 6ADAE7F01BE8F6C300641C89 /* Frameworks */ = { 75 | isa = PBXFrameworksBuildPhase; 76 | buildActionMask = 2147483647; 77 | files = ( 78 | ); 79 | runOnlyForDeploymentPostprocessing = 0; 80 | }; 81 | 6ADAE7FB1BE8F6C300641C89 /* Frameworks */ = { 82 | isa = PBXFrameworksBuildPhase; 83 | buildActionMask = 2147483647; 84 | files = ( 85 | ); 86 | runOnlyForDeploymentPostprocessing = 0; 87 | }; 88 | /* End PBXFrameworksBuildPhase section */ 89 | 90 | /* Begin PBXGroup section */ 91 | 6A8662FE1BEB59C500EC7AFB /* FlowSlideMenuCore */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 6A8662FF1BEB59C500EC7AFB /* LLFlowCurveProtocol.swift */, 95 | 6A8663001BEB59C500EC7AFB /* LLFlowCurveView.swift */, 96 | 6A8663011BEB59C500EC7AFB /* LLFlowLayer.swift */, 97 | 6A8663021BEB59C500EC7AFB /* LLFlowSlideMenuConfig.swift */, 98 | 6A8663031BEB59C500EC7AFB /* LLFlowSlideMenuEnum.swift */, 99 | 6A8663041BEB59C500EC7AFB /* LLFlowSlideMenuVC.swift */, 100 | ); 101 | path = FlowSlideMenuCore; 102 | sourceTree = ""; 103 | }; 104 | 6ADAE7D61BE8F6C200641C89 = { 105 | isa = PBXGroup; 106 | children = ( 107 | 6A8662FE1BEB59C500EC7AFB /* FlowSlideMenuCore */, 108 | 6ADAE7E11BE8F6C200641C89 /* FlowSlideMenu */, 109 | 6ADAE8011BE8F6C300641C89 /* FlowSlideMenuUITests */, 110 | 6ADAE7E01BE8F6C200641C89 /* Products */, 111 | ); 112 | sourceTree = ""; 113 | }; 114 | 6ADAE7E01BE8F6C200641C89 /* Products */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | 6ADAE7DF1BE8F6C200641C89 /* FlowSlideMenu.app */, 118 | 6ADAE7F31BE8F6C300641C89 /* FlowSlideMenuTests.xctest */, 119 | 6ADAE7FE1BE8F6C300641C89 /* FlowSlideMenuUITests.xctest */, 120 | ); 121 | name = Products; 122 | sourceTree = ""; 123 | }; 124 | 6ADAE7E11BE8F6C200641C89 /* FlowSlideMenu */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | 6ADAE7E21BE8F6C200641C89 /* AppDelegate.swift */, 128 | 6ADAE8171BE8F72900641C89 /* LeftVC.swift */, 129 | 6ABD1EF71BE9FF0600FCFDF3 /* MainVC.swift */, 130 | 6ADAE7E61BE8F6C200641C89 /* Main.storyboard */, 131 | 6ADAE7E91BE8F6C300641C89 /* Assets.xcassets */, 132 | 6ADAE7EB1BE8F6C300641C89 /* LaunchScreen.storyboard */, 133 | 6ADAE7EE1BE8F6C300641C89 /* Info.plist */, 134 | ); 135 | path = FlowSlideMenu; 136 | sourceTree = ""; 137 | }; 138 | 6ADAE7F61BE8F6C300641C89 /* FlowSlideMenuTests */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | 6ADAE7F71BE8F6C300641C89 /* FlowSlideMenuTests.swift */, 142 | 6ADAE7F91BE8F6C300641C89 /* Info.plist */, 143 | ); 144 | name = FlowSlideMenuTests; 145 | path = ../FlowSlideMenuTests; 146 | sourceTree = ""; 147 | }; 148 | 6ADAE8011BE8F6C300641C89 /* FlowSlideMenuUITests */ = { 149 | isa = PBXGroup; 150 | children = ( 151 | 6ADAE7F61BE8F6C300641C89 /* FlowSlideMenuTests */, 152 | 6ADAE8021BE8F6C300641C89 /* FlowSlideMenuUITests.swift */, 153 | 6ADAE8041BE8F6C300641C89 /* Info.plist */, 154 | ); 155 | path = FlowSlideMenuUITests; 156 | sourceTree = ""; 157 | }; 158 | /* End PBXGroup section */ 159 | 160 | /* Begin PBXNativeTarget section */ 161 | 6ADAE7DE1BE8F6C200641C89 /* FlowSlideMenu */ = { 162 | isa = PBXNativeTarget; 163 | buildConfigurationList = 6ADAE8071BE8F6C300641C89 /* Build configuration list for PBXNativeTarget "FlowSlideMenu" */; 164 | buildPhases = ( 165 | 6ADAE7DB1BE8F6C200641C89 /* Sources */, 166 | 6ADAE7DC1BE8F6C200641C89 /* Frameworks */, 167 | 6ADAE7DD1BE8F6C200641C89 /* Resources */, 168 | ); 169 | buildRules = ( 170 | ); 171 | dependencies = ( 172 | ); 173 | name = FlowSlideMenu; 174 | productName = FlowSlideMenu; 175 | productReference = 6ADAE7DF1BE8F6C200641C89 /* FlowSlideMenu.app */; 176 | productType = "com.apple.product-type.application"; 177 | }; 178 | 6ADAE7F21BE8F6C300641C89 /* FlowSlideMenuTests */ = { 179 | isa = PBXNativeTarget; 180 | buildConfigurationList = 6ADAE80A1BE8F6C300641C89 /* Build configuration list for PBXNativeTarget "FlowSlideMenuTests" */; 181 | buildPhases = ( 182 | 6ADAE7EF1BE8F6C300641C89 /* Sources */, 183 | 6ADAE7F01BE8F6C300641C89 /* Frameworks */, 184 | 6ADAE7F11BE8F6C300641C89 /* Resources */, 185 | ); 186 | buildRules = ( 187 | ); 188 | dependencies = ( 189 | 6ADAE7F51BE8F6C300641C89 /* PBXTargetDependency */, 190 | ); 191 | name = FlowSlideMenuTests; 192 | productName = FlowSlideMenuTests; 193 | productReference = 6ADAE7F31BE8F6C300641C89 /* FlowSlideMenuTests.xctest */; 194 | productType = "com.apple.product-type.bundle.unit-test"; 195 | }; 196 | 6ADAE7FD1BE8F6C300641C89 /* FlowSlideMenuUITests */ = { 197 | isa = PBXNativeTarget; 198 | buildConfigurationList = 6ADAE80D1BE8F6C300641C89 /* Build configuration list for PBXNativeTarget "FlowSlideMenuUITests" */; 199 | buildPhases = ( 200 | 6ADAE7FA1BE8F6C300641C89 /* Sources */, 201 | 6ADAE7FB1BE8F6C300641C89 /* Frameworks */, 202 | 6ADAE7FC1BE8F6C300641C89 /* Resources */, 203 | ); 204 | buildRules = ( 205 | ); 206 | dependencies = ( 207 | 6ADAE8001BE8F6C300641C89 /* PBXTargetDependency */, 208 | ); 209 | name = FlowSlideMenuUITests; 210 | productName = FlowSlideMenuUITests; 211 | productReference = 6ADAE7FE1BE8F6C300641C89 /* FlowSlideMenuUITests.xctest */; 212 | productType = "com.apple.product-type.bundle.ui-testing"; 213 | }; 214 | /* End PBXNativeTarget section */ 215 | 216 | /* Begin PBXProject section */ 217 | 6ADAE7D71BE8F6C200641C89 /* Project object */ = { 218 | isa = PBXProject; 219 | attributes = { 220 | LastSwiftUpdateCheck = 0710; 221 | LastUpgradeCheck = 0710; 222 | ORGANIZATIONNAME = LL; 223 | TargetAttributes = { 224 | 6ADAE7DE1BE8F6C200641C89 = { 225 | CreatedOnToolsVersion = 7.1; 226 | DevelopmentTeam = E485GNNC3F; 227 | }; 228 | 6ADAE7F21BE8F6C300641C89 = { 229 | CreatedOnToolsVersion = 7.1; 230 | TestTargetID = 6ADAE7DE1BE8F6C200641C89; 231 | }; 232 | 6ADAE7FD1BE8F6C300641C89 = { 233 | CreatedOnToolsVersion = 7.1; 234 | TestTargetID = 6ADAE7DE1BE8F6C200641C89; 235 | }; 236 | }; 237 | }; 238 | buildConfigurationList = 6ADAE7DA1BE8F6C200641C89 /* Build configuration list for PBXProject "FlowSlideMenu" */; 239 | compatibilityVersion = "Xcode 3.2"; 240 | developmentRegion = English; 241 | hasScannedForEncodings = 0; 242 | knownRegions = ( 243 | en, 244 | Base, 245 | ); 246 | mainGroup = 6ADAE7D61BE8F6C200641C89; 247 | productRefGroup = 6ADAE7E01BE8F6C200641C89 /* Products */; 248 | projectDirPath = ""; 249 | projectRoot = ""; 250 | targets = ( 251 | 6ADAE7DE1BE8F6C200641C89 /* FlowSlideMenu */, 252 | 6ADAE7F21BE8F6C300641C89 /* FlowSlideMenuTests */, 253 | 6ADAE7FD1BE8F6C300641C89 /* FlowSlideMenuUITests */, 254 | ); 255 | }; 256 | /* End PBXProject section */ 257 | 258 | /* Begin PBXResourcesBuildPhase section */ 259 | 6ADAE7DD1BE8F6C200641C89 /* Resources */ = { 260 | isa = PBXResourcesBuildPhase; 261 | buildActionMask = 2147483647; 262 | files = ( 263 | 6ADAE7ED1BE8F6C300641C89 /* LaunchScreen.storyboard in Resources */, 264 | 6ADAE7EA1BE8F6C300641C89 /* Assets.xcassets in Resources */, 265 | 6ADAE7E81BE8F6C200641C89 /* Main.storyboard in Resources */, 266 | ); 267 | runOnlyForDeploymentPostprocessing = 0; 268 | }; 269 | 6ADAE7F11BE8F6C300641C89 /* Resources */ = { 270 | isa = PBXResourcesBuildPhase; 271 | buildActionMask = 2147483647; 272 | files = ( 273 | ); 274 | runOnlyForDeploymentPostprocessing = 0; 275 | }; 276 | 6ADAE7FC1BE8F6C300641C89 /* Resources */ = { 277 | isa = PBXResourcesBuildPhase; 278 | buildActionMask = 2147483647; 279 | files = ( 280 | ); 281 | runOnlyForDeploymentPostprocessing = 0; 282 | }; 283 | /* End PBXResourcesBuildPhase section */ 284 | 285 | /* Begin PBXSourcesBuildPhase section */ 286 | 6ADAE7DB1BE8F6C200641C89 /* Sources */ = { 287 | isa = PBXSourcesBuildPhase; 288 | buildActionMask = 2147483647; 289 | files = ( 290 | 6A8663081BEB59C500EC7AFB /* LLFlowSlideMenuConfig.swift in Sources */, 291 | 6ABD1EF81BE9FF0600FCFDF3 /* MainVC.swift in Sources */, 292 | 6A8663071BEB59C500EC7AFB /* LLFlowLayer.swift in Sources */, 293 | 6ADAE8181BE8F72900641C89 /* LeftVC.swift in Sources */, 294 | 6ADAE7E31BE8F6C200641C89 /* AppDelegate.swift in Sources */, 295 | 6A86630A1BEB59C500EC7AFB /* LLFlowSlideMenuVC.swift in Sources */, 296 | 6A8663051BEB59C500EC7AFB /* LLFlowCurveProtocol.swift in Sources */, 297 | 6A8663061BEB59C500EC7AFB /* LLFlowCurveView.swift in Sources */, 298 | 6A8663091BEB59C500EC7AFB /* LLFlowSlideMenuEnum.swift in Sources */, 299 | ); 300 | runOnlyForDeploymentPostprocessing = 0; 301 | }; 302 | 6ADAE7EF1BE8F6C300641C89 /* Sources */ = { 303 | isa = PBXSourcesBuildPhase; 304 | buildActionMask = 2147483647; 305 | files = ( 306 | 6ADAE7F81BE8F6C300641C89 /* FlowSlideMenuTests.swift in Sources */, 307 | ); 308 | runOnlyForDeploymentPostprocessing = 0; 309 | }; 310 | 6ADAE7FA1BE8F6C300641C89 /* Sources */ = { 311 | isa = PBXSourcesBuildPhase; 312 | buildActionMask = 2147483647; 313 | files = ( 314 | 6ADAE8031BE8F6C300641C89 /* FlowSlideMenuUITests.swift in Sources */, 315 | ); 316 | runOnlyForDeploymentPostprocessing = 0; 317 | }; 318 | /* End PBXSourcesBuildPhase section */ 319 | 320 | /* Begin PBXTargetDependency section */ 321 | 6ADAE7F51BE8F6C300641C89 /* PBXTargetDependency */ = { 322 | isa = PBXTargetDependency; 323 | target = 6ADAE7DE1BE8F6C200641C89 /* FlowSlideMenu */; 324 | targetProxy = 6ADAE7F41BE8F6C300641C89 /* PBXContainerItemProxy */; 325 | }; 326 | 6ADAE8001BE8F6C300641C89 /* PBXTargetDependency */ = { 327 | isa = PBXTargetDependency; 328 | target = 6ADAE7DE1BE8F6C200641C89 /* FlowSlideMenu */; 329 | targetProxy = 6ADAE7FF1BE8F6C300641C89 /* PBXContainerItemProxy */; 330 | }; 331 | /* End PBXTargetDependency section */ 332 | 333 | /* Begin PBXVariantGroup section */ 334 | 6ADAE7E61BE8F6C200641C89 /* Main.storyboard */ = { 335 | isa = PBXVariantGroup; 336 | children = ( 337 | 6ADAE7E71BE8F6C200641C89 /* Base */, 338 | ); 339 | name = Main.storyboard; 340 | sourceTree = ""; 341 | }; 342 | 6ADAE7EB1BE8F6C300641C89 /* LaunchScreen.storyboard */ = { 343 | isa = PBXVariantGroup; 344 | children = ( 345 | 6ADAE7EC1BE8F6C300641C89 /* Base */, 346 | ); 347 | name = LaunchScreen.storyboard; 348 | sourceTree = ""; 349 | }; 350 | /* End PBXVariantGroup section */ 351 | 352 | /* Begin XCBuildConfiguration section */ 353 | 6ADAE8051BE8F6C300641C89 /* Debug */ = { 354 | isa = XCBuildConfiguration; 355 | buildSettings = { 356 | ALWAYS_SEARCH_USER_PATHS = NO; 357 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 358 | CLANG_CXX_LIBRARY = "libc++"; 359 | CLANG_ENABLE_MODULES = YES; 360 | CLANG_ENABLE_OBJC_ARC = YES; 361 | CLANG_WARN_BOOL_CONVERSION = YES; 362 | CLANG_WARN_CONSTANT_CONVERSION = YES; 363 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 364 | CLANG_WARN_EMPTY_BODY = YES; 365 | CLANG_WARN_ENUM_CONVERSION = YES; 366 | CLANG_WARN_INT_CONVERSION = YES; 367 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 368 | CLANG_WARN_UNREACHABLE_CODE = YES; 369 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 370 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 371 | COPY_PHASE_STRIP = NO; 372 | DEBUG_INFORMATION_FORMAT = dwarf; 373 | ENABLE_STRICT_OBJC_MSGSEND = YES; 374 | ENABLE_TESTABILITY = YES; 375 | GCC_C_LANGUAGE_STANDARD = gnu99; 376 | GCC_DYNAMIC_NO_PIC = NO; 377 | GCC_NO_COMMON_BLOCKS = YES; 378 | GCC_OPTIMIZATION_LEVEL = 0; 379 | GCC_PREPROCESSOR_DEFINITIONS = ( 380 | "DEBUG=1", 381 | "$(inherited)", 382 | ); 383 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 384 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 385 | GCC_WARN_UNDECLARED_SELECTOR = YES; 386 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 387 | GCC_WARN_UNUSED_FUNCTION = YES; 388 | GCC_WARN_UNUSED_VARIABLE = YES; 389 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 390 | MTL_ENABLE_DEBUG_INFO = YES; 391 | ONLY_ACTIVE_ARCH = YES; 392 | SDKROOT = iphoneos; 393 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 394 | }; 395 | name = Debug; 396 | }; 397 | 6ADAE8061BE8F6C300641C89 /* Release */ = { 398 | isa = XCBuildConfiguration; 399 | buildSettings = { 400 | ALWAYS_SEARCH_USER_PATHS = NO; 401 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 402 | CLANG_CXX_LIBRARY = "libc++"; 403 | CLANG_ENABLE_MODULES = YES; 404 | CLANG_ENABLE_OBJC_ARC = YES; 405 | CLANG_WARN_BOOL_CONVERSION = YES; 406 | CLANG_WARN_CONSTANT_CONVERSION = YES; 407 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 408 | CLANG_WARN_EMPTY_BODY = YES; 409 | CLANG_WARN_ENUM_CONVERSION = YES; 410 | CLANG_WARN_INT_CONVERSION = YES; 411 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 412 | CLANG_WARN_UNREACHABLE_CODE = YES; 413 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 414 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 415 | COPY_PHASE_STRIP = NO; 416 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 417 | ENABLE_NS_ASSERTIONS = NO; 418 | ENABLE_STRICT_OBJC_MSGSEND = YES; 419 | GCC_C_LANGUAGE_STANDARD = gnu99; 420 | GCC_NO_COMMON_BLOCKS = YES; 421 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 422 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 423 | GCC_WARN_UNDECLARED_SELECTOR = YES; 424 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 425 | GCC_WARN_UNUSED_FUNCTION = YES; 426 | GCC_WARN_UNUSED_VARIABLE = YES; 427 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 428 | MTL_ENABLE_DEBUG_INFO = NO; 429 | SDKROOT = iphoneos; 430 | VALIDATE_PRODUCT = YES; 431 | }; 432 | name = Release; 433 | }; 434 | 6ADAE8081BE8F6C300641C89 /* Debug */ = { 435 | isa = XCBuildConfiguration; 436 | buildSettings = { 437 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 438 | CODE_SIGN_IDENTITY = "iPhone Developer"; 439 | INFOPLIST_FILE = FlowSlideMenu/Info.plist; 440 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 441 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 442 | PRODUCT_BUNDLE_IDENTIFIER = com.ll.FlowSlideMenu; 443 | PRODUCT_NAME = "$(TARGET_NAME)"; 444 | }; 445 | name = Debug; 446 | }; 447 | 6ADAE8091BE8F6C300641C89 /* Release */ = { 448 | isa = XCBuildConfiguration; 449 | buildSettings = { 450 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 451 | CODE_SIGN_IDENTITY = "iPhone Developer"; 452 | INFOPLIST_FILE = FlowSlideMenu/Info.plist; 453 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 454 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 455 | PRODUCT_BUNDLE_IDENTIFIER = com.ll.FlowSlideMenu; 456 | PRODUCT_NAME = "$(TARGET_NAME)"; 457 | }; 458 | name = Release; 459 | }; 460 | 6ADAE80B1BE8F6C300641C89 /* Debug */ = { 461 | isa = XCBuildConfiguration; 462 | buildSettings = { 463 | BUNDLE_LOADER = "$(TEST_HOST)"; 464 | INFOPLIST_FILE = FlowSlideMenuTests/Info.plist; 465 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 466 | PRODUCT_BUNDLE_IDENTIFIER = com.ll.flowslidemenu.FlowSlideMenuTests; 467 | PRODUCT_NAME = "$(TARGET_NAME)"; 468 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FlowSlideMenu.app/FlowSlideMenu"; 469 | }; 470 | name = Debug; 471 | }; 472 | 6ADAE80C1BE8F6C300641C89 /* Release */ = { 473 | isa = XCBuildConfiguration; 474 | buildSettings = { 475 | BUNDLE_LOADER = "$(TEST_HOST)"; 476 | INFOPLIST_FILE = FlowSlideMenuTests/Info.plist; 477 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 478 | PRODUCT_BUNDLE_IDENTIFIER = com.ll.flowslidemenu.FlowSlideMenuTests; 479 | PRODUCT_NAME = "$(TARGET_NAME)"; 480 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FlowSlideMenu.app/FlowSlideMenu"; 481 | }; 482 | name = Release; 483 | }; 484 | 6ADAE80E1BE8F6C300641C89 /* Debug */ = { 485 | isa = XCBuildConfiguration; 486 | buildSettings = { 487 | INFOPLIST_FILE = FlowSlideMenuUITests/Info.plist; 488 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 489 | PRODUCT_BUNDLE_IDENTIFIER = com.ll.flowslidemenu.FlowSlideMenuUITests; 490 | PRODUCT_NAME = "$(TARGET_NAME)"; 491 | TEST_TARGET_NAME = FlowSlideMenu; 492 | USES_XCTRUNNER = YES; 493 | }; 494 | name = Debug; 495 | }; 496 | 6ADAE80F1BE8F6C300641C89 /* Release */ = { 497 | isa = XCBuildConfiguration; 498 | buildSettings = { 499 | INFOPLIST_FILE = FlowSlideMenuUITests/Info.plist; 500 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 501 | PRODUCT_BUNDLE_IDENTIFIER = com.ll.flowslidemenu.FlowSlideMenuUITests; 502 | PRODUCT_NAME = "$(TARGET_NAME)"; 503 | TEST_TARGET_NAME = FlowSlideMenu; 504 | USES_XCTRUNNER = YES; 505 | }; 506 | name = Release; 507 | }; 508 | /* End XCBuildConfiguration section */ 509 | 510 | /* Begin XCConfigurationList section */ 511 | 6ADAE7DA1BE8F6C200641C89 /* Build configuration list for PBXProject "FlowSlideMenu" */ = { 512 | isa = XCConfigurationList; 513 | buildConfigurations = ( 514 | 6ADAE8051BE8F6C300641C89 /* Debug */, 515 | 6ADAE8061BE8F6C300641C89 /* Release */, 516 | ); 517 | defaultConfigurationIsVisible = 0; 518 | defaultConfigurationName = Release; 519 | }; 520 | 6ADAE8071BE8F6C300641C89 /* Build configuration list for PBXNativeTarget "FlowSlideMenu" */ = { 521 | isa = XCConfigurationList; 522 | buildConfigurations = ( 523 | 6ADAE8081BE8F6C300641C89 /* Debug */, 524 | 6ADAE8091BE8F6C300641C89 /* Release */, 525 | ); 526 | defaultConfigurationIsVisible = 0; 527 | defaultConfigurationName = Release; 528 | }; 529 | 6ADAE80A1BE8F6C300641C89 /* Build configuration list for PBXNativeTarget "FlowSlideMenuTests" */ = { 530 | isa = XCConfigurationList; 531 | buildConfigurations = ( 532 | 6ADAE80B1BE8F6C300641C89 /* Debug */, 533 | 6ADAE80C1BE8F6C300641C89 /* Release */, 534 | ); 535 | defaultConfigurationIsVisible = 0; 536 | defaultConfigurationName = Release; 537 | }; 538 | 6ADAE80D1BE8F6C300641C89 /* Build configuration list for PBXNativeTarget "FlowSlideMenuUITests" */ = { 539 | isa = XCConfigurationList; 540 | buildConfigurations = ( 541 | 6ADAE80E1BE8F6C300641C89 /* Debug */, 542 | 6ADAE80F1BE8F6C300641C89 /* Release */, 543 | ); 544 | defaultConfigurationIsVisible = 0; 545 | defaultConfigurationName = Release; 546 | }; 547 | /* End XCConfigurationList section */ 548 | }; 549 | rootObject = 6ADAE7D71BE8F6C200641C89 /* Project object */; 550 | } 551 | -------------------------------------------------------------------------------- /FlowSlideMenu.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /FlowSlideMenu/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // FlowSlideMenu 4 | // 5 | // Created by LL on 15/11/3. 6 | // Copyright © 2015 LL. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 18 | // Override point for customization after application launch. 19 | 20 | 21 | let storyboard = UIStoryboard(name: "Main",bundle:nil) 22 | let leftvc = storyboard.instantiateViewControllerWithIdentifier("leftvc") 23 | let mainvc = storyboard.instantiateViewControllerWithIdentifier("mainvc") 24 | 25 | let slideMenuController = LLFlowSlideMenuVC(mainViewController: mainvc, leftViewController: leftvc) 26 | self.window?.rootViewController = slideMenuController 27 | self.window?.makeKeyAndVisible() 28 | 29 | 30 | return true 31 | } 32 | 33 | func applicationWillResignActive(application: UIApplication) { 34 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 35 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 36 | } 37 | 38 | func applicationDidEnterBackground(application: UIApplication) { 39 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 40 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 41 | } 42 | 43 | func applicationWillEnterForeground(application: UIApplication) { 44 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 45 | } 46 | 47 | func applicationDidBecomeActive(application: UIApplication) { 48 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 49 | } 50 | 51 | func applicationWillTerminate(application: UIApplication) { 52 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 53 | } 54 | 55 | 56 | } 57 | 58 | -------------------------------------------------------------------------------- /FlowSlideMenu/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /FlowSlideMenu/Base.lproj/LaunchScreen.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 | -------------------------------------------------------------------------------- /FlowSlideMenu/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 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 | 58 | 65 | 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 | -------------------------------------------------------------------------------- /FlowSlideMenu/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /FlowSlideMenu/LeftVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LeftVC.swift 3 | // 4 | // Created by LL on 15/10/31. 5 | // Copyright © 2015 LL. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | 10 | class LeftVC :UIViewController { 11 | 12 | 13 | } -------------------------------------------------------------------------------- /FlowSlideMenu/MainVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainVC.swift 3 | // FlowSlideMenu 4 | // 5 | // Created by LL on 15/11/4. 6 | // Copyright © 2015 LL. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class MainVC: UIViewController { 12 | 13 | @IBAction func open() 14 | { 15 | self.openLeft() 16 | } 17 | 18 | @IBAction func close() 19 | { 20 | self.closeLeft() 21 | } 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | 25 | // Do any additional setup after loading the view. 26 | } 27 | 28 | override func didReceiveMemoryWarning() { 29 | super.didReceiveMemoryWarning() 30 | // Dispose of any resources that can be recreated. 31 | } 32 | 33 | 34 | /* 35 | // MARK: - Navigation 36 | 37 | // In a storyboard-based application, you will often want to do a little preparation before navigation 38 | override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { 39 | // Get the new view controller using segue.destinationViewController. 40 | // Pass the selected object to the new view controller. 41 | } 42 | */ 43 | 44 | } 45 | -------------------------------------------------------------------------------- /FlowSlideMenuCore/LLFlowCurveProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LLFlowCurveProtocol.swift 3 | // FlowSlideMenu 4 | // 5 | // Created by LL on 15/11/5. 6 | // Copyright © 2015 LL. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @objc public protocol LLFlowCurveViewDelegate : NSObjectProtocol 12 | { 13 | func flowViewStartAnimation(flow:LLFlowCurveView) 14 | 15 | func flowViewEndAnimation(flow:LLFlowCurveView) 16 | } 17 | -------------------------------------------------------------------------------- /FlowSlideMenuCore/LLFlowCurveView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LLFakeCurveView.swift 3 | // 4 | // Created by LL on 15/11/1. 5 | // Copyright © 2015 LL. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | import QuartzCore 10 | 11 | public class LLFlowCurveView : UIView 12 | { 13 | weak public var delegate: LLFlowCurveViewDelegate? 14 | 15 | public var animating : Bool = false 16 | 17 | public enum Status { 18 | case OPEN_MANUAL 19 | case OPEN_ANI_ALL 20 | case OPEN_ANI_TO_HALF 21 | case OPEN_ANI_TO_BOUNCE 22 | case OPEN_BOUNCE 23 | case OPEN_FINISH 24 | case CLOSE 25 | } 26 | 27 | var bgColor : UIColor = UIColor.blueColor() 28 | 29 | var startpoint : CGPoint = CGPoint.zero 30 | var endPoint : CGPoint = CGPoint.zero 31 | 32 | var controlPoint1 : CGPoint = CGPoint.zero 33 | var controlPoint2 : CGPoint = CGPoint.zero 34 | var controlPoint3 : CGPoint = CGPoint.zero 35 | var orientation : Orientation = .Left 36 | 37 | var revealPoint : CGPoint = CGPoint.zero 38 | 39 | var status : Status = .CLOSE 40 | 41 | var maxRate : CGFloat = 0.3 42 | 43 | 44 | let ANIMATION_KEY_OPENALL : String = "openall" 45 | let ANIMATION_KEY_OPEN2HALF : String = "open2half" 46 | let ANIMATION_KEY_OPEN2BOUNCE : String = "open2bounce" 47 | let ANIMATION_KEY_BOUNCE : String = "bounce" 48 | 49 | // MARK: - 50 | // MARK: lifecycle 51 | 52 | public override init(frame: CGRect) 53 | { 54 | super.init(frame: frame) 55 | layer.opaque = false 56 | } 57 | 58 | required public init?(coder aDecoder: NSCoder) { 59 | super.init(coder: aDecoder) 60 | } 61 | 62 | public func updatePoint(revealPoint:CGPoint, 63 | orientation:Orientation 64 | ) 65 | { 66 | self.revealPoint = revealPoint 67 | self.setNeedsDisplay() 68 | } 69 | 70 | public func updatePointKVO(x:CGFloat, 71 | orientation:Orientation 72 | ) 73 | { 74 | if(self.status == Status.OPEN_ANI_ALL) 75 | { 76 | self.revealPoint = CGPointMake(x, FlowCurveOptions.startRevealY) 77 | self.setNeedsDisplay() 78 | } 79 | } 80 | 81 | public func updatePointTimer() 82 | { 83 | if(self.status == Status.OPEN_ANI_ALL) 84 | { 85 | let offset : CGFloat = self.getWidth() + self.frame.origin.x 86 | self.revealPoint = CGPointMake(offset, FlowCurveOptions.startRevealY) 87 | self.setNeedsDisplay() 88 | // if(offset > self.getWidth()/2) 89 | // { 90 | // stopTimer() 91 | // open() 92 | // return 93 | // } 94 | } 95 | } 96 | 97 | public override func drawRect(rect: CGRect) 98 | { 99 | if(self.status == .CLOSE) 100 | { 101 | return 102 | } 103 | computePointsForStatus(self.status) 104 | 105 | let context : CGContext = UIGraphicsGetCurrentContext()! 106 | 107 | CGContextSetStrokeColorWithColor(context, FlowCurveOptions.bgColor.CGColor) 108 | CGContextSetLineWidth(context, 10) 109 | CGContextSetFillColorWithColor(context, FlowCurveOptions.bgColor.CGColor) 110 | 111 | let path : UIBezierPath = UIBezierPath() 112 | 113 | path.moveToPoint(CGPointZero) 114 | path.addLineToPoint(self.startpoint) 115 | 116 | path.moveToPoint(self.startpoint) 117 | 118 | path.addCurveToPoint(self.controlPoint2, controlPoint1:self.controlPoint1, controlPoint2:CGPointMake(self.controlPoint2.x, self.controlPoint1.y)) 119 | path.addLineToPoint(CGPointMake(0, self.controlPoint2.y)) 120 | path.addLineToPoint(CGPointZero) 121 | 122 | path.moveToPoint(self.controlPoint2) 123 | path.addCurveToPoint(self.endPoint, controlPoint1:CGPointMake(self.controlPoint2.x, self.controlPoint3.y), controlPoint2:self.controlPoint3) 124 | 125 | path.moveToPoint(self.endPoint) 126 | path.addLineToPoint(CGPointMake(0, self.getHeight())) 127 | path.addLineToPoint(CGPointMake(0, self.controlPoint2.y)) 128 | path.addLineToPoint(self.controlPoint2) 129 | 130 | path.stroke() 131 | path.fill() 132 | } 133 | 134 | public override class func layerClass() -> AnyClass 135 | { 136 | return LLFlowLayer.classForCoder() 137 | } 138 | 139 | // MARK: - 140 | // MARK: private funs 141 | 142 | // MARK: compute points funs 143 | 144 | private func computePointsForStatus(_status:Status) 145 | { 146 | 147 | switch(_status) 148 | { 149 | case .OPEN_MANUAL : 150 | computePoints() 151 | case .OPEN_ANI_ALL : 152 | let layer :LLFlowLayer = self.layer.presentationLayer() as! LLFlowLayer 153 | self.revealPoint = CGPointMake(layer.reveal, self.revealPoint.y) 154 | computePoints() 155 | case .OPEN_ANI_TO_HALF : 156 | let layer :LLFlowLayer = self.layer.presentationLayer() as! LLFlowLayer 157 | self.revealPoint = CGPointMake(layer.reveal, self.revealPoint.y) 158 | computePoints() 159 | case .OPEN_ANI_TO_BOUNCE : 160 | let layer :LLFlowLayer = self.layer.presentationLayer() as! LLFlowLayer 161 | self.startpoint = CGPointMake(layer.start, self.startpoint.y) 162 | self.endPoint = CGPointMake(layer.start, self.endPoint.y) 163 | self.controlPoint1 = CGPointMake(layer.control, self.controlPoint1.y) 164 | self.controlPoint3 = CGPointMake(layer.control, self.controlPoint3.y) 165 | case .OPEN_BOUNCE : 166 | let layer :LLFlowLayer = self.layer.presentationLayer() as! LLFlowLayer 167 | self.controlPoint2 = CGPointMake(layer.reveal, self.controlPoint2.y) 168 | self.startpoint = CGPointMake(layer.start, self.startpoint.y) 169 | self.endPoint = CGPointMake(layer.start, self.endPoint.y) 170 | self.controlPoint1 = CGPointMake(layer.control, self.controlPoint1.y) 171 | self.controlPoint3 = CGPointMake(layer.control, self.controlPoint3.y) 172 | default: 173 | break 174 | } 175 | /*if (animating){ 176 | let layer :LLFlowLayer = self.layer.presentationLayer() as! LLFlowLayer 177 | 178 | self.revealPoint = CGPointMake(layer.reveal, self.revealPoint.y) 179 | 180 | if(self.status == .OPEN_ANI_TO_BOUNCE) 181 | { 182 | self.controlPoint1 = CGPointMake(layer.control, self.controlPoint1.y) 183 | self.controlPoint3 = CGPointMake(layer.control, self.controlPoint3.y) 184 | self.startpoint = CGPointMake(layer.start, self.startpoint.y) 185 | self.endPoint = CGPointMake(layer.start, self.endPoint.y) 186 | 187 | } 188 | if(self.status == .OPEN_ALL) 189 | { 190 | computePoints() 191 | } 192 | }else 193 | { 194 | computePoints() 195 | } 196 | */ 197 | } 198 | /* 199 | 200 | private func computeControlPoint(point : CGPoint , bottom : Bool) -> CGPoint 201 | { 202 | if(self.status == .OPEN_FINISH) 203 | { 204 | return CGPointMake(self.getWidth(), point.y) 205 | } 206 | if(bottom) 207 | { 208 | if(isSticky()) 209 | { 210 | if(status == .OPEN_ANI_TO_BOUNCE) 211 | { 212 | return CGPointMake(self.startpoint.x, point.y) 213 | } 214 | var a : CGFloat = revealPoint.x/self.getWidth()*2 215 | a = 1 - a 216 | if(a < maxRate) 217 | { 218 | a = maxRate 219 | } 220 | return CGPointMake((getMidPointX()-self.revealPoint.x*a), point.y) 221 | } 222 | let a : CGFloat = revealPoint.x/self.controlPoint1.x 223 | let maxRatio :CGFloat = 0.7 224 | if(a > maxRatio && status != .OPEN_ANI_TO_BOUNCE ) 225 | { 226 | return CGPointMake(getMidPointX() * maxRatio, point.y) 227 | } 228 | return CGPointMake(getMidPointX()*a , point.y) 229 | } 230 | if(isSticky() && status == .OPEN_ANI_TO_BOUNCE) 231 | { 232 | return CGPointMake(self.controlPoint2.x, point.y) 233 | } 234 | return CGPointMake(self.getMidPointX(), point.y) 235 | } 236 | */ 237 | 238 | private func getWaveWidth() -> CGFloat 239 | { 240 | return getHeight() 241 | } 242 | 243 | public func getWidth() -> CGFloat 244 | { 245 | return self.frame.size.width - FlowCurveOptions.waveMargin 246 | } 247 | 248 | private func getHeight() -> CGFloat 249 | { 250 | return self.frame.size.height 251 | } 252 | 253 | private func getStartPoint() -> CGPoint 254 | { 255 | var x :CGFloat = self.getWidth() - self.revealPoint.x 256 | let y :CGFloat = 0 257 | 258 | if(x < 0) 259 | { 260 | x = 0 261 | } 262 | if(self.status == .OPEN_ANI_ALL) 263 | { 264 | return CGPointZero 265 | } 266 | if(self.status == .OPEN_FINISH) 267 | { 268 | return CGPointMake(getWidth(), y) 269 | } 270 | return CGPointMake(x,y) 271 | } 272 | 273 | private func getEndPoint() -> CGPoint 274 | { 275 | 276 | var x = self.getWidth() - self.revealPoint.x 277 | let y = self.getHeight() 278 | 279 | if(self.status == .OPEN_ANI_ALL) 280 | { 281 | return CGPointMake(0, self.getHeight()) 282 | } 283 | 284 | if(x < 0) 285 | { 286 | x = 0 287 | } 288 | 289 | if(self.status == .OPEN_FINISH) 290 | { 291 | return CGPointMake(getWidth(), y) 292 | } 293 | return CGPointMake(x,y) 294 | } 295 | 296 | private func getControlPoint1InOpenAllWithRevealPoint(revealPoint:CGPoint) -> CGPoint 297 | { 298 | let y = revealPoint.y - (getWaveWidth()/5 * revealPoint.x/self.getWidth()) - getWaveWidth()/20 299 | return CGPointMake(revealPoint.x/2, y) 300 | } 301 | 302 | private func getControlPoint3InOpenAllWithRevealPoint(revealPoint:CGPoint) -> CGPoint 303 | { 304 | let y = revealPoint.y + (getWaveWidth()/5 * revealPoint.x/self.getWidth()) + getWaveWidth()/20 305 | return CGPointMake(revealPoint.x/2, y) 306 | } 307 | 308 | private func getControlPoint1() -> CGPoint 309 | { 310 | var x = getMidPointX() - (self.revealPoint.x/1.5) 311 | var y = getMidPointY() - (getWaveWidth()/10 * self.revealPoint.x/self.getWidth()) - getWaveWidth()/20 312 | 313 | if(self.status == .OPEN_ANI_ALL) 314 | { 315 | y = getMidPointY() - (getWaveWidth()/5 * self.revealPoint.x/self.getWidth()) - getWaveWidth()/20 316 | return CGPointMake(self.revealPoint.x/2, y) 317 | } 318 | if(x < getStartPoint().x) 319 | { 320 | x = getStartPoint().x 321 | } 322 | if(self.status == .OPEN_FINISH) 323 | { 324 | return CGPointMake(getWidth(), y) 325 | } 326 | return CGPointMake(x,y) 327 | } 328 | 329 | public func getControlPoint2() -> CGPoint 330 | { 331 | let x : CGFloat = self.getMidPointX() 332 | let y : CGFloat = self.revealPoint.y 333 | 334 | if(self.status == .OPEN_FINISH) 335 | { 336 | return CGPointMake(getWidth(), y) 337 | } 338 | return CGPointMake(x,y) 339 | } 340 | 341 | private func getControlPoint3() -> CGPoint 342 | { 343 | var x : CGFloat = getMidPointX() - (self.revealPoint.x/1.5) 344 | var y : CGFloat = getMidPointY() + (getWaveWidth()/10 * self.revealPoint.x/self.getWidth()) + getWaveWidth()/20 345 | 346 | if(self.status == .OPEN_ANI_ALL) 347 | { 348 | y = getMidPointY() + (getWaveWidth()/5 * self.revealPoint.x/self.getWidth()) + getWaveWidth()/20 349 | return CGPointMake(self.revealPoint.x/2, y) 350 | } 351 | 352 | if(x < getStartPoint().x) 353 | { 354 | x = getStartPoint().x 355 | } 356 | 357 | if(self.status == .OPEN_FINISH) 358 | { 359 | return CGPointMake(getWidth(), y) 360 | } 361 | return CGPointMake(x,y) 362 | } 363 | 364 | //start point 365 | private func getMidPointY() -> CGFloat 366 | { 367 | return self.revealPoint.y 368 | } 369 | 370 | //start point 371 | private func getMidPointX() -> CGFloat 372 | { 373 | if(self.status == .OPEN_ANI_ALL) 374 | { 375 | return self.revealPoint.x 376 | } 377 | return getWidth() - (self.revealPoint.x * 0.3) 378 | } 379 | 380 | private func computePoints() 381 | { 382 | self.startpoint = self.getStartPoint() 383 | self.endPoint = self.getEndPoint() 384 | self.controlPoint1 = self.getControlPoint1() 385 | self.controlPoint2 = self.getControlPoint2() 386 | self.controlPoint3 = self.getControlPoint3() 387 | } 388 | 389 | private func getTo1(float:CGFloat) -> CGFloat 390 | { 391 | let to : CGFloat = getWidth() - float 392 | return to 393 | } 394 | 395 | private func getTo1() -> CGFloat 396 | { 397 | let to : CGFloat = getWidth() 398 | return to 399 | } 400 | 401 | private func getAnimationToHalf() -> CGFloat 402 | { 403 | let to : CGFloat = getWidth()*1/2 404 | return to 405 | } 406 | 407 | private func getAnimationToHalfPoint() -> CGPoint 408 | { 409 | let to : CGFloat = getWidth()*1/2 410 | return CGPointMake(to, FlowCurveOptions.startRevealY) 411 | } 412 | 413 | private func reset() 414 | { 415 | self.revealPoint = CGPointZero 416 | self.animating = false 417 | } 418 | 419 | // MARK: get animation 420 | private func getSpringAnimationWithTo(to:Float,from:Float,name:String) ->CASpringAnimation 421 | { 422 | let animation:CASpringAnimation = CASpringAnimation(keyPath: name) 423 | animation.toValue = Float(to) 424 | animation.fromValue = Float(from) 425 | animation.damping = FlowCurveOptions.animation_damping 426 | animation.duration = animation.settlingDuration 427 | animation.stiffness = FlowCurveOptions.animation_stiffness 428 | animation.mass = FlowCurveOptions.animation_mass 429 | animation.initialVelocity = FlowCurveOptions.animation_initialVelocity 430 | animation.fillMode = kCAFillModeForwards 431 | animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionDefault) 432 | animation.removedOnCompletion = false 433 | return animation 434 | } 435 | 436 | private func getAnimationWithTo(to:Float,from:Float,duration:Float,name:String) ->CABasicAnimation 437 | { 438 | let animation:CABasicAnimation = CABasicAnimation(keyPath: name) 439 | animation.toValue = Float(to) 440 | animation.fromValue = Float(from) 441 | animation.duration = Double(duration) 442 | animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) 443 | animation.fillMode = kCAFillModeForwards 444 | animation.removedOnCompletion = false 445 | return animation 446 | } 447 | 448 | // MARK: animations 449 | private func openAll(delay:Double) 450 | { 451 | let ani_reveal : CABasicAnimation = getAnimationWithTo(Float(self.getAnimationToHalf() 452 | ),from: Float(0),duration:Float(FlowCurveOptions.animation_reveal),name: LLFlowLayer.KEY_REVEAL) 453 | 454 | self.revealPoint = CGPointMake(0,FlowCurveOptions.startRevealY) 455 | 456 | ani_reveal.delegate = self 457 | 458 | ani_reveal.beginTime = CACurrentMediaTime() + delay 459 | 460 | self.layer.addAnimation(ani_reveal, forKey:ANIMATION_KEY_OPENALL) 461 | } 462 | 463 | private func openToHalf(delay:Double) 464 | { 465 | let ani_reveal : CABasicAnimation = getAnimationWithTo(Float(self.getWidth()/2 466 | ),from: Float(0),duration:Float(FlowCurveOptions.animation_reveal),name: LLFlowLayer.KEY_REVEAL) 467 | 468 | self.revealPoint = CGPointMake(0,FlowCurveOptions.startRevealY) 469 | 470 | ani_reveal.delegate = self 471 | 472 | ani_reveal.beginTime = CACurrentMediaTime() + delay 473 | 474 | self.layer.addAnimation(ani_reveal, forKey: ANIMATION_KEY_OPEN2HALF) 475 | } 476 | 477 | private func openToBounce(delay:Double) 478 | { 479 | let ani_controlpoint : CASpringAnimation = getSpringAnimationWithTo(Float(getTo1()),from: Float(self.controlPoint1.x),name:LLFlowLayer.KEY_CONTROL) 480 | let ani_startpoint : CASpringAnimation = getSpringAnimationWithTo(Float(getTo1()),from: Float(self.startpoint.x),name: LLFlowLayer.KEY_START) 481 | 482 | ani_controlpoint.beginTime = CACurrentMediaTime() + delay 483 | 484 | ani_controlpoint.delegate = self 485 | 486 | self.layer.addAnimation(ani_controlpoint, forKey: ANIMATION_KEY_OPEN2BOUNCE) 487 | self.layer.addAnimation(ani_startpoint, forKey: ANIMATION_KEY_OPEN2BOUNCE + "1") 488 | } 489 | 490 | private func openToBounce(delay:Double,from:CGFloat) 491 | { 492 | let ani_controlpoint : CASpringAnimation = getSpringAnimationWithTo(Float(getTo1()),from: Float(self.getControlPoint1InOpenAllWithRevealPoint(self.getAnimationToHalfPoint()).x),name:LLFlowLayer.KEY_CONTROL) 493 | let ani_startpoint : CASpringAnimation = getSpringAnimationWithTo(Float(getTo1()), 494 | from: Float(0), 495 | name: LLFlowLayer.KEY_START) 496 | 497 | ani_controlpoint.beginTime = CACurrentMediaTime() + delay 498 | 499 | ani_controlpoint.delegate = self 500 | 501 | self.layer.addAnimation(ani_controlpoint, forKey: ANIMATION_KEY_OPEN2BOUNCE) 502 | self.layer.addAnimation(ani_startpoint, forKey: ANIMATION_KEY_OPEN2BOUNCE + "1") 503 | } 504 | 505 | private func bounce(delay:Double,from:CGFloat) { 506 | 507 | let ani_reveal : CASpringAnimation = getSpringAnimationWithTo(Float(getTo1()),from:Float(from),name:LLFlowLayer.KEY_REVEAL) 508 | 509 | ani_reveal.delegate = self 510 | 511 | ani_reveal.beginTime = CACurrentMediaTime() + delay 512 | 513 | self.layer.addAnimation(ani_reveal, forKey: ANIMATION_KEY_BOUNCE) 514 | } 515 | 516 | private func bounce(delay:Double) { 517 | 518 | let ani_reveal : CASpringAnimation = getSpringAnimationWithTo(Float(getTo1()),from:Float(revealPoint.x),name:LLFlowLayer.KEY_REVEAL) 519 | 520 | ani_reveal.delegate = self 521 | 522 | ani_reveal.beginTime = CACurrentMediaTime() + delay 523 | 524 | self.layer.addAnimation(ani_reveal, forKey: ANIMATION_KEY_BOUNCE) 525 | } 526 | 527 | private func finish() 528 | { 529 | if (self.layer.animationKeys() != nil && (self.layer.animationKeys()!.count > 0)) 530 | { 531 | layer.removeAllAnimations() 532 | self.animating = false 533 | reset() 534 | self.status = .OPEN_FINISH 535 | notifyDelegateAnimationEnd() 536 | } 537 | } 538 | 539 | 540 | private func notifyDelegateAnimationStart() 541 | { 542 | if(self.delegate != nil) 543 | { 544 | self.delegate?.flowViewStartAnimation(self) 545 | } 546 | } 547 | 548 | private func notifyDelegateAnimationEnd() 549 | { 550 | if(self.delegate != nil) 551 | { 552 | self.delegate?.flowViewEndAnimation(self) 553 | } 554 | } 555 | 556 | // MARK: - 557 | // MARK: public funs 558 | public func openAll() 559 | { 560 | if(self.animating == true) 561 | { 562 | return 563 | } 564 | 565 | notifyDelegateAnimationStart() 566 | 567 | self.animating = true 568 | 569 | self.status = .OPEN_ANI_ALL 570 | 571 | self.layer.removeAllAnimations() 572 | 573 | openAll(0.0) 574 | 575 | openToBounce(FlowCurveOptions.animation_reveal,from:self.getAnimationToHalf()) 576 | 577 | bounce(FlowCurveOptions.animation_open + FlowCurveOptions.animation_reveal,from:self.getAnimationToHalf()) 578 | } 579 | 580 | public func open() { 581 | 582 | if(self.status != .OPEN_MANUAL) 583 | { 584 | return 585 | } 586 | self.layer.removeAllAnimations() 587 | 588 | notifyDelegateAnimationStart() 589 | 590 | self.animating = true 591 | 592 | openToBounce(0) 593 | 594 | bounce(FlowCurveOptions.animation_open) 595 | } 596 | 597 | public func start() 598 | { 599 | if(self.status == .CLOSE) 600 | { 601 | self.status = .OPEN_MANUAL 602 | self.frame.origin.x = self.frame.origin.x + FlowCurveOptions.waveMargin 603 | } 604 | } 605 | 606 | public func close() 607 | { 608 | self.layer.removeAllAnimations() 609 | self.status = .CLOSE 610 | self.reset() 611 | } 612 | 613 | // MARK: - 614 | // MARK: caanimation delegate 615 | 616 | public override func animationDidStop(anim: CAAnimation, finished flag: Bool) 617 | { 618 | if(anim == self.layer.animationForKey(ANIMATION_KEY_BOUNCE)) 619 | { 620 | finish() 621 | } 622 | } 623 | 624 | public override func animationDidStart(anim: CAAnimation) { 625 | 626 | if(anim == self.layer.animationForKey(ANIMATION_KEY_OPEN2HALF)) 627 | { 628 | self.status = .OPEN_ANI_TO_HALF 629 | }else if(anim == self.layer.animationForKey(ANIMATION_KEY_BOUNCE)) 630 | { 631 | self.status = .OPEN_BOUNCE 632 | }else if(anim == self.layer.animationForKey(ANIMATION_KEY_OPEN2BOUNCE)) 633 | { 634 | self.status = .OPEN_ANI_TO_BOUNCE 635 | } 636 | } 637 | 638 | } 639 | -------------------------------------------------------------------------------- /FlowSlideMenuCore/LLFlowLayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LLFlowLayer.swift 3 | // 4 | // Created by LL on 15/11/1. 5 | // Copyright © 2015 LL. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | import QuartzCore 10 | 11 | public class LLFlowLayer:CALayer 12 | { 13 | @NSManaged var reveal : CGFloat 14 | @NSManaged var control : CGFloat 15 | @NSManaged var start : CGFloat 16 | 17 | static public var KEY_REVEAL :String = "reveal" 18 | static public var KEY_CONTROL :String = "control" 19 | static public var KEY_START :String = "start" 20 | 21 | override init() { 22 | super.init() 23 | } 24 | 25 | override init(layer: AnyObject) { 26 | super.init(layer: layer) 27 | if(layer.isKindOfClass(LLFlowLayer.classForCoder())) 28 | { 29 | let l:LLFlowLayer = layer as! LLFlowLayer 30 | self.reveal = l.reveal 31 | self.start = l.start 32 | self.control = l.control 33 | } 34 | } 35 | 36 | required public init?(coder aDecoder: NSCoder) { 37 | super.init(coder: aDecoder) 38 | } 39 | 40 | public override class func needsDisplayForKey(key: String) -> Bool 41 | { 42 | if( key == "reveal" || key == "control" || key == "start") 43 | { 44 | return true 45 | } 46 | return super.needsDisplayForKey(key) 47 | } 48 | 49 | public override func actionForKey(key: String) -> CAAction? 50 | { 51 | if ( key == "reveal" || key == "control" || key == "start") 52 | { 53 | let theAnimation : CABasicAnimation = CABasicAnimation(keyPath:key) 54 | 55 | theAnimation.fromValue = self.presentationLayer()?.contents 56 | 57 | return theAnimation; 58 | } 59 | 60 | return super.actionForKey(key); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /FlowSlideMenuCore/LLFlowSlideMenuConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LLFlowSlideMenuConfig.swift 3 | // FlowSlideMenu 4 | // 5 | // Created by LL on 15/11/5. 6 | // Copyright © 2015 LL. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public struct FlowSlideMenuOptions { 12 | public static var leftViewWidth: CGFloat = 200.0 13 | public static var leftBezelWidth: CGFloat = 100.0 14 | public static var contentViewScale: CGFloat = 0.96 15 | public static var contentViewOpacity: CGFloat = 0.5 16 | public static var shadowOpacity: CGFloat = 0.0 17 | public static var shadowRadius: CGFloat = 0.0 18 | public static var shadowOffset: CGSize = CGSizeMake(0,0) 19 | public static var panFromBezel: Bool = true 20 | public static var animationDuration: CGFloat = 0.5 21 | public static var hideStatusBar: Bool = true 22 | public static var pointOfNoReturnWidth: CGFloat = 150.0 23 | public static var opacityViewBackgroundColor: UIColor = UIColor.blackColor() 24 | } 25 | 26 | public struct FlowCurveOptions { 27 | //background color for animation view 28 | public static var bgColor : UIColor = UIColor.whiteColor() 29 | //the wave cloud be floating so have to make margin 30 | public static var waveMargin : CGFloat = 100 31 | //the auto open point which is start Y 32 | public static var startRevealY : CGFloat = 300 33 | //animation duration total time 34 | public static var animation_reveal:Double = 0.3 35 | //animation duration time for open 36 | public static var animation_open:Double = 0.1 37 | //animation damping factor 38 | public static var animation_damping:CGFloat = 10 39 | //animation stiffness factor 40 | public static var animation_stiffness:CGFloat = 100 41 | //animation mass factor 42 | public static var animation_mass:CGFloat = 1 43 | //animation initial velocity 44 | public static var animation_initialVelocity:CGFloat = 10 45 | } 46 | 47 | 48 | -------------------------------------------------------------------------------- /FlowSlideMenuCore/LLFlowSlideMenuEnum.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LLFlowSlideMenuEnum.swift 3 | // FlowSlideMenu 4 | // 5 | // Created by LL on 15/11/5. 6 | // Copyright © 2015 LL. All rights reserved. 7 | // 8 | 9 | public enum Orientation { 10 | case Left 11 | } 12 | 13 | -------------------------------------------------------------------------------- /FlowSlideMenuCore/LLFlowSlideMenuVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LLFlowSlideMenuVC 3 | // 4 | // Created by LL on 15/10/31. 5 | // Copyright © 2015 LL. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | import QuartzCore 10 | 11 | public class LLFlowSlideMenuVC : UIViewController, UIGestureRecognizerDelegate ,LLFlowCurveViewDelegate 12 | { 13 | public enum SlideAction { 14 | case Open 15 | case Close 16 | } 17 | 18 | struct PanInfo { 19 | var action: SlideAction 20 | var shouldBounce: Bool 21 | var velocity: CGFloat 22 | } 23 | 24 | // MARK: - 25 | // MARK: parms 26 | public var leftViewController: UIViewController? 27 | public var mainViewController: UIViewController? 28 | 29 | public var opacityView = UIView() 30 | public var mainContainerView = UIView() 31 | public var leftContainerView = LLFlowCurveView() 32 | 33 | public var leftPanGesture: UIPanGestureRecognizer? 34 | public var leftTapGetsture: UITapGestureRecognizer? 35 | 36 | private var curContext = 0 37 | 38 | // MARK: - 39 | // MARK: lifecycle 40 | public required init?(coder aDecoder: NSCoder) { 41 | super.init(coder: aDecoder) 42 | } 43 | 44 | public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { 45 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 46 | } 47 | 48 | public convenience init(mainViewController: UIViewController,leftViewController: UIViewController) { 49 | self.init() 50 | self.mainViewController = mainViewController 51 | self.leftViewController = leftViewController 52 | initView() 53 | 54 | self.leftContainerView.addObserver(self, forKeyPath: "frame", options: .New, context: &curContext) 55 | } 56 | 57 | override public func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer) { 58 | if context == &curContext { 59 | if let newValue : CGRect = change?[NSKeyValueChangeNewKey]?.CGRectValue{ 60 | 61 | self.leftContainerView.updatePointKVO(self.leftContainerView.frame.size.width + newValue.origin.x, orientation: Orientation.Left) 62 | } 63 | } else { 64 | super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context) 65 | } 66 | } 67 | 68 | public override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) { 69 | 70 | super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator) 71 | mainContainerView.transform = CGAffineTransformMakeScale(1.0, 1.0) 72 | leftContainerView.hidden = true 73 | 74 | coordinator.animateAlongsideTransition(nil, completion: { (context: UIViewControllerTransitionCoordinatorContext!) -> Void in 75 | self.closeLeftNonAnimation() 76 | 77 | self.leftContainerView.hidden = false 78 | 79 | if self.leftPanGesture != nil && self.leftPanGesture != nil { 80 | self.removeLeftGestures() 81 | self.addLeftGestures() 82 | } 83 | 84 | }) 85 | } 86 | 87 | public override func viewWillLayoutSubviews() { 88 | setUpViewController(mainContainerView, targetViewController: mainViewController) 89 | setUpViewController(leftContainerView, targetViewController: leftViewController,slideview: true) 90 | 91 | } 92 | 93 | deinit { 94 | self.leftContainerView.removeObserver(self, forKeyPath: "frame", context: &curContext) 95 | } 96 | 97 | // MARK: - 98 | // MARK: private funs 99 | 100 | func initView() { 101 | mainContainerView = UIView(frame: view.bounds) 102 | mainContainerView.backgroundColor = UIColor.clearColor() 103 | mainContainerView.autoresizingMask = [.FlexibleHeight, .FlexibleWidth] 104 | view.insertSubview(mainContainerView, atIndex: 0) 105 | 106 | var opacityframe: CGRect = view.bounds 107 | let opacityOffset: CGFloat = 0 108 | opacityframe.origin.y = opacityframe.origin.y + opacityOffset 109 | opacityframe.size.height = opacityframe.size.height - opacityOffset 110 | opacityView = UIView(frame: opacityframe) 111 | opacityView.backgroundColor = FlowSlideMenuOptions.opacityViewBackgroundColor 112 | opacityView.autoresizingMask = [UIViewAutoresizing.FlexibleHeight, UIViewAutoresizing.FlexibleWidth] 113 | opacityView.layer.opacity = 0.0 114 | view.insertSubview(opacityView, atIndex: 1) 115 | 116 | var leftFrame: CGRect = view.bounds 117 | leftFrame.size.width = FlowSlideMenuOptions.leftViewWidth + FlowCurveOptions.waveMargin 118 | leftFrame.origin.x = leftMinOrigin(); 119 | let leftOffset: CGFloat = 0 120 | leftFrame.origin.y = leftFrame.origin.y + leftOffset 121 | leftFrame.size.height = leftFrame.size.height - leftOffset 122 | leftContainerView = LLFlowCurveView(frame: leftFrame) 123 | leftContainerView.backgroundColor = UIColor.clearColor() 124 | leftContainerView.autoresizingMask = UIViewAutoresizing.FlexibleHeight 125 | leftContainerView.delegate = self 126 | view.insertSubview(leftContainerView, atIndex: 2) 127 | addLeftGestures() 128 | 129 | } 130 | 131 | private func setUpViewController(targetView: UIView, targetViewController: UIViewController?) { 132 | if let viewController = targetViewController { 133 | addChildViewController(viewController) 134 | viewController.view.frame = targetView.bounds 135 | targetView.addSubview(viewController.view) 136 | viewController.didMoveToParentViewController(self) 137 | } 138 | } 139 | 140 | 141 | private func setUpViewController(targetView: UIView, targetViewController: UIViewController?, slideview:Bool) { 142 | if let viewController = targetViewController { 143 | addChildViewController(viewController) 144 | if(slideview) 145 | { 146 | viewController.view.frame = CGRectMake(0,0,slideViewWidth(),targetView.bounds.height) 147 | }else 148 | { 149 | viewController.view.frame = targetView.bounds 150 | } 151 | targetView.addSubview(viewController.view) 152 | viewController.didMoveToParentViewController(self) 153 | } 154 | } 155 | 156 | private func removeViewController(viewController: UIViewController?) { 157 | if let _viewController = viewController { 158 | _viewController.willMoveToParentViewController(nil) 159 | _viewController.view.removeFromSuperview() 160 | _viewController.removeFromParentViewController() 161 | } 162 | } 163 | 164 | private func leftMinOrigin() -> CGFloat { 165 | return -FlowSlideMenuOptions.leftViewWidth - FlowCurveOptions.waveMargin 166 | } 167 | 168 | private func slideViewWidth() -> CGFloat { 169 | return FlowSlideMenuOptions.leftViewWidth 170 | } 171 | 172 | private func isLeftPointContainedWithinBezelRect(point: CGPoint) -> Bool{ 173 | var leftBezelRect: CGRect = CGRectZero 174 | var tempRect: CGRect = CGRectZero 175 | let bezelWidth: CGFloat = FlowSlideMenuOptions.leftBezelWidth 176 | 177 | CGRectDivide(view.bounds, &leftBezelRect, &tempRect, bezelWidth, CGRectEdge.MinXEdge) 178 | return CGRectContainsPoint(leftBezelRect, point) 179 | } 180 | 181 | private func addLeftGestures() { 182 | 183 | if (leftViewController != nil) { 184 | if leftPanGesture == nil { 185 | leftPanGesture = UIPanGestureRecognizer(target: self, action: "handleLeftPanGesture:") 186 | leftPanGesture!.delegate = self 187 | view.addGestureRecognizer(leftPanGesture!) 188 | } 189 | } 190 | } 191 | 192 | private func panLeftResultInfoForVelocity(velocity: CGPoint) -> PanInfo { 193 | 194 | let thresholdVelocity: CGFloat = 1000.0 195 | let pointOfNoReturn: CGFloat = CGFloat(floor(leftMinOrigin())) + FlowSlideMenuOptions.pointOfNoReturnWidth 196 | let leftOrigin: CGFloat = leftContainerView.frame.origin.x 197 | 198 | var panInfo: PanInfo = PanInfo(action: .Close, shouldBounce: false, velocity: 0.0) 199 | 200 | panInfo.action = leftOrigin <= pointOfNoReturn ? .Close : .Open; 201 | 202 | if velocity.x >= thresholdVelocity { 203 | panInfo.action = .Open 204 | panInfo.velocity = velocity.x 205 | } else if velocity.x <= (-1.0 * thresholdVelocity) { 206 | panInfo.action = .Close 207 | panInfo.velocity = velocity.x 208 | } 209 | 210 | return panInfo 211 | } 212 | 213 | public func isTagetViewController() -> Bool { 214 | // Function to determine the target ViewController 215 | // Please to override it if necessary 216 | return true 217 | } 218 | 219 | private func slideLeftForGestureRecognizer( gesture: UIGestureRecognizer, point:CGPoint) -> Bool{ 220 | return isLeftOpen() || FlowSlideMenuOptions.panFromBezel && isLeftPointContainedWithinBezelRect(point) 221 | } 222 | 223 | private func isPointContainedWithinLeftRect(point: CGPoint) -> Bool { 224 | return CGRectContainsPoint(leftContainerView.frame, point) 225 | } 226 | 227 | private func applyLeftTranslation(translation: CGPoint, toFrame:CGRect) -> CGRect { 228 | 229 | var newOrigin: CGFloat = toFrame.origin.x 230 | newOrigin += translation.x 231 | 232 | let minOrigin: CGFloat = leftMinOrigin() 233 | let maxOrigin: CGFloat = 0.0 234 | var newFrame: CGRect = toFrame 235 | 236 | if newOrigin < minOrigin { 237 | newOrigin = minOrigin 238 | } else if newOrigin > maxOrigin { 239 | newOrigin = maxOrigin 240 | } 241 | 242 | newFrame.origin.x = newOrigin 243 | return newFrame 244 | } 245 | 246 | private func setOpenWindowLevel() { 247 | if (FlowSlideMenuOptions.hideStatusBar) { 248 | dispatch_async(dispatch_get_main_queue(), { 249 | if let window = UIApplication.sharedApplication().keyWindow { 250 | window.windowLevel = UIWindowLevelStatusBar + 1 251 | } 252 | }) 253 | } 254 | } 255 | 256 | private func setCloseWindowLebel() { 257 | if (FlowSlideMenuOptions.hideStatusBar) { 258 | dispatch_async(dispatch_get_main_queue(), { 259 | if let window = UIApplication.sharedApplication().keyWindow { 260 | window.windowLevel = UIWindowLevelNormal 261 | } 262 | }) 263 | } 264 | } 265 | 266 | public func isLeftOpen() -> Bool { 267 | return leftContainerView.frame.origin.x == 0.0 268 | } 269 | 270 | public func isLeftHidden() -> Bool { 271 | return leftContainerView.frame.origin.x <= leftMinOrigin() 272 | } 273 | 274 | public func closeLeftWithVelocity(velocity: CGFloat) { 275 | 276 | leftContainerView.close() 277 | 278 | let xOrigin: CGFloat = leftContainerView.frame.origin.x 279 | let finalXOrigin: CGFloat = leftMinOrigin() 280 | 281 | var frame: CGRect = leftContainerView.frame; 282 | frame.origin.x = finalXOrigin 283 | 284 | var duration: NSTimeInterval = Double(FlowSlideMenuOptions.animationDuration) 285 | if velocity != 0.0 { 286 | duration = Double(fabs(xOrigin - finalXOrigin) / velocity) 287 | duration = Double(fmax(0.1, fmin(1.0, duration))) 288 | } 289 | 290 | UIView.animateWithDuration(duration, delay: 0.0, options: UIViewAnimationOptions.CurveEaseInOut, animations: { [weak self]() -> Void in 291 | if let strongSelf = self { 292 | strongSelf.leftContainerView.frame = frame 293 | strongSelf.opacityView.layer.opacity = 0.0 294 | strongSelf.mainContainerView.transform = CGAffineTransformMakeScale(1.0, 1.0) 295 | } 296 | }) { [weak self](Bool) -> Void in 297 | if let strongSelf = self { 298 | strongSelf.enableContentInteraction() 299 | strongSelf.leftViewController?.endAppearanceTransition() 300 | strongSelf.leftViewController?.view.alpha = 0 301 | } 302 | } 303 | } 304 | 305 | public override func openLeft (){ 306 | setOpenWindowLevel() 307 | leftViewController?.beginAppearanceTransition(isLeftHidden(), animated:false) 308 | openLeftFakeAnimation() 309 | self.leftContainerView.openAll() 310 | } 311 | 312 | public func updatePointTimer() 313 | { 314 | print(self.leftContainerView.frame.origin.x) 315 | } 316 | 317 | public override func closeLeft (){ 318 | 319 | leftViewController?.beginAppearanceTransition(isLeftHidden(), animated: true) 320 | closeLeftWithVelocity(0.0) 321 | setCloseWindowLebel() 322 | } 323 | 324 | public func openLeftFakeAnimation() 325 | { 326 | 327 | var frame = leftContainerView.frame; 328 | frame.origin.x = 0; 329 | 330 | self.leftContainerView.frame = frame 331 | self.opacityView.layer.opacity = Float(FlowSlideMenuOptions.contentViewOpacity) 332 | 333 | let duration: NSTimeInterval = Double(FlowSlideMenuOptions.animationDuration) 334 | UIView.animateWithDuration(duration, delay: 0.0, options: UIViewAnimationOptions.CurveEaseInOut, animations: { [weak self]() -> Void in 335 | if let strongSelf = self { 336 | strongSelf.mainContainerView.transform = CGAffineTransformMakeScale(FlowSlideMenuOptions.contentViewScale, FlowSlideMenuOptions.contentViewScale) 337 | } 338 | }) { [weak self](Bool) -> Void in 339 | if let strongSelf = self { 340 | strongSelf.disableContentInteraction() 341 | strongSelf.leftViewController?.endAppearanceTransition() 342 | } 343 | } 344 | } 345 | public func openLeftWithVelocity(velocity: CGFloat) { 346 | let xOrigin: CGFloat = leftContainerView.frame.origin.x 347 | let finalXOrigin: CGFloat = 0.0 348 | 349 | var frame = leftContainerView.frame; 350 | frame.origin.x = finalXOrigin; 351 | 352 | var duration: NSTimeInterval = Double(FlowSlideMenuOptions.animationDuration) 353 | if velocity != 0.0 { 354 | duration = Double(fabs(xOrigin - finalXOrigin) / velocity) 355 | duration = Double(fmax(0.1, fmin(1.0, duration))) 356 | } 357 | 358 | UIView.animateWithDuration(duration, delay: 0.0, options: UIViewAnimationOptions.CurveEaseInOut, animations: { [weak self]() -> Void in 359 | if let strongSelf = self { 360 | strongSelf.leftContainerView.frame = frame 361 | strongSelf.opacityView.layer.opacity = Float(FlowSlideMenuOptions.contentViewOpacity) 362 | strongSelf.mainContainerView.transform = CGAffineTransformMakeScale(FlowSlideMenuOptions.contentViewScale, FlowSlideMenuOptions.contentViewScale) 363 | } 364 | }) { [weak self](Bool) -> Void in 365 | if let strongSelf = self { 366 | strongSelf.disableContentInteraction() 367 | strongSelf.leftViewController?.endAppearanceTransition() 368 | } 369 | } 370 | 371 | self.leftContainerView.open() 372 | } 373 | 374 | private func applyLeftContentViewScale() { 375 | let openedLeftRatio: CGFloat = getOpenedLeftRatio() 376 | let scale: CGFloat = 1.0 - ((1.0 - FlowSlideMenuOptions.contentViewScale) * openedLeftRatio); 377 | mainContainerView.transform = CGAffineTransformMakeScale(scale, scale) 378 | } 379 | 380 | private func getOpenedLeftRatio() -> CGFloat { 381 | 382 | let width: CGFloat = leftContainerView.frame.size.width 383 | let currentPosition: CGFloat = leftContainerView.frame.origin.x - leftMinOrigin() 384 | return currentPosition / width 385 | } 386 | 387 | // MARK: - 388 | // MARK: public funs 389 | public func removeLeftGestures() { 390 | if leftPanGesture != nil { 391 | view.removeGestureRecognizer(leftPanGesture!) 392 | leftPanGesture = nil 393 | } 394 | } 395 | 396 | public func closeLeftNonAnimation(){ 397 | setCloseWindowLebel() 398 | let finalXOrigin: CGFloat = leftMinOrigin() 399 | var frame: CGRect = leftContainerView.frame; 400 | frame.origin.x = finalXOrigin 401 | leftContainerView.frame = frame 402 | opacityView.layer.opacity = 0.0 403 | mainContainerView.transform = CGAffineTransformMakeScale(1.0, 1.0) 404 | enableContentInteraction() 405 | self.leftContainerView.animating = false 406 | } 407 | 408 | private func disableContentInteraction() { 409 | mainContainerView.userInteractionEnabled = false 410 | } 411 | 412 | private func enableContentInteraction() { 413 | mainContainerView.userInteractionEnabled = true 414 | } 415 | 416 | // MARK: - 417 | // MARK: handleLeftPanGesture funs 418 | struct LeftPanState { 419 | static var frameAtStartOfPan: CGRect = CGRectZero 420 | static var startPointOfPan: CGPoint = CGPointZero 421 | static var wasOpenAtStartOfPan: Bool = false 422 | static var wasHiddenAtStartOfPan: Bool = false 423 | } 424 | 425 | func handleLeftPanGesture(panGesture: UIPanGestureRecognizer) { 426 | 427 | if !isTagetViewController() { 428 | return 429 | } 430 | 431 | switch panGesture.state { 432 | case UIGestureRecognizerState.Began: 433 | 434 | 435 | LeftPanState.wasHiddenAtStartOfPan = isLeftHidden() 436 | LeftPanState.wasOpenAtStartOfPan = isLeftOpen() 437 | 438 | 439 | self.leftContainerView.start() 440 | 441 | LeftPanState.frameAtStartOfPan = leftContainerView.frame 442 | LeftPanState.startPointOfPan = panGesture.locationInView(self.view) 443 | 444 | leftViewController?.beginAppearanceTransition(LeftPanState.wasHiddenAtStartOfPan, animated: true) 445 | 446 | setOpenWindowLevel() 447 | 448 | case UIGestureRecognizerState.Changed: 449 | 450 | 451 | let translation: CGPoint = panGesture.translationInView(panGesture.view) 452 | leftContainerView.updatePoint(CGPointMake(translation.x, translation.y + LeftPanState.startPointOfPan.y), orientation: Orientation.Left) 453 | leftContainerView.frame = applyLeftTranslation(translation, toFrame: LeftPanState.frameAtStartOfPan) 454 | applyLeftContentViewScale() 455 | 456 | case UIGestureRecognizerState.Ended: 457 | 458 | let velocity:CGPoint = panGesture.velocityInView(panGesture.view) 459 | let panInfo: PanInfo = panLeftResultInfoForVelocity(velocity) 460 | 461 | if panInfo.action == .Open { 462 | if !LeftPanState.wasHiddenAtStartOfPan { 463 | leftViewController?.beginAppearanceTransition(true, animated: true) 464 | } 465 | openLeftWithVelocity(panInfo.velocity) 466 | 467 | } else { 468 | if LeftPanState.wasHiddenAtStartOfPan { 469 | leftViewController?.beginAppearanceTransition(false, animated: true) 470 | } 471 | 472 | closeLeftWithVelocity(panInfo.velocity) 473 | setCloseWindowLebel() 474 | 475 | } 476 | 477 | default: 478 | break 479 | } 480 | } 481 | 482 | // MARK: - 483 | // MARK: UIGestureRecognizerDelegate 484 | public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool { 485 | 486 | let point: CGPoint = touch.locationInView(view) 487 | 488 | if gestureRecognizer == leftPanGesture { 489 | return slideLeftForGestureRecognizer(gestureRecognizer, point: point) 490 | } else if gestureRecognizer == leftTapGetsture { 491 | return isLeftOpen() && !isPointContainedWithinLeftRect(point) 492 | } 493 | 494 | return true 495 | } 496 | 497 | // MARK: - 498 | // MARK: LLFlowCurveViewDelegate 499 | public func flowViewStartAnimation(flow:LLFlowCurveView) 500 | { 501 | leftViewController?.view.alpha = 0.0 502 | } 503 | 504 | public func flowViewEndAnimation(flow:LLFlowCurveView) 505 | { 506 | UIView.animateWithDuration(0.3) { () -> Void in 507 | self.leftViewController?.view.alpha = 1 508 | } 509 | } 510 | } 511 | 512 | // MARK: - 513 | // MARK: UIViewController extension 514 | extension UIViewController { 515 | 516 | public func slideMenuController() -> LLFlowSlideMenuVC? { 517 | var viewController: UIViewController? = self 518 | while viewController != nil { 519 | if viewController is LLFlowSlideMenuVC { 520 | return viewController as? LLFlowSlideMenuVC 521 | } 522 | viewController = viewController?.parentViewController 523 | } 524 | return nil; 525 | } 526 | 527 | public func addLeftBarButtonWithImage(buttonImage: UIImage) { 528 | let leftButton: UIBarButtonItem = UIBarButtonItem(image: buttonImage, style: UIBarButtonItemStyle.Plain, target: self, action: "toggleLeft") 529 | navigationItem.leftBarButtonItem = leftButton; 530 | } 531 | 532 | public func openLeft() { 533 | slideMenuController()?.openLeft() 534 | } 535 | 536 | public func closeLeft() { 537 | slideMenuController()?.closeLeft() 538 | } 539 | 540 | // Please specify if you want menu gesuture give priority to than targetScrollView 541 | public func addPriorityToMenuGesuture(targetScrollView: UIScrollView) { 542 | guard let slideController = slideMenuController(), let recognizers = slideController.view.gestureRecognizers else { 543 | return 544 | } 545 | for recognizer in recognizers where recognizer is UIPanGestureRecognizer { 546 | targetScrollView.panGestureRecognizer.requireGestureRecognizerToFail(recognizer) 547 | } 548 | } 549 | } -------------------------------------------------------------------------------- /FlowSlideMenuTests/FlowSlideMenuTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlowSlideMenuTests.swift 3 | // FlowSlideMenuTests 4 | // 5 | // Created by LL on 15/11/3. 6 | // Copyright © 2015年 LL. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import FlowSlideMenu 11 | 12 | class FlowSlideMenuTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measureBlock { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /FlowSlideMenuTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /FlowSlideMenuUITests/FlowSlideMenuUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlowSlideMenuUITests.swift 3 | // FlowSlideMenuUITests 4 | // 5 | // Created by LL on 15/11/3. 6 | // Copyright © 2015年 LL. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class FlowSlideMenuUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | 18 | // In UI tests it is usually best to stop immediately when a failure occurs. 19 | continueAfterFailure = false 20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 21 | XCUIApplication().launch() 22 | 23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testExample() { 32 | // Use recording to get started writing UI tests. 33 | // Use XCTAssert and related functions to verify your tests produce the correct results. 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /FlowSlideMenuUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Bob Liu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FlowSlideMenu-SWIFT 2 | 3 | [![Platform](http://img.shields.io/badge/platform-ios-blue.svg?style=flat 4 | )](https://developer.apple.com/iphone/index.action) 5 | [![Language](http://img.shields.io/badge/language-swift-brightgreen.svg?style=flat 6 | )](https://developer.apple.com/swift) 7 | [![License](http://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat 8 | )](http://mit-license.org) 9 | 10 | # A FlowSlideMenu like this 11 | 12 | ![Showcase](effect.gif) 13 | ####Hits 14 | `0.1.7` is more smoothly for touch 15 | 16 | ####CocoaPods 17 | ``` 18 | pod 'FlowSlideMenu' 19 | ``` 20 | 21 | ##Usage 22 | 23 | ###Setup 24 | 25 | Add `import FlowSlideMenu` in your file 26 | 27 | In your app delegate: 28 | 29 | ```swift 30 | 31 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 32 | 33 | // create viewController code... 34 | 35 | let slideMenu = LLFlowSlideMenuVC(mainViewController: mainvc, leftViewController: leftvc) 36 | self.window?.rootViewController = slideMenu 37 | self.window?.makeKeyAndVisible() 38 | 39 | return true 40 | } 41 | ``` 42 | ## Inspired 43 | 44 | [SlideMenuControllerSwift](https://github.com/dekatotoro/SlideMenuControllerSwift) 45 | 46 | ## License 47 | 48 | FlowSlideMenu is available under the MIT license. See the LICENSE file for more info. 49 | 50 | ## Requirement 51 | 52 | iOS9 53 | 54 | ## Todo 55 | 56 | * Support to iOS7 57 | 58 | * Make code more better 59 | 60 | * Support to 4 orientation 61 | 62 | * Make doc 63 | 64 | 65 | ## Hope 66 | 67 | Guys,U can give me a star or submiting a issue for me~ I'will fix it soonly. 68 | 69 | -------------------------------------------------------------------------------- /effect.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatrixHero/FlowSlideMenu/88c3fc8e59c23e003c5346c2a17bba6572a103cc/effect.gif --------------------------------------------------------------------------------