diff --git a/CariocaMenu.podspec b/CariocaMenu.podspec new file mode 100644 index 0000000..d514338 --- /dev/null +++ b/CariocaMenu.podspec @@ -0,0 +1,16 @@ +Pod::Spec.new do |s| + s.name = 'CariocaMenu' + s.version = '1.0' + s.license = 'MIT' + s.summary = 'The fastest zero-tap iOS menu.' + s.homepage = 'https://github.com/' + s.social_media_url = 'https://twitter.com/arnaud_momo' + s.authors = { 'Arnaud Schloune' => 'arnaud.schloune@gmail.com' } + s.source = { :git => 'https://github.com/arn00s/cariocamenu.git', :tag => s.version } + + s.ios.deployment_target = '8.0' + + s.source_files = 'Library/*.swift' + + s.requires_arc = true +end diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c1da46d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ + The MIT License (MIT) + + Copyright (c) 2015 Arnaud Schloune + + 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: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + 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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9f09efb --- /dev/null +++ b/README.md @@ -0,0 +1,118 @@ +![CariocaMenu: The fastest zero-tap iOS menu] + +CariocaMenu is a simple, elegant, fast, modern, innovative, ..., navigation menu for your iOS app. + +## Features + +- [x] Accessible from a single swipe of the screen edge +- [x] Accessible with a tap on the indicator +- [x] Customisation for each side of the screen +- [x] Boomerang mode +- [x] Full AutoLayout +- [x] Easily customisable +- [x] Complete Documentation + +## Requirements + +- Autolayout +- iOS 8.0+ +- Xcode 7.0 + +## Communication + +- If you **need help**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/CariocaMenu). (Tag 'CariocaMenu') +- If you'd like to **ask a general question**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/CariocaMenu). +- If you **found a bug**, open an issue. +- If you **have a feature request**, open an issue. +- If you **want to contribute**, submit a pull request. + +## Installation + +### CocoaPods + +Coming soon + +### Manually + +If you prefer, you can integrate CariocaMenu into your project manually. + +#### Source File + +Simply add the `CariocaMenu.swift` source file directly into your project. + +--- + +## Usage + +### Creating your menu + +```swift +import CariocaMenu + +//Initialise the tableviewcontroller of the menu +let menuCtrl = self.storyboard?.instantiateViewControllerWithIdentifier("MyMenu") as! MyMenuContentController + +//Set the tableviewcontroller for the menu +menu = CariocaMenu(menuController: menuCtrl) + +``` + +### Adding it to a view + +```swift +menu.addInView(yourView) +``` + +### Boomerang + +A boomerang always comes back to it's original place (in theory). +By default, the boomerang is set to `None`. It means that the menu will stay where the user let it. +The two other boomerang options are : + +- `Vertical` : Will always come back at the same vertical position, but on the edge the user has chosen. +```swift +menu.boomerang = .Vertical +``` + +- `VerticalAndHorizontal` : Will always come back at the same position (vertical + same screen edge) +```swift +menu.boomerang = .VerticalAndHorizontal +``` + +## TODO + +- Add a `live tutorial` to indicate users how to get the most of this menu +- Unit tests / UIAutomation Tests +- Check support for Objective-C projects +- Add emoji support instead of the menu images +- Add live background update when preselecting a menu item. The goal is to preview the preselected view before selection (with a blur effect) + +## Known issues + +Check the ([GitHub issues](https://github.com/arn00s/CariocaMenu/issues)) + +## FAQ + +### Why should I use `CariocaMenu`? + +You're starting a new iOS app, and you want to innovate on the user experience. + +### Why the name `CariocaMenu`? + +I didn't want to use the same naming convention that EVERYONE uses. I could have named it `ASSuperCoolMenu`, but it sucks. +A `Carioca` is someone who lives in Rio de Janeiro. I lived there for two months, and I had this menu idea while walking on the beach ๐Ÿ˜ + +* * * + +## Contact + +- Twitter ([@arnaud_momo](https://twitter.com/arnaud_momo)) +- LinkedIn ([@arnaud Schloune](https://lu.linkedin.com/in/arnaud.schloune)) + +### Creator + +- [Arnaud Schloune](http://github.com/arn00s) ([@arnaud_momo](https://twitter.com/arnaud_momo)) + +## License + +CariocaMenu is released under the MIT license. See LICENSE for details. diff --git a/carioca.xcodeproj/project.pbxproj b/carioca.xcodeproj/project.pbxproj new file mode 100644 index 0000000..51f5d27 --- /dev/null +++ b/carioca.xcodeproj/project.pbxproj @@ -0,0 +1,621 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + FA02108C1B638333005693F0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA02108B1B638333005693F0 /* AppDelegate.swift */; }; + FA0210911B638333005693F0 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FA02108F1B638333005693F0 /* Main.storyboard */; }; + FA0210931B638333005693F0 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FA0210921B638333005693F0 /* Images.xcassets */; }; + FA0210961B638333005693F0 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = FA0210941B638333005693F0 /* LaunchScreen.xib */; }; + FA0210A21B638333005693F0 /* cariocaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA0210A11B638333005693F0 /* cariocaTests.swift */; }; + FA0210B41B6387C4005693F0 /* CariocaMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA0210B21B6387C4005693F0 /* CariocaMenu.swift */; }; + FA108FFD1BA46D8F00D37078 /* MyMenuContentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA108FFC1BA46D8F00D37078 /* MyMenuContentController.swift */; }; + FA108FFF1BA46E6800D37078 /* MyMenuTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA108FFE1BA46E6800D37078 /* MyMenuTableViewCell.swift */; }; + FA109A6B1BA7EA4600D4688A /* COSTouchVisualizerWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = FA109A6A1BA7EA4600D4688A /* COSTouchVisualizerWindow.m */; }; + FA29F58A1BA5814200D01F47 /* DemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA29F5881BA5814200D01F47 /* DemoViewController.swift */; }; + FA29F58C1BA588F700D01F47 /* DemoIntroViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA29F58B1BA588F700D01F47 /* DemoIntroViewController.swift */; }; + FA29F5911BA5A38300D01F47 /* DemoWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA29F5901BA5A38300D01F47 /* DemoWebViewController.swift */; }; + FA29F5941BA6905C00D01F47 /* whiteGradientBottom.png in Resources */ = {isa = PBXBuildFile; fileRef = FA29F5921BA6905C00D01F47 /* whiteGradientBottom.png */; }; + FA29F5951BA6905C00D01F47 /* whiteGradientTop.png in Resources */ = {isa = PBXBuildFile; fileRef = FA29F5931BA6905C00D01F47 /* whiteGradientTop.png */; }; + FA68F8A41BB694DF0061A2D6 /* about_indicator.png in Resources */ = {isa = PBXBuildFile; fileRef = FA68F8991BB694DF0061A2D6 /* about_indicator.png */; }; + FA68F8A51BB694DF0061A2D6 /* hamburger_indicator.png in Resources */ = {isa = PBXBuildFile; fileRef = FA68F89A1BB694DF0061A2D6 /* hamburger_indicator.png */; }; + FA68F8A61BB694DF0061A2D6 /* hello_indicator.png in Resources */ = {isa = PBXBuildFile; fileRef = FA68F89B1BB694DF0061A2D6 /* hello_indicator.png */; }; + FA68F8A71BB694DF0061A2D6 /* map_indicator.png in Resources */ = {isa = PBXBuildFile; fileRef = FA68F89C1BB694DF0061A2D6 /* map_indicator.png */; }; + FA68F8A81BB694DF0061A2D6 /* settings_indicator.png in Resources */ = {isa = PBXBuildFile; fileRef = FA68F89D1BB694DF0061A2D6 /* settings_indicator.png */; }; + FA68F8A91BB694DF0061A2D6 /* about_menu.png in Resources */ = {isa = PBXBuildFile; fileRef = FA68F89F1BB694DF0061A2D6 /* about_menu.png */; }; + FA68F8AA1BB694DF0061A2D6 /* hamburger_menu.png in Resources */ = {isa = PBXBuildFile; fileRef = FA68F8A01BB694DF0061A2D6 /* hamburger_menu.png */; }; + FA68F8AB1BB694DF0061A2D6 /* hello_menu.png in Resources */ = {isa = PBXBuildFile; fileRef = FA68F8A11BB694DF0061A2D6 /* hello_menu.png */; }; + FA68F8AC1BB694DF0061A2D6 /* map_menu.png in Resources */ = {isa = PBXBuildFile; fileRef = FA68F8A21BB694DF0061A2D6 /* map_menu.png */; }; + FA68F8AD1BB694DF0061A2D6 /* settings_menu.png in Resources */ = {isa = PBXBuildFile; fileRef = FA68F8A31BB694DF0061A2D6 /* settings_menu.png */; }; + FA68F8AF1BB6956B0061A2D6 /* DemoAboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA68F8AE1BB6956B0061A2D6 /* DemoAboutViewController.swift */; }; + FA68F8B11BB7EF3F0061A2D6 /* icon-small.png in Resources */ = {isa = PBXBuildFile; fileRef = FA68F8B01BB7EF3F0061A2D6 /* icon-small.png */; }; + FA68F8B31BB7F0630061A2D6 /* arnaud.schloune.jpg in Resources */ = {isa = PBXBuildFile; fileRef = FA68F8B21BB7F0630061A2D6 /* arnaud.schloune.jpg */; }; + FAC48D221BB513F70072AC0A /* DemoSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC48D211BB513F70072AC0A /* DemoSettingsViewController.swift */; }; + FAC48D241BB51BD70072AC0A /* DemoMapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC48D231BB51BD70072AC0A /* DemoMapViewController.swift */; }; + FAC48D261BB51CEC0072AC0A /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FAC48D251BB51CEC0072AC0A /* MapKit.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + FA02109C1B638333005693F0 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = FA02107E1B638333005693F0 /* Project object */; + proxyType = 1; + remoteGlobalIDString = FA0210851B638333005693F0; + remoteInfo = carioca; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + FA0210861B638333005693F0 /* carioca.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = carioca.app; sourceTree = BUILT_PRODUCTS_DIR; }; + FA02108A1B638333005693F0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + FA02108B1B638333005693F0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + FA0210901B638333005693F0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + FA0210921B638333005693F0 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + FA0210951B638333005693F0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; + FA02109B1B638333005693F0 /* cariocaTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = cariocaTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + FA0210A01B638333005693F0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + FA0210A11B638333005693F0 /* cariocaTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = cariocaTests.swift; sourceTree = ""; }; + FA0210B21B6387C4005693F0 /* CariocaMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CariocaMenu.swift; sourceTree = ""; }; + FA108FFC1BA46D8F00D37078 /* MyMenuContentController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyMenuContentController.swift; sourceTree = ""; }; + FA108FFE1BA46E6800D37078 /* MyMenuTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyMenuTableViewCell.swift; sourceTree = ""; }; + FA109A681BA7EA4500D4688A /* carioca-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "carioca-Bridging-Header.h"; sourceTree = ""; }; + FA109A691BA7EA4600D4688A /* COSTouchVisualizerWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = COSTouchVisualizerWindow.h; sourceTree = ""; }; + FA109A6A1BA7EA4600D4688A /* COSTouchVisualizerWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = COSTouchVisualizerWindow.m; sourceTree = ""; }; + FA29F5881BA5814200D01F47 /* DemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoViewController.swift; sourceTree = ""; }; + FA29F58B1BA588F700D01F47 /* DemoIntroViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoIntroViewController.swift; sourceTree = ""; }; + FA29F5901BA5A38300D01F47 /* DemoWebViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoWebViewController.swift; sourceTree = ""; }; + FA29F5921BA6905C00D01F47 /* whiteGradientBottom.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = whiteGradientBottom.png; sourceTree = ""; }; + FA29F5931BA6905C00D01F47 /* whiteGradientTop.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = whiteGradientTop.png; sourceTree = ""; }; + FA68F8991BB694DF0061A2D6 /* about_indicator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = about_indicator.png; sourceTree = ""; }; + FA68F89A1BB694DF0061A2D6 /* hamburger_indicator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = hamburger_indicator.png; sourceTree = ""; }; + FA68F89B1BB694DF0061A2D6 /* hello_indicator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = hello_indicator.png; sourceTree = ""; }; + FA68F89C1BB694DF0061A2D6 /* map_indicator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = map_indicator.png; sourceTree = ""; }; + FA68F89D1BB694DF0061A2D6 /* settings_indicator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = settings_indicator.png; sourceTree = ""; }; + FA68F89F1BB694DF0061A2D6 /* about_menu.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = about_menu.png; sourceTree = ""; }; + FA68F8A01BB694DF0061A2D6 /* hamburger_menu.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = hamburger_menu.png; sourceTree = ""; }; + FA68F8A11BB694DF0061A2D6 /* hello_menu.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = hello_menu.png; sourceTree = ""; }; + FA68F8A21BB694DF0061A2D6 /* map_menu.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = map_menu.png; sourceTree = ""; }; + FA68F8A31BB694DF0061A2D6 /* settings_menu.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = settings_menu.png; sourceTree = ""; }; + FA68F8AE1BB6956B0061A2D6 /* DemoAboutViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoAboutViewController.swift; sourceTree = ""; }; + FA68F8B01BB7EF3F0061A2D6 /* icon-small.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-small.png"; sourceTree = ""; }; + FA68F8B21BB7F0630061A2D6 /* arnaud.schloune.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = arnaud.schloune.jpg; sourceTree = ""; }; + FAC48D211BB513F70072AC0A /* DemoSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoSettingsViewController.swift; sourceTree = ""; }; + FAC48D231BB51BD70072AC0A /* DemoMapViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoMapViewController.swift; sourceTree = ""; }; + FAC48D251BB51CEC0072AC0A /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + FA0210831B638333005693F0 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + FAC48D261BB51CEC0072AC0A /* MapKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FA0210981B638333005693F0 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + FA02107D1B638333005693F0 = { + isa = PBXGroup; + children = ( + FAC48D251BB51CEC0072AC0A /* MapKit.framework */, + FA0210881B638333005693F0 /* carioca */, + FA02109E1B638333005693F0 /* cariocaTests */, + FA0210871B638333005693F0 /* Products */, + ); + sourceTree = ""; + }; + FA0210871B638333005693F0 /* Products */ = { + isa = PBXGroup; + children = ( + FA0210861B638333005693F0 /* carioca.app */, + FA02109B1B638333005693F0 /* cariocaTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + FA0210881B638333005693F0 /* carioca */ = { + isa = PBXGroup; + children = ( + FA02108B1B638333005693F0 /* AppDelegate.swift */, + FA0210B11B6387C4005693F0 /* Library */, + FA108FFC1BA46D8F00D37078 /* MyMenuContentController.swift */, + FA108FFE1BA46E6800D37078 /* MyMenuTableViewCell.swift */, + FA02108F1B638333005693F0 /* Main.storyboard */, + FA109A691BA7EA4600D4688A /* COSTouchVisualizerWindow.h */, + FA109A6A1BA7EA4600D4688A /* COSTouchVisualizerWindow.m */, + FA29F5691BA5636100D01F47 /* DemoControllers */, + FA29F56C1BA5636A00D01F47 /* Ressources */, + FA0210921B638333005693F0 /* Images.xcassets */, + FA0210941B638333005693F0 /* LaunchScreen.xib */, + FA0210891B638333005693F0 /* Supporting Files */, + FA109A681BA7EA4500D4688A /* carioca-Bridging-Header.h */, + ); + path = carioca; + sourceTree = ""; + }; + FA0210891B638333005693F0 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + FA02108A1B638333005693F0 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + FA02109E1B638333005693F0 /* cariocaTests */ = { + isa = PBXGroup; + children = ( + FA0210A11B638333005693F0 /* cariocaTests.swift */, + FA02109F1B638333005693F0 /* Supporting Files */, + ); + path = cariocaTests; + sourceTree = ""; + }; + FA02109F1B638333005693F0 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + FA0210A01B638333005693F0 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + FA0210B11B6387C4005693F0 /* Library */ = { + isa = PBXGroup; + children = ( + FA0210B21B6387C4005693F0 /* CariocaMenu.swift */, + ); + path = Library; + sourceTree = ""; + }; + FA29F5691BA5636100D01F47 /* DemoControllers */ = { + isa = PBXGroup; + children = ( + FA29F5881BA5814200D01F47 /* DemoViewController.swift */, + FA29F58B1BA588F700D01F47 /* DemoIntroViewController.swift */, + FA29F5901BA5A38300D01F47 /* DemoWebViewController.swift */, + FAC48D211BB513F70072AC0A /* DemoSettingsViewController.swift */, + FAC48D231BB51BD70072AC0A /* DemoMapViewController.swift */, + FA68F8AE1BB6956B0061A2D6 /* DemoAboutViewController.swift */, + ); + path = DemoControllers; + sourceTree = ""; + }; + FA29F56C1BA5636A00D01F47 /* Ressources */ = { + isa = PBXGroup; + children = ( + FA29F56D1BA5636A00D01F47 /* Images */, + ); + path = Ressources; + sourceTree = ""; + }; + FA29F56D1BA5636A00D01F47 /* Images */ = { + isa = PBXGroup; + children = ( + FA29F58D1BA593E700D01F47 /* demoContent */, + FA29F56E1BA5636A00D01F47 /* icons */, + ); + path = Images; + sourceTree = ""; + }; + FA29F56E1BA5636A00D01F47 /* icons */ = { + isa = PBXGroup; + children = ( + FA68F8981BB694DF0061A2D6 /* indicator */, + FA68F89E1BB694DF0061A2D6 /* menu */, + ); + path = icons; + sourceTree = ""; + }; + FA29F58D1BA593E700D01F47 /* demoContent */ = { + isa = PBXGroup; + children = ( + FA68F8B01BB7EF3F0061A2D6 /* icon-small.png */, + FA29F5921BA6905C00D01F47 /* whiteGradientBottom.png */, + FA29F5931BA6905C00D01F47 /* whiteGradientTop.png */, + FA68F8B21BB7F0630061A2D6 /* arnaud.schloune.jpg */, + ); + path = demoContent; + sourceTree = ""; + }; + FA68F8981BB694DF0061A2D6 /* indicator */ = { + isa = PBXGroup; + children = ( + FA68F8991BB694DF0061A2D6 /* about_indicator.png */, + FA68F89A1BB694DF0061A2D6 /* hamburger_indicator.png */, + FA68F89B1BB694DF0061A2D6 /* hello_indicator.png */, + FA68F89C1BB694DF0061A2D6 /* map_indicator.png */, + FA68F89D1BB694DF0061A2D6 /* settings_indicator.png */, + ); + path = indicator; + sourceTree = ""; + }; + FA68F89E1BB694DF0061A2D6 /* menu */ = { + isa = PBXGroup; + children = ( + FA68F89F1BB694DF0061A2D6 /* about_menu.png */, + FA68F8A01BB694DF0061A2D6 /* hamburger_menu.png */, + FA68F8A11BB694DF0061A2D6 /* hello_menu.png */, + FA68F8A21BB694DF0061A2D6 /* map_menu.png */, + FA68F8A31BB694DF0061A2D6 /* settings_menu.png */, + ); + path = menu; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + FA0210851B638333005693F0 /* carioca */ = { + isa = PBXNativeTarget; + buildConfigurationList = FA0210A51B638333005693F0 /* Build configuration list for PBXNativeTarget "carioca" */; + buildPhases = ( + FA0210821B638333005693F0 /* Sources */, + FA0210831B638333005693F0 /* Frameworks */, + FA0210841B638333005693F0 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = carioca; + productName = carioca; + productReference = FA0210861B638333005693F0 /* carioca.app */; + productType = "com.apple.product-type.application"; + }; + FA02109A1B638333005693F0 /* cariocaTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = FA0210A81B638333005693F0 /* Build configuration list for PBXNativeTarget "cariocaTests" */; + buildPhases = ( + FA0210971B638333005693F0 /* Sources */, + FA0210981B638333005693F0 /* Frameworks */, + FA0210991B638333005693F0 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + FA02109D1B638333005693F0 /* PBXTargetDependency */, + ); + name = cariocaTests; + productName = cariocaTests; + productReference = FA02109B1B638333005693F0 /* cariocaTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + FA02107E1B638333005693F0 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftMigration = 0700; + LastSwiftUpdateCheck = 0700; + LastUpgradeCheck = 0700; + ORGANIZATIONNAME = "Arnaud Schloune"; + TargetAttributes = { + FA0210851B638333005693F0 = { + CreatedOnToolsVersion = 6.4; + SystemCapabilities = { + com.apple.Maps.iOS = { + enabled = 1; + }; + }; + }; + FA02109A1B638333005693F0 = { + CreatedOnToolsVersion = 6.4; + TestTargetID = FA0210851B638333005693F0; + }; + }; + }; + buildConfigurationList = FA0210811B638333005693F0 /* Build configuration list for PBXProject "carioca" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = FA02107D1B638333005693F0; + productRefGroup = FA0210871B638333005693F0 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + FA0210851B638333005693F0 /* carioca */, + FA02109A1B638333005693F0 /* cariocaTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + FA0210841B638333005693F0 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FA68F8AD1BB694DF0061A2D6 /* settings_menu.png in Resources */, + FA0210911B638333005693F0 /* Main.storyboard in Resources */, + FA68F8AA1BB694DF0061A2D6 /* hamburger_menu.png in Resources */, + FA68F8A51BB694DF0061A2D6 /* hamburger_indicator.png in Resources */, + FA29F5941BA6905C00D01F47 /* whiteGradientBottom.png in Resources */, + FA0210961B638333005693F0 /* LaunchScreen.xib in Resources */, + FA29F5951BA6905C00D01F47 /* whiteGradientTop.png in Resources */, + FA68F8A61BB694DF0061A2D6 /* hello_indicator.png in Resources */, + FA0210931B638333005693F0 /* Images.xcassets in Resources */, + FA68F8B11BB7EF3F0061A2D6 /* icon-small.png in Resources */, + FA68F8AB1BB694DF0061A2D6 /* hello_menu.png in Resources */, + FA68F8A91BB694DF0061A2D6 /* about_menu.png in Resources */, + FA68F8A71BB694DF0061A2D6 /* map_indicator.png in Resources */, + FA68F8B31BB7F0630061A2D6 /* arnaud.schloune.jpg in Resources */, + FA68F8A41BB694DF0061A2D6 /* about_indicator.png in Resources */, + FA68F8A81BB694DF0061A2D6 /* settings_indicator.png in Resources */, + FA68F8AC1BB694DF0061A2D6 /* map_menu.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FA0210991B638333005693F0 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + FA0210821B638333005693F0 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FAC48D241BB51BD70072AC0A /* DemoMapViewController.swift in Sources */, + FAC48D221BB513F70072AC0A /* DemoSettingsViewController.swift in Sources */, + FA109A6B1BA7EA4600D4688A /* COSTouchVisualizerWindow.m in Sources */, + FA29F5911BA5A38300D01F47 /* DemoWebViewController.swift in Sources */, + FA02108C1B638333005693F0 /* AppDelegate.swift in Sources */, + FA29F58C1BA588F700D01F47 /* DemoIntroViewController.swift in Sources */, + FA0210B41B6387C4005693F0 /* CariocaMenu.swift in Sources */, + FA68F8AF1BB6956B0061A2D6 /* DemoAboutViewController.swift in Sources */, + FA108FFD1BA46D8F00D37078 /* MyMenuContentController.swift in Sources */, + FA108FFF1BA46E6800D37078 /* MyMenuTableViewCell.swift in Sources */, + FA29F58A1BA5814200D01F47 /* DemoViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FA0210971B638333005693F0 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FA0210A21B638333005693F0 /* cariocaTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + FA02109D1B638333005693F0 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = FA0210851B638333005693F0 /* carioca */; + targetProxy = FA02109C1B638333005693F0 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + FA02108F1B638333005693F0 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + FA0210901B638333005693F0 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + FA0210941B638333005693F0 /* LaunchScreen.xib */ = { + isa = PBXVariantGroup; + children = ( + FA0210951B638333005693F0 /* Base */, + ); + name = LaunchScreen.xib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + FA0210A31B638333005693F0 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.4; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + FA0210A41B638333005693F0 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.4; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_OBJC_BRIDGING_HEADER = ""; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + FA0210A61B638333005693F0 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + INFOPLIST_FILE = carioca/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.cariocamenu; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_OBJC_BRIDGING_HEADER = "carioca/carioca-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + FA0210A71B638333005693F0 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + INFOPLIST_FILE = carioca/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.cariocamenu; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_OBJC_BRIDGING_HEADER = "carioca/carioca-Bridging-Header.h"; + }; + name = Release; + }; + FA0210A91B638333005693F0 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = cariocaTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "mmmommmo.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/carioca.app/carioca"; + }; + name = Debug; + }; + FA0210AA1B638333005693F0 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = cariocaTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "mmmommmo.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/carioca.app/carioca"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + FA0210811B638333005693F0 /* Build configuration list for PBXProject "carioca" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + FA0210A31B638333005693F0 /* Debug */, + FA0210A41B638333005693F0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + FA0210A51B638333005693F0 /* Build configuration list for PBXNativeTarget "carioca" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + FA0210A61B638333005693F0 /* Debug */, + FA0210A71B638333005693F0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + FA0210A81B638333005693F0 /* Build configuration list for PBXNativeTarget "cariocaTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + FA0210A91B638333005693F0 /* Debug */, + FA0210AA1B638333005693F0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = FA02107E1B638333005693F0 /* Project object */; +} diff --git a/carioca.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/carioca.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1399544 --- /dev/null +++ b/carioca.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/carioca/AppDelegate.swift b/carioca/AppDelegate.swift new file mode 100644 index 0000000..73aca30 --- /dev/null +++ b/carioca/AppDelegate.swift @@ -0,0 +1,61 @@ +// +// AppDelegate.swift +// carioca +// +// Created by Arnaud Schloune +// Copyright (c) 2015 Arnaud Schloune. All rights reserved. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate, COSTouchVisualizerWindowDelegate { + + var window: UIWindow? +// lazy var window: UIWindow? = { +// var customWindow = COSTouchVisualizerWindow(frame: UIScreen.mainScreen().bounds) +// customWindow.touchVisualizerWindowDelegate = self +// +// customWindow.fillColor = UIColor(red:0.07, green:0.73, blue:0.86, alpha:1) +// customWindow.strokeColor = UIColor.clearColor() +// customWindow.touchAlpha = 0.4 +// +// customWindow.rippleFillColor = UIColor(red:0.98, green:0.68, blue:0.22, alpha:1) +// customWindow.rippleStrokeColor = UIColor.clearColor() +// customWindow.rippleAlpha = 0.4 +// +// return customWindow +// }() + + + func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { + // Override point for customization after application launch. + return true + } + + func applicationWillResignActive(application: UIApplication) { + // 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. + // 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. + } + + func applicationDidEnterBackground(application: UIApplication) { + // 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. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. + } + + func applicationWillEnterForeground(application: UIApplication) { + // 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. + } + + func applicationDidBecomeActive(application: UIApplication) { + // 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. + } + + func applicationWillTerminate(application: UIApplication) { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. + } + + func touchVisualizerWindowShouldAlwaysShowFingertip(window: COSTouchVisualizerWindow!) -> Bool { + return true + } +} diff --git a/carioca/AppIcon.appiconset/Contents.json b/carioca/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..44676ae --- /dev/null +++ b/carioca/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-40.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-40@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-60@2x.png", + "scale" : "2x" + }, + { + "size" : "72x72", + "idiom" : "ipad", + "filename" : "Icon-72.png", + "scale" : "1x" + }, + { + "size" : "72x72", + "idiom" : "ipad", + "filename" : "Icon-72@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-76.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-76@2x.png", + "scale" : "2x" + }, + { + "size" : "50x50", + "idiom" : "ipad", + "filename" : "Icon-Small-50.png", + "scale" : "1x" + }, + { + "size" : "50x50", + "idiom" : "ipad", + "filename" : "Icon-Small-50@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-Small.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-Small@2x.png", + "scale" : "2x" + }, + { + "size" : "57x57", + "idiom" : "iphone", + "filename" : "Icon.png", + "scale" : "1x" + }, + { + "size" : "57x57", + "idiom" : "iphone", + "filename" : "Icon@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-Small@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-60@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-40@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-Small.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-Small@2x.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/carioca/AppIcon.appiconset/Icon-40.png b/carioca/AppIcon.appiconset/Icon-40.png new file mode 100644 index 0000000..ef2ab8e Binary files /dev/null and b/carioca/AppIcon.appiconset/Icon-40.png differ diff --git a/carioca/AppIcon.appiconset/Icon-40@2x.png b/carioca/AppIcon.appiconset/Icon-40@2x.png new file mode 100644 index 0000000..97b255c Binary files /dev/null and b/carioca/AppIcon.appiconset/Icon-40@2x.png differ diff --git a/carioca/AppIcon.appiconset/Icon-40@3x.png b/carioca/AppIcon.appiconset/Icon-40@3x.png new file mode 100644 index 0000000..3b9cf3e Binary files /dev/null and b/carioca/AppIcon.appiconset/Icon-40@3x.png differ diff --git a/carioca/AppIcon.appiconset/Icon-60@2x.png b/carioca/AppIcon.appiconset/Icon-60@2x.png new file mode 100644 index 0000000..3b9cf3e Binary files /dev/null and b/carioca/AppIcon.appiconset/Icon-60@2x.png differ diff --git a/carioca/AppIcon.appiconset/Icon-60@3x.png b/carioca/AppIcon.appiconset/Icon-60@3x.png new file mode 100644 index 0000000..91cd6f3 Binary files /dev/null and b/carioca/AppIcon.appiconset/Icon-60@3x.png differ diff --git a/carioca/AppIcon.appiconset/Icon-72.png b/carioca/AppIcon.appiconset/Icon-72.png new file mode 100644 index 0000000..4a35b4a Binary files /dev/null and b/carioca/AppIcon.appiconset/Icon-72.png differ diff --git a/carioca/AppIcon.appiconset/Icon-72@2x.png b/carioca/AppIcon.appiconset/Icon-72@2x.png new file mode 100644 index 0000000..09f09dc Binary files /dev/null and b/carioca/AppIcon.appiconset/Icon-72@2x.png differ diff --git a/carioca/AppIcon.appiconset/Icon-76.png b/carioca/AppIcon.appiconset/Icon-76.png new file mode 100644 index 0000000..fbb2b49 Binary files /dev/null and b/carioca/AppIcon.appiconset/Icon-76.png differ diff --git a/carioca/AppIcon.appiconset/Icon-76@2x.png b/carioca/AppIcon.appiconset/Icon-76@2x.png new file mode 100644 index 0000000..1db2039 Binary files /dev/null and b/carioca/AppIcon.appiconset/Icon-76@2x.png differ diff --git a/carioca/AppIcon.appiconset/Icon-Small-50.png b/carioca/AppIcon.appiconset/Icon-Small-50.png new file mode 100644 index 0000000..858f387 Binary files /dev/null and b/carioca/AppIcon.appiconset/Icon-Small-50.png differ diff --git a/carioca/AppIcon.appiconset/Icon-Small-50@2x.png b/carioca/AppIcon.appiconset/Icon-Small-50@2x.png new file mode 100644 index 0000000..34efe9f Binary files /dev/null and b/carioca/AppIcon.appiconset/Icon-Small-50@2x.png differ diff --git a/carioca/AppIcon.appiconset/Icon-Small.png b/carioca/AppIcon.appiconset/Icon-Small.png new file mode 100644 index 0000000..55f4332 Binary files /dev/null and b/carioca/AppIcon.appiconset/Icon-Small.png differ diff --git a/carioca/AppIcon.appiconset/Icon-Small@2x.png b/carioca/AppIcon.appiconset/Icon-Small@2x.png new file mode 100644 index 0000000..193ec93 Binary files /dev/null and b/carioca/AppIcon.appiconset/Icon-Small@2x.png differ diff --git a/carioca/AppIcon.appiconset/Icon-Small@3x.png b/carioca/AppIcon.appiconset/Icon-Small@3x.png new file mode 100644 index 0000000..a9978d1 Binary files /dev/null and b/carioca/AppIcon.appiconset/Icon-Small@3x.png differ diff --git a/carioca/AppIcon.appiconset/Icon.png b/carioca/AppIcon.appiconset/Icon.png new file mode 100644 index 0000000..5ed7a82 Binary files /dev/null and b/carioca/AppIcon.appiconset/Icon.png differ diff --git a/carioca/AppIcon.appiconset/Icon@2x.png b/carioca/AppIcon.appiconset/Icon@2x.png new file mode 100644 index 0000000..6437652 Binary files /dev/null and b/carioca/AppIcon.appiconset/Icon@2x.png differ diff --git a/carioca/Base.lproj/LaunchScreen.xib b/carioca/Base.lproj/LaunchScreen.xib new file mode 100644 index 0000000..84242f7 --- /dev/null +++ b/carioca/Base.lproj/LaunchScreen.xib @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/carioca/Base.lproj/Main.storyboard b/carioca/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f31324b --- /dev/null +++ b/carioca/Base.lproj/Main.storyboard @@ -0,0 +1,654 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The fastest, "zero-tap" menu. + +This is a sample app to show the features of the Carioca Menu. + +I invite all of you to contribute to the project, share your ideas, report bugs, .... +Every feedback is welcome ๐Ÿ˜Ž + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + I'm Arnaud. +iOS developer & designer in Luxembourg. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/carioca/COSTouchVisualizerWindow.h b/carioca/COSTouchVisualizerWindow.h new file mode 100755 index 0000000..a216590 --- /dev/null +++ b/carioca/COSTouchVisualizerWindow.h @@ -0,0 +1,42 @@ +// +// COSTouchVisualizerWindow.h +// TouchVisualizer +// +// Created by Joe Blau on 3/22/14. +// Copyright (c) 2014 conopsys. All rights reserved. +// +#include + +@protocol COSTouchVisualizerWindowDelegate; + +@interface COSTouchVisualizerWindow : UIWindow + +@property (nonatomic, readonly, getter=isActive) BOOL active; +@property (nonatomic, weak) id touchVisualizerWindowDelegate; + +// Touch effects +@property (nonatomic) UIImage *touchImage; +@property (nonatomic) CGFloat touchAlpha; +@property (nonatomic) NSTimeInterval fadeDuration; +@property (nonatomic) UIColor *strokeColor; +@property (nonatomic) UIColor *fillColor; + +// Ripple Effects +@property (nonatomic) UIImage *rippleImage; +@property (nonatomic) CGFloat rippleAlpha; +@property (nonatomic) NSTimeInterval rippleFadeDuration; +@property (nonatomic) UIColor *rippleStrokeColor; +@property (nonatomic) UIColor *rippleFillColor; + +@property (nonatomic) BOOL stationaryMorphEnabled; // default: YES + +@end + +@protocol COSTouchVisualizerWindowDelegate + +@optional + +- (BOOL)touchVisualizerWindowShouldShowFingertip:(COSTouchVisualizerWindow *)window; +- (BOOL)touchVisualizerWindowShouldAlwaysShowFingertip:(COSTouchVisualizerWindow *)window; + +@end diff --git a/carioca/COSTouchVisualizerWindow.m b/carioca/COSTouchVisualizerWindow.m new file mode 100755 index 0000000..9664716 --- /dev/null +++ b/carioca/COSTouchVisualizerWindow.m @@ -0,0 +1,414 @@ +// +// COSTouchVisualizerWindow.m +// TouchVisualizer +// +// Created by Joe Blau on 3/22/14. +// Copyright (c) 2014 conopsys. All rights reserved. +// + +#import "COSTouchVisualizerWindow.h" + +#pragma mark - Touch Visualizer Window + +@interface TouchVisualizerWindow : UIWindow +@end + +@implementation TouchVisualizerWindow + +// UIKit tries to get the rootViewController from the overlay window. +// Instead, try to find the rootViewController on some other +// application window. +// Fixes problems with status bar hiding, because it considers the +// overlay window a candidate for controlling the status bar. +- (UIViewController *)rootViewController { + for (UIWindow *window in [[UIApplication sharedApplication] windows]) { + if (self == window) + continue; + UIViewController *realRootViewController = window.rootViewController; + if (realRootViewController != nil) + return realRootViewController; + } + return [super rootViewController]; +} + +@end + +#pragma mark - Conopsys Touch Spot View + +@interface COSTouchSpotView : UIImageView + +@property (nonatomic) NSTimeInterval timestamp; +@property (nonatomic) BOOL shouldAutomaticallyRemoveAfterTimeout; +@property (nonatomic, getter=isFadingOut) BOOL fadingOut; + +@end + +@implementation COSTouchSpotView +@end + +#pragma mark - Conopsys Touch Visualizer Window + +@interface COSTouchVisualizerWindow () + +@property (nonatomic) UIWindow *overlayWindow; +@property (nonatomic) UIViewController *overlayWindowViewController; +@property (nonatomic) BOOL fingerTipRemovalScheduled; +@property (nonatomic) NSTimer *timer; +@property (nonatomic) NSSet *allTouches; + +- (void)COSTouchVisualizerWindow_commonInit; +- (void)scheduleFingerTipRemoval; +- (void)cancelScheduledFingerTipRemoval; +- (void)removeInactiveFingerTips; +- (void)removeFingerTipWithHash:(NSUInteger)hash animated:(BOOL)animated; +- (BOOL)shouldAutomaticallyRemoveFingerTipForTouch:(UITouch *)touch; + +@end + +@implementation COSTouchVisualizerWindow + +- (id)initWithCoder:(NSCoder *)decoder { + // This covers NIB-loaded windows. + if (self = [super initWithCoder:decoder]) + [self COSTouchVisualizerWindow_commonInit]; + return self; +} + +- (id)initWithFrame:(CGRect)rect { + // This covers programmatically-created windows. + if (self = [super initWithFrame:rect]) + [self COSTouchVisualizerWindow_commonInit]; + return self; +} + +- (void)COSTouchVisualizerWindow_commonInit { + self.strokeColor = [UIColor blackColor]; + self.fillColor = [UIColor whiteColor]; + self.rippleStrokeColor = [UIColor whiteColor]; + self.rippleFillColor = [UIColor blueColor]; + self.touchAlpha = 0.5; + self.fadeDuration = 0.3; + self.rippleAlpha = 0.2; + self.rippleFadeDuration = 0.2; + self.stationaryMorphEnabled = YES; +} + +#pragma mark - Touch / Ripple and Images + +- (UIImage *)touchImage { + if (!_touchImage) { + UIBezierPath *clipPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 50.0, 50.0)]; + + UIGraphicsBeginImageContextWithOptions(clipPath.bounds.size, NO, 0); + + UIBezierPath *drawPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(25.0, 25.0) + radius:22.0 + startAngle:0 + endAngle:2 * M_PI + clockwise:YES]; + + drawPath.lineWidth = 2.0; + + [self.strokeColor setStroke]; + [self.fillColor setFill]; + + [drawPath stroke]; + [drawPath fill]; + + [clipPath addClip]; + + _touchImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + } + return _touchImage; +} + +- (UIImage *)rippleImage { + if (!_rippleImage) { + UIBezierPath *clipPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 50.0, 50.0)]; + + UIGraphicsBeginImageContextWithOptions(clipPath.bounds.size, NO, 0); + + UIBezierPath *drawPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(25.0, 25.0) + radius:22.0 + startAngle:0 + endAngle:2 * M_PI + clockwise:YES]; + + drawPath.lineWidth = 2.0; + + [self.rippleStrokeColor setStroke]; + [self.rippleFillColor setFill]; + + [drawPath stroke]; + [drawPath fill]; + + [clipPath addClip]; + + _rippleImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + } + return _rippleImage; +} + +#pragma mark - Active + +- (BOOL)anyScreenIsMirrored { + if (![UIScreen instancesRespondToSelector:@selector(mirroredScreen)]) + return NO; + + for (UIScreen *screen in [UIScreen screens]) { + if ([screen mirroredScreen] != nil) { + return YES; + } + } + return NO; +} + +- (BOOL)isActive { + // should show fingertip or not + if (![self.touchVisualizerWindowDelegate respondsToSelector:@selector(touchVisualizerWindowShouldShowFingertip:)] || + [self.touchVisualizerWindowDelegate touchVisualizerWindowShouldShowFingertip:self]) { + // should always show or only when any screen is mirrored. + return (([self.touchVisualizerWindowDelegate respondsToSelector:@selector(touchVisualizerWindowShouldAlwaysShowFingertip:)] && + [self.touchVisualizerWindowDelegate touchVisualizerWindowShouldAlwaysShowFingertip:self]) || + [self anyScreenIsMirrored]); + } else { + return NO; + } +} + +#pragma mark - UIWindow overrides + +- (void)sendEvent:(UIEvent *)event { + if (self.active) { + self.allTouches = [event allTouches]; + for (UITouch *touch in [self.allTouches allObjects]) { + switch (touch.phase) { + case UITouchPhaseBegan: + case UITouchPhaseMoved: { + // Generate ripples + COSTouchSpotView *rippleView = [[COSTouchSpotView alloc] initWithImage:self.rippleImage]; + [self.overlayWindow addSubview:rippleView]; + + rippleView.alpha = self.rippleAlpha; + rippleView.center = [touch locationInView:self.overlayWindow]; + + [UIView animateWithDuration:self.rippleFadeDuration + delay:0.0 + options:UIViewAnimationOptionCurveEaseIn // See other + // options + animations:^{ + [rippleView setAlpha:0.0]; + [rippleView setFrame:CGRectInset(rippleView.frame, 25, 25)]; + } completion:^(BOOL finished) { + [rippleView removeFromSuperview]; + }]; + } + case UITouchPhaseStationary: { + COSTouchSpotView *touchView = (COSTouchSpotView *)[self.overlayWindow viewWithTag:touch.hash]; + + if (touch.phase != UITouchPhaseStationary && touchView != nil && [touchView isFadingOut]) { + [self.timer invalidate]; + [touchView removeFromSuperview]; + touchView = nil; + } + + if (touchView == nil && touch.phase != UITouchPhaseStationary) { + touchView = [[COSTouchSpotView alloc] initWithImage:self.touchImage]; + [self.overlayWindow addSubview:touchView]; + + if (self.stationaryMorphEnabled) { + self.timer = [NSTimer scheduledTimerWithTimeInterval:0.6 + target:self + selector:@selector(performMorph:) + userInfo:touchView + repeats:YES]; + } + } + if (![touchView isFadingOut]) { + touchView.alpha = self.touchAlpha; + touchView.center = [touch locationInView:self.overlayWindow]; + touchView.tag = touch.hash; + touchView.timestamp = touch.timestamp; + touchView.shouldAutomaticallyRemoveAfterTimeout = [self shouldAutomaticallyRemoveFingerTipForTouch:touch]; + } + break; + } + case UITouchPhaseEnded: + case UITouchPhaseCancelled: { + [self removeFingerTipWithHash:touch.hash animated:YES]; + break; + } + } + } + } + + [super sendEvent:event]; + [self scheduleFingerTipRemoval]; // We may not see all UITouchPhaseEnded/UITouchPhaseCancelled events. +} + +#pragma mark - Private + +- (UIWindow *)overlayWindow { + if (!_overlayWindow) { + _overlayWindow = [[TouchVisualizerWindow alloc] initWithFrame:self.frame]; + _overlayWindow.userInteractionEnabled = NO; + _overlayWindow.windowLevel = UIWindowLevelStatusBar; + _overlayWindow.backgroundColor = [UIColor clearColor]; + _overlayWindow.hidden = NO; + } + return _overlayWindow; +} + +- (void)scheduleFingerTipRemoval { + + if (self.fingerTipRemovalScheduled) + return; + self.fingerTipRemovalScheduled = YES; + [self performSelector:@selector(removeInactiveFingerTips) + withObject:nil + afterDelay:0.1]; +} + +- (void)cancelScheduledFingerTipRemoval { + self.fingerTipRemovalScheduled = YES; + [NSObject cancelPreviousPerformRequestsWithTarget:self + selector:@selector(removeInactiveFingerTips) + object:nil]; +} + +- (void)removeInactiveFingerTips { + self.fingerTipRemovalScheduled = NO; + + NSTimeInterval now = [[NSProcessInfo processInfo] systemUptime]; + const CGFloat REMOVAL_DELAY = 0.2; + for (COSTouchSpotView *touchView in [self.overlayWindow subviews]) { + if (![touchView isKindOfClass:[COSTouchSpotView class]]) + continue; + + if (touchView.shouldAutomaticallyRemoveAfterTimeout && now > touchView.timestamp + REMOVAL_DELAY) + [self removeFingerTipWithHash:touchView.tag animated:YES]; + } + + if ([[self.overlayWindow subviews] count]) + [self scheduleFingerTipRemoval]; +} + +- (void)removeFingerTipWithHash:(NSUInteger)hash animated:(BOOL)animated { + COSTouchSpotView *touchView = (COSTouchSpotView *)[self.overlayWindow viewWithTag:hash]; + if (touchView == nil) + return; + + if ([touchView isFadingOut]) + return; + + BOOL animationsWereEnabled = [UIView areAnimationsEnabled]; + + if (animated) { + [UIView setAnimationsEnabled:YES]; + [UIView beginAnimations:nil context:nil]; + [UIView setAnimationDuration:self.fadeDuration]; + } + + touchView.frame = CGRectMake(touchView.center.x - touchView.frame.size.width, + touchView.center.y - touchView.frame.size.height, + touchView.frame.size.width * 2, touchView.frame.size.height * 2); + + touchView.alpha = 0.0; + + if (animated) { + [UIView commitAnimations]; + [UIView setAnimationsEnabled:animationsWereEnabled]; + } + + touchView.fadingOut = YES; + [touchView performSelector:@selector(removeFromSuperview) + withObject:nil + afterDelay:self.fadeDuration]; +} + +- (BOOL)shouldAutomaticallyRemoveFingerTipForTouch:(UITouch *)touch; +{ + // We don't reliably get UITouchPhaseEnded or UITouchPhaseCancelled + // events via -sendEvent: for certain touch events. Known cases + // include swipe-to-delete on a table view row, and tap-to-cancel + // swipe to delete. We automatically remove their associated + // fingertips after a suitable timeout. + // + // It would be much nicer if we could remove all touch events after + // a suitable time out, but then we'll prematurely remove touch and + // hold events that are picked up by gesture recognizers (since we + // don't use UITouchPhaseStationary touches for those. *sigh*). So we + // end up with this more complicated setup. + + UIView *view = [touch view]; + view = [view hitTest:[touch locationInView:view] withEvent:nil]; + + while (view != nil) { + if ([view isKindOfClass:[UITableViewCell class]]) { + for (UIGestureRecognizer *recognizer in [touch gestureRecognizers]) { + if ([recognizer isKindOfClass:[UISwipeGestureRecognizer class]]) + return YES; + } + } + + if ([view isKindOfClass:[UITableView class]]) { + if ([[touch gestureRecognizers] count] == 0) + return YES; + } + view = view.superview; + } + + return NO; +} + +- (void)performMorph:(NSTimer *)theTimer { + UIView *view = (UIView *)[theTimer userInfo]; + NSTimeInterval duration = .4; + NSTimeInterval delay = 0; + // Start + view.alpha = self.touchAlpha; + view.transform = CGAffineTransformMakeScale(1, 1); + [UIView animateWithDuration:duration / 4 + delay:delay + options:0 + animations:^{ + // End + view.transform = CGAffineTransformMakeScale(1, 1.2); + } + completion:^(BOOL finished) { + [UIView animateWithDuration:duration / 4 + delay:0 + options:0 + animations:^{ + // End + view.transform = CGAffineTransformMakeScale(1.2, 0.9); + } + completion:^(BOOL finished) { + [UIView animateWithDuration:duration / 4 + delay:0 + options:0 + animations:^{ + // End + view.transform = CGAffineTransformMakeScale(0.9, 0.9); + } + completion:^(BOOL finished) { + [UIView animateWithDuration:duration / 4 + delay:0 + options:0 + animations:^{ + // End + view.transform = CGAffineTransformMakeScale(1, 1); + } + completion:^(BOOL finished){ + // If there are no touches, remove this morping touch + if (self.allTouches.count == 0) + [view removeFromSuperview]; + }]; + }]; + }]; + }]; +} + +@end \ No newline at end of file diff --git a/carioca/DemoControllers/DemoAboutViewController.swift b/carioca/DemoControllers/DemoAboutViewController.swift new file mode 100644 index 0000000..3303a41 --- /dev/null +++ b/carioca/DemoControllers/DemoAboutViewController.swift @@ -0,0 +1,19 @@ + +import UIKit + +class DemoAboutViewController: UIViewController{ + + @IBAction func actionTwitter(sender: AnyObject) { + url("https://twitter.com/arnaud_momo") + } + @IBAction func actionLinkedin(sender: AnyObject) { + url("https://lu.linkedin.com/in/arnaudschloune") + } + @IBAction func actionGithub(sender: AnyObject) { + url("https://github.com/arn00s/cariocamenu") + } + + func url(urlString:String){ + UIApplication.sharedApplication().openURL(NSURL(string: urlString)!) + } +} \ No newline at end of file diff --git a/carioca/DemoControllers/DemoIntroViewController.swift b/carioca/DemoControllers/DemoIntroViewController.swift new file mode 100644 index 0000000..2b2e565 --- /dev/null +++ b/carioca/DemoControllers/DemoIntroViewController.swift @@ -0,0 +1,6 @@ + +import UIKit + +class DemoIntroViewController: UIViewController{ + +} \ No newline at end of file diff --git a/carioca/DemoControllers/DemoMapViewController.swift b/carioca/DemoControllers/DemoMapViewController.swift new file mode 100644 index 0000000..606c674 --- /dev/null +++ b/carioca/DemoControllers/DemoMapViewController.swift @@ -0,0 +1,13 @@ + +import UIKit + +class DemoMapViewController: UIViewController{ + + + override func viewDidLoad() { + } + + override func viewDidAppear(animated: Bool) { + + } +} \ No newline at end of file diff --git a/carioca/DemoControllers/DemoSettingsViewController.swift b/carioca/DemoControllers/DemoSettingsViewController.swift new file mode 100644 index 0000000..d9db634 --- /dev/null +++ b/carioca/DemoControllers/DemoSettingsViewController.swift @@ -0,0 +1,132 @@ + +import UIKit + +class DemoSettingsViewController: UITableViewController{ + + weak var menu:CariocaMenu? + + var settingsNames = Array() + + override func viewDidLoad() { + settingsNames.append("Screen edges") + settingsNames.append("Dragging") + settingsNames.append("Boomerang") + } + + override func viewWillAppear(animated: Bool) { + self.tableView.reloadData() + } + + // MARK: - Table view data source + + override func numberOfSectionsInTableView(tableView: UITableView) -> Int { + return 1 + } + + override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return settingsNames.count + } + + override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { + + let cellTypeIdentifier = indexPath.row == 2 ? "cellSegment" : "cellSwitch" + let cell = tableView.dequeueReusableCellWithIdentifier(cellTypeIdentifier, forIndexPath: indexPath) + + //set the title in the cell + let title = cell.viewWithTag(10) as! UILabel + title.text = settingsNames[indexPath.row] + + let description = cell.viewWithTag(20) as! UITextView + var descriptionText = "" + + switch indexPath.row { + + //edges + case 0: + let switcher = cell.viewWithTag(30) as! UISwitch + switcher.addTarget(self, action: Selector("edgesSwitchChanged:"), forControlEvents: .ValueChanged) + switcher.setOn((menu?.isAlwaysOnScreen)!, animated: true) + descriptionText = (menu?.isAlwaysOnScreen)! ? "The menu stays on screen." : "The menu is completely free !!" + + break + + //dragging + case 1: + let switcher = cell.viewWithTag(30) as! UISwitch + switcher.addTarget(self, action: Selector("dragSwitchChanged:"), forControlEvents: .ValueChanged) + switcher.setOn((menu?.isDraggableVertically)!, animated: true) + descriptionText = (menu?.isDraggableVertically)! ? "The user can drag the indicators." : "No dragging of the indicators." + + break + + //boomerang + default: + let segment = cell.viewWithTag(30) as! UISegmentedControl + segment.addTarget(self, action: Selector("boomerangSegmentChanged:"), forControlEvents: .ValueChanged) + + if menu?.boomerang == .Vertical { + segment.selectedSegmentIndex = 1 + descriptionText = "The menu always comes back at it's initial Y position." + } + else if menu?.boomerang == .VerticalAndHorizontal { + segment.selectedSegmentIndex = 2 + descriptionText = "The menu always comes back in place ๐Ÿ‘" + } + else { + segment.selectedSegmentIndex = 0 + descriptionText = "By default, there is no boomerang set to the menu." + } + + break + } + + description.text = descriptionText + + return cell + } + + override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { + return indexPath.row == 2 ? 140 : 90.0 + } + + override func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { + let footerView = UIView(frame: CGRectMake(0, 0, tableView.frame.size.width, 0)) + footerView.backgroundColor = UIColor.clearColor() + return footerView + } + + override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + let view = UIView(frame: CGRectMake(0, 0, tableView.frame.size.width, 80)) + view.backgroundColor = UIColor.clearColor() + return view + } + + func boomerangSegmentChanged(sender: UISegmentedControl) { + switch sender.selectedSegmentIndex { + case 1: + menu?.boomerang = .Vertical + break; + case 2: + menu?.boomerang = .VerticalAndHorizontal + default: + menu?.boomerang = .None + break; + } + + self.tableView.reloadData() + } + + func edgesSwitchChanged(sender: UISwitch) { + menu?.isAlwaysOnScreen = (menu?.isAlwaysOnScreen == true) ? false : true + self.tableView.reloadData() + } + + func dragSwitchChanged(sender: UISwitch) { + menu?.isDraggableVertically = (menu?.isDraggableVertically == true) ? false : true + self.tableView.reloadData() + } + + override func viewWillDisappear(animated: Bool) { + menu = nil + } +} \ No newline at end of file diff --git a/carioca/DemoControllers/DemoViewController.swift b/carioca/DemoControllers/DemoViewController.swift new file mode 100644 index 0000000..9da6c70 --- /dev/null +++ b/carioca/DemoControllers/DemoViewController.swift @@ -0,0 +1,183 @@ + +import UIKit + +class DemoViewController: UIViewController, CariocaMenuDelegate { + + var menu:CariocaMenu? + var logging = false + var demoContentController:UIViewController! + + override func viewDidLoad() { + super.viewDidLoad() + + self.view.translatesAutoresizingMaskIntoConstraints = false + + //Initialise the tableviewcontroller of the menu + let menuCtrl = self.storyboard?.instantiateViewControllerWithIdentifier("MyMenu") as! MyMenuContentController + + //Set the tableviewcontroller for the shared carioca menu + menu = CariocaMenu(dataSource: menuCtrl) + menu?.selectedIndexPath = NSIndexPath(forItem: 0, inSection: 0) + + menu?.delegate = self + menu?.boomerang = .None + + //reverse delegate for cell selection by tap : + menuCtrl.cariocaMenu = menu + + //show the first demo controller + showDemoControllerForIndex(0) + } + + override func viewDidAppear(animated: Bool) { + + menu?.addInView(self.view) + menu?.isDraggableVertically = true +// menu?.showIndicator(.RightEdge, position: .Bottom, offset: -50) + menu?.showIndicator(.RightEdge, position: .Center, offset: 30) +// menu?.showIndicator(.RightEdge, position: .Top, offset: 50) +// menu?.showIndicator(.LeftEdge, position: .Top, offset: 50) +// menu?.showIndicator(.LeftEdge, position: .Center, offset: 50) + + menu?.addGestureHelperViews([.LeftEdge,.RightEdge], width:30) + } + +// MARK: - Various demo controllers + + func showDemoControllerForIndex(index:Int){ + + if demoContentController != nil { + demoContentController.view.removeFromSuperview() + demoContentController.removeFromParentViewController() + demoContentController = nil + } + + switch index { + + case 1: + if let demoWeb = self.storyboard?.instantiateViewControllerWithIdentifier("DemoWebView") as? DemoWebViewController{ + self.addChildViewController(demoWeb) + self.view.addSubview(demoWeb.view) + demoContentController = demoWeb as UIViewController + } + break + case 2: + if let demoMap = self.storyboard?.instantiateViewControllerWithIdentifier("DemoMapView") as? DemoMapViewController{ + self.addChildViewController(demoMap) + self.view.addSubview(demoMap.view) + demoContentController = demoMap as UIViewController + } + break + case 3: + if let demoScreen = self.storyboard?.instantiateViewControllerWithIdentifier("DemoSettingsView") as? DemoSettingsViewController{ + demoScreen.menu = menu + self.addChildViewController(demoScreen) + self.view.addSubview(demoScreen.view) + demoContentController = demoScreen as UITableViewController + } + break + case 4: + if let demoAbout = self.storyboard?.instantiateViewControllerWithIdentifier("DemoAboutView") as? DemoAboutViewController{ + self.addChildViewController(demoAbout) + self.view.addSubview(demoAbout.view) + demoContentController = demoAbout as UIViewController + } + break + default: + if let demoIntro = self.storyboard?.instantiateViewControllerWithIdentifier("DemoIntroView") as? DemoIntroViewController{ + self.addChildViewController(demoIntro) + self.view.addSubview(demoIntro.view) + demoContentController = demoIntro as UIViewController + } + break + } + + demoContentController.view.translatesAutoresizingMaskIntoConstraints = false + + //Add constraints for autolayout + self.view.addConstraints([ + getEqualConstraint(demoContentController.view, toItem: self.view, attribute: .Trailing), + getEqualConstraint(demoContentController.view, toItem: self.view, attribute: .Leading), + getEqualConstraint(demoContentController.view, toItem: self.view, attribute: .Bottom), + getEqualConstraint(demoContentController.view, toItem: self.view, attribute: .Top) + ]) + + self.view.setNeedsLayout() + + menu?.moveToTop() + } + + + private func getEqualConstraint(item: AnyObject, toItem: AnyObject, attribute: NSLayoutAttribute) -> NSLayoutConstraint{ + return NSLayoutConstraint(item: item, attribute: attribute, relatedBy: .Equal, toItem: toItem, attribute: attribute, multiplier: 1, constant: 0) + } + +// MARK: - CariocaMenu Delegate + + ///`Optional` Called when a menu item was selected + ///- parameters: + /// - menu: The menu object + /// - indexPath: The selected indexPath + func cariocaMenuDidSelect(menu:CariocaMenu, indexPath:NSIndexPath) { + + showDemoControllerForIndex(indexPath.row) + } + + ///`Optional` Called when the menu is about to open + ///- parameters: + /// - menu: The opening menu object + func cariocaMenuWillOpen(menu:CariocaMenu) { + if(logging){ + print("carioca MenuWillOpen \(menu)") + } + } + + ///`Optional` Called when the menu just opened + ///- parameters: + /// - menu: The opening menu object + func cariocaMenuDidOpen(menu:CariocaMenu){ + if(logging){ + switch menu.openingEdge{ + case .LeftEdge: + print("carioca MenuDidOpen \(menu) left") + break; + default: + print("carioca MenuDidOpen \(menu) right") + break; + } + } + } + + ///`Optional` Called when the menu is about to be dismissed + ///- parameters: + /// - menu: The disappearing menu object + func cariocaMenuWillClose(menu:CariocaMenu) { + if(logging){ + print("carioca MenuWillClose \(menu)") + } + } + + ///`Optional` Called when the menu is dismissed + ///- parameters: + /// - menu: The disappearing menu object + func cariocaMenuDidClose(menu:CariocaMenu){ + if(logging){ + print("carioca MenuDidClose \(menu)") + } + } + + // MARK: - + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + } +} + +//Used in demo controllers, simply to round the button's corners + +class roundedButton:UIButton{ + + override func drawRect(rect: CGRect) { + self.layer.cornerRadius = 6 + self.clipsToBounds = true + } +} diff --git a/carioca/DemoControllers/DemoWebViewController.swift b/carioca/DemoControllers/DemoWebViewController.swift new file mode 100644 index 0000000..e16694c --- /dev/null +++ b/carioca/DemoControllers/DemoWebViewController.swift @@ -0,0 +1,51 @@ + +import UIKit + +class DemoWebViewController: UIViewController, UIWebViewDelegate{ + + + @IBOutlet weak var webview: UIWebView! + @IBOutlet weak var loader: UIActivityIndicatorView! + @IBOutlet weak var errorMessage: UILabel! + @IBOutlet weak var tryAgain: UIButton! + + + override func viewDidLoad() { + webview.delegate = self + loader.hidesWhenStopped = true + } + + override func viewDidAppear(animated: Bool) { + loadURL() + } + + func loadURL(){ + webview.scrollView.scrollEnabled = false + errorMessage.hidden = true + tryAgain.hidden = true + webview.loadRequest(NSURLRequest(URL: NSURL(string: "https://medium.com/search?q=Hamburger%20menu")!)) + } + + func webViewDidStartLoad(webView: UIWebView){ + loader.startAnimating() + loader.hidden = false + } + + func webViewDidFinishLoad(webView: UIWebView){ + webview.scrollView.scrollEnabled = true + loader.stopAnimating() + loader.hidden = true + } + + func webView(webView: UIWebView, didFailLoadWithError error: NSError?){ + webview.scrollView.scrollEnabled = false + loader.stopAnimating() + loader.hidden = true + webview.hidden = true + errorMessage.hidden = false + tryAgain.hidden = false + } + @IBAction func tryAgainAction(sender: AnyObject) { + loadURL() + } +} \ No newline at end of file diff --git a/carioca/Images.xcassets/AppIcon.appiconset/Contents.json b/carioca/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..44676ae --- /dev/null +++ b/carioca/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-40.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-40@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-60@2x.png", + "scale" : "2x" + }, + { + "size" : "72x72", + "idiom" : "ipad", + "filename" : "Icon-72.png", + "scale" : "1x" + }, + { + "size" : "72x72", + "idiom" : "ipad", + "filename" : "Icon-72@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-76.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-76@2x.png", + "scale" : "2x" + }, + { + "size" : "50x50", + "idiom" : "ipad", + "filename" : "Icon-Small-50.png", + "scale" : "1x" + }, + { + "size" : "50x50", + "idiom" : "ipad", + "filename" : "Icon-Small-50@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-Small.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-Small@2x.png", + "scale" : "2x" + }, + { + "size" : "57x57", + "idiom" : "iphone", + "filename" : "Icon.png", + "scale" : "1x" + }, + { + "size" : "57x57", + "idiom" : "iphone", + "filename" : "Icon@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-Small@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-60@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-40@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-Small.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-Small@2x.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/carioca/Images.xcassets/AppIcon.appiconset/Icon-40.png b/carioca/Images.xcassets/AppIcon.appiconset/Icon-40.png new file mode 100644 index 0000000..e4071b9 Binary files /dev/null and b/carioca/Images.xcassets/AppIcon.appiconset/Icon-40.png differ diff --git a/carioca/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png b/carioca/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png new file mode 100644 index 0000000..1823d53 Binary files /dev/null and b/carioca/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png differ diff --git a/carioca/Images.xcassets/AppIcon.appiconset/Icon-40@3x.png b/carioca/Images.xcassets/AppIcon.appiconset/Icon-40@3x.png new file mode 100644 index 0000000..729aba9 Binary files /dev/null and b/carioca/Images.xcassets/AppIcon.appiconset/Icon-40@3x.png differ diff --git a/carioca/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png b/carioca/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png new file mode 100644 index 0000000..729aba9 Binary files /dev/null and b/carioca/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png differ diff --git a/carioca/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png b/carioca/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png new file mode 100644 index 0000000..19dbffa Binary files /dev/null and b/carioca/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png differ diff --git a/carioca/Images.xcassets/AppIcon.appiconset/Icon-72.png b/carioca/Images.xcassets/AppIcon.appiconset/Icon-72.png new file mode 100644 index 0000000..8f37a86 Binary files /dev/null and b/carioca/Images.xcassets/AppIcon.appiconset/Icon-72.png differ diff --git a/carioca/Images.xcassets/AppIcon.appiconset/Icon-72@2x.png b/carioca/Images.xcassets/AppIcon.appiconset/Icon-72@2x.png new file mode 100644 index 0000000..cc468d0 Binary files /dev/null and b/carioca/Images.xcassets/AppIcon.appiconset/Icon-72@2x.png differ diff --git a/carioca/Images.xcassets/AppIcon.appiconset/Icon-76.png b/carioca/Images.xcassets/AppIcon.appiconset/Icon-76.png new file mode 100644 index 0000000..5a081e9 Binary files /dev/null and b/carioca/Images.xcassets/AppIcon.appiconset/Icon-76.png differ diff --git a/carioca/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png b/carioca/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png new file mode 100644 index 0000000..89219ed Binary files /dev/null and b/carioca/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png differ diff --git a/carioca/Images.xcassets/AppIcon.appiconset/Icon-Small-50.png b/carioca/Images.xcassets/AppIcon.appiconset/Icon-Small-50.png new file mode 100644 index 0000000..f2fcbd1 Binary files /dev/null and b/carioca/Images.xcassets/AppIcon.appiconset/Icon-Small-50.png differ diff --git a/carioca/Images.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png b/carioca/Images.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png new file mode 100644 index 0000000..c174178 Binary files /dev/null and b/carioca/Images.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png differ diff --git a/carioca/Images.xcassets/AppIcon.appiconset/Icon-Small.png b/carioca/Images.xcassets/AppIcon.appiconset/Icon-Small.png new file mode 100644 index 0000000..330e14e Binary files /dev/null and b/carioca/Images.xcassets/AppIcon.appiconset/Icon-Small.png differ diff --git a/carioca/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png b/carioca/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png new file mode 100644 index 0000000..c8357ea Binary files /dev/null and b/carioca/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png differ diff --git a/carioca/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png b/carioca/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png new file mode 100644 index 0000000..dc47319 Binary files /dev/null and b/carioca/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png differ diff --git a/carioca/Images.xcassets/AppIcon.appiconset/Icon.png b/carioca/Images.xcassets/AppIcon.appiconset/Icon.png new file mode 100644 index 0000000..2d1a8c4 Binary files /dev/null and b/carioca/Images.xcassets/AppIcon.appiconset/Icon.png differ diff --git a/carioca/Images.xcassets/AppIcon.appiconset/Icon@2x.png b/carioca/Images.xcassets/AppIcon.appiconset/Icon@2x.png new file mode 100644 index 0000000..8169a94 Binary files /dev/null and b/carioca/Images.xcassets/AppIcon.appiconset/Icon@2x.png differ diff --git a/carioca/Info.plist b/carioca/Info.plist new file mode 100644 index 0000000..eb3a489 --- /dev/null +++ b/carioca/Info.plist @@ -0,0 +1,69 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + wow + CFBundleDocumentTypes + + + CFBundleTypeName + MKDirectionsRequest + LSItemContentTypes + + com.apple.maps.directionsrequest + + + + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + MKDirectionsApplicationSupportedModes + + MKDirectionsModeCar + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UIRequiresFullScreen + + UIStatusBarHidden + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationLandscapeLeft + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/carioca/Library/CariocaMenu.swift b/carioca/Library/CariocaMenu.swift new file mode 100644 index 0000000..619cf34 --- /dev/null +++ b/carioca/Library/CariocaMenu.swift @@ -0,0 +1,1018 @@ +// CariocaMenu.swift +// +// The MIT License (MIT) +// +// Copyright (c) 2015 Arnaud Schloune +// +// 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: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// 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. + +import UIKit + +//These keys will be used to save values in NSUserDefaults. +private let CariocaMenuUserDefaultsBoomerangVerticalKey = "com.cariocamenu.boomerang.vertical" +private let CariocaMenuUserDefaultsBoomerangHorizontalKey = "com.cariocamenu.boomerang.horizontal" + +///The opening edge of the menu. +///- `LeftEdge`: Left edge of the screen +///- `RightEdge`: Right edge of the screen +@objc public enum CariocaMenuEdge : Int { + case LeftEdge = 0 + case RightEdge = 1 +} + +///The initial vertical position of the menu +///- `Top`: Top of the hostView +///- `Center`: Center of the hostView +///- `Bottom`: Bottom of the hostView +@objc public enum CariocaMenuIndicatorViewPosition : Int { + case Top = 0 + case Center = 1 + case Bottom = 2 +} + +///The boomerang type of the menu. +///- `None`: Default value. The indicators will always return where they were let. +///- `Vertical`: The indicators will always come back at the same Y value. They may switch from Edge if the user wants. +///- `VerticalAndHorizontal`: The indicators will always come back at the exact same place +@objc public enum CariocaMenuBoomerangType : Int { + case None = 0 + case Vertical = 1 + case VerticalAndHorizontal = 2 +} + +//MARK: Delegate Protocol +@objc public protocol CariocaMenuDelegate { + + ///`Optional` Called when the menu is about to open + ///- parameters: + /// - menu: The opening menu object + optional func cariocaMenuWillOpen(menu:CariocaMenu) + + ///`Optional` Called when the menu just opened + ///- parameters: + /// - menu: The opening menu object + optional func cariocaMenuDidOpen(menu:CariocaMenu) + + ///`Optional` Called when the menu is about to be dismissed + ///- parameters: + /// - menu: The disappearing menu object + optional func cariocaMenuWillClose(menu:CariocaMenu) + + ///`Optional` Called when the menu is dismissed + ///- parameters: + /// - menu: The disappearing menu object + optional func cariocaMenuDidClose(menu:CariocaMenu) + + ///`Optional` Called when a menu item was selected + ///- parameters: + /// - menu: The menu object + /// - indexPath: The selected indexPath + optional func cariocaMenuDidSelect(menu:CariocaMenu, indexPath:NSIndexPath) +} + +//MARK: - Datasource Protocol +@objc public protocol CariocaMenuDataSource { + + ///`Required` Gets the menu view, will be used to set constraints + ///- returns: `UIVIew` the view of the menu that will be displayed + func getMenuView()->UIView + + ///`Optional` Unselects a menu item + ///- parameters: + /// - indexPath: The required indexPath + ///- returns: Nothing. Void + optional func unselectRowAtIndexPath(indexPath: NSIndexPath) -> Void + + ///`Optional` Will be called when the indicator hovers a menu item. You may apply some custom styles to your UITableViewCell + ///- parameters: + /// - indexPath: The preselected indexPath + optional func preselectRowAtIndexPath(indexPath:NSIndexPath) + + ///`Required` Will be called when the user selects a menu item (by tapping or just releasing the indicator) + ///- parameters: + /// - indexPath: The selected indexPath + func selectRowAtIndexPath(indexPath:NSIndexPath) + + ///`Required` Gets the height by each row of the menu. Used for internal calculations + ///- returns: `CGFloat` The height for each menu item. + ///- warning: The height should be the same for each row + ///- todo: Manage different heights for each row + func heightByMenuItem()->CGFloat + + ///`Required` Gets the number of menu items + ///- returns: `Int` The total number of menu items + func numberOfMenuItems()->Int + + ///`Required` Gets the icon for a specific menu item + ///- parameters: + /// - indexPath: The required indexPath + ///- returns: `UIImage` The image to show in the indicator. Should be the same that the image displayed in the menu. + ///- todo: Add emoji support ?๐Ÿ‘ + func iconForRowAtIndexPath(indexPath:NSIndexPath)->UIImage + + ///`Optional` Sets the selected indexPath + ///- parameters: + /// - indexPath: The selected indexPath + ///- returns: `Void` Nothing. Nada. + optional func setSelectedIndexPath(indexPath:NSIndexPath)->Void + + ///`Optional` Sets the cell identifier. Used to adapt the tableView cells depending on which side the menu is presented. + ///- parameters: + /// - identifier: The cell identifier + ///- returns: `Void` Nada. Nothing. + optional func setCellIdentifierForEdge(identifier:String)->Void +} + +//MARK: - +public class CariocaMenu : NSObject, UIGestureRecognizerDelegate { + + /** + Initializes an instance of a `CariocaMenu` object. + - parameters: + - dataSource: `CariocaMenuDataSource` The controller presenting your menu + - returns: An initialized `CariocaMenu` object + */ + public init(dataSource:CariocaMenuDataSource) { + self.dataSource = dataSource + self.menuView = dataSource.getMenuView() + self.menuHeight = dataSource.heightByMenuItem() * CGFloat(dataSource.numberOfMenuItems()) + self.boomerang = .None + super.init() + } + + ///The main view of the menu. Will contain the blur effect view, and the menu view. Will match the hostView's frame with AutoLayout constraints. + private var containerView = UIView() + ///The view in which containerView will be added as a subview. + private weak var hostView:UIView? + private var menuView:UIView + + private var menuTopEdgeConstraint:NSLayoutConstraint? + + private var menuOriginalY:CGFloat = 0.0 + private var panOriginalY:CGFloat = 0.0 + + private var sidePanLeft = UIScreenEdgePanGestureRecognizer() + private var sidePanRight = UIScreenEdgePanGestureRecognizer() + private var panGestureRecognizer = UIPanGestureRecognizer() + private var longPressForDragLeft:UILongPressGestureRecognizer? + private var longPressForDragRight:UILongPressGestureRecognizer? + + var dataSource:CariocaMenuDataSource + weak var delegate:CariocaMenuDelegate? + /// The type of boomerang for the menu. Default : None + public var boomerang:CariocaMenuBoomerangType + + /// The selected index of the menu + public var selectedIndexPath:NSIndexPath = NSIndexPath(forItem: 0, inSection: 0) + private var preSelectedIndexPath:NSIndexPath! + + public var openingEdge:CariocaMenuEdge = .LeftEdge + private let menuHeight:CGFloat + + private var leftIndicatorView:CariocaMenuIndicatorView! + private var rightIndicatorView:CariocaMenuIndicatorView! + private var indicatorOffset:CGFloat = 0.0 + + private var gestureHelperViewLeft:UIView! + private var gestureHelperViewRight:UIView! + + /// Allows the user to reposition the menu vertically. Should be called AFTER addIn View() + var isDraggableVertically = false { + didSet { + updateDraggableIndicators() + } + } + + /// If true, the menu will always stay on screen. If false, it will depend on the user's gestures. + var isAlwaysOnScreen = true + +//MARK: - Menu methods + + /** + Adds the menu in the selected view + - parameters: + - view: The view in which the menu will be shown, with indicators on top + */ + public func addInView(view:UIView) { + + if(hostView == view){ + CariocaMenu.Log("Cannot be added to the same view twice") + return + } + + hostView = view + containerView.hidden = true + + addBlur() + containerView.backgroundColor = UIColor.clearColor() + hostView?.addSubview(containerView) + containerView.translatesAutoresizingMaskIntoConstraints = false; + + hostView?.addConstraints([ + getEqualConstraint(containerView, toItem: hostView!, attribute: .Trailing), + getEqualConstraint(containerView, toItem: hostView!, attribute: .Leading), + getEqualConstraint(containerView, toItem: hostView!, attribute: .Bottom), + getEqualConstraint(containerView, toItem: hostView!, attribute: .Top) + ]) + + containerView.setNeedsLayout() + + //Add the menuview to the container + containerView.addSubview(menuView) + + //Gesture recognizers + sidePanLeft.addTarget(self, action: Selector("gestureTouched:")) + hostView!.addGestureRecognizer(sidePanLeft) + sidePanLeft.edges = .Left + + sidePanRight.addTarget(self, action: Selector("gestureTouched:")) + hostView!.addGestureRecognizer(sidePanRight) + sidePanRight.edges = .Right + + panGestureRecognizer.addTarget(self, action: Selector("gestureTouched:")) + containerView.addGestureRecognizer(panGestureRecognizer) + + //Autolayout constraints for the menu + menuView.translatesAutoresizingMaskIntoConstraints = false; + menuView.addConstraint(NSLayoutConstraint(item: menuView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: menuHeight)) + menuTopEdgeConstraint = getEqualConstraint(menuView, toItem: containerView, attribute: .Top) + containerView.addConstraints([ + getEqualConstraint(menuView, toItem: containerView, attribute: .Width), + getEqualConstraint(menuView, toItem: containerView, attribute: .Leading), + menuTopEdgeConstraint! + ]) + menuView.setNeedsLayout() + + addIndicator(.LeftEdge) + addIndicator(.RightEdge) + moveToTop() + + updateDraggableIndicators() + } + + /** + Manages the menu dragging vertically + - parameters: + - gesture: The long press gesture + */ + func longPressedForDrag(gesture: UIGestureRecognizer) { + let location = gesture.locationInView(containerView) + let indicator = gesture.view as! CariocaMenuIndicatorView + + if(gesture.state == .Began) { + indicator.moveInScreenForDragging() + } + + if(gesture.state == .Changed) { + indicator.updateY(location.y - (indicator.size.height / 2)) + } + + if(gesture.state == .Ended) { + indicator.show() + indicatorOffset = location.y - (indicator.size.height / 2) + adaptMenuYForIndicatorY(indicator, afterDragging:true) + } + } + + /** + Manages the menu position depending on the gesture (UIScreenEdgePanGestureRecognizer and UIPanGestureRecognizer) + - parameters: + - gesture: The gesture (EdgePan or Pan) + */ + func gestureTouched(gesture: UIGestureRecognizer) { + + let location = gesture.locationInView(gesture.view) + + //remove the status bar + let topMinimum:CGFloat = 20.0 + let bottomMaximum = (gesture.view?.frame.height)! - menuHeight + + if(gesture.state == .Began) { + + if(gesture != panGestureRecognizer){ + let newEdge:CariocaMenuEdge = (gesture == sidePanLeft) ? .LeftEdge : .RightEdge + if openingEdge != newEdge { + openingEdge = newEdge + getIndicatorForEdge((openingEdge == .RightEdge) ? .LeftEdge : .RightEdge).hide() + dataSource.setCellIdentifierForEdge!((openingEdge == .LeftEdge) ? "cellRight" : "cellLeft") + } + } + + delegate?.cariocaMenuWillOpen!(self) + showMenu() + showIndicatorOnTopOfMenu(openingEdge) + + panOriginalY = location.y + + //Y to add to match the preselected index + menuOriginalY = panOriginalY - ((dataSource.heightByMenuItem() * CGFloat(selectedIndexPath.row)) + (dataSource.heightByMenuItem()/2)) + + if isAlwaysOnScreen { + if menuOriginalY < topMinimum { + menuOriginalY = topMinimum + } + else if menuOriginalY > bottomMaximum { + menuOriginalY = bottomMaximum + } + } + menuTopEdgeConstraint?.constant = menuOriginalY + + delegate?.cariocaMenuDidOpen!(self) + } + + if(gesture.state == .Changed) { +// CariocaMenu.Log("changed \(Double(location.y))") + + let difference = panOriginalY - location.y + var newYconstant = menuOriginalY + difference + + if isAlwaysOnScreen { + newYconstant = (newYconstant < topMinimum) ? topMinimum : ((newYconstant > bottomMaximum) ? bottomMaximum : newYconstant) + } + + menuTopEdgeConstraint?.constant = newYconstant + + var matchingIndex = Int(floor((location.y - newYconstant) / dataSource.heightByMenuItem())) + //check if < 0 or > numberOfMenuItems + matchingIndex = (matchingIndex < 0) ? 0 : ((matchingIndex > (dataSource.numberOfMenuItems()-1)) ? (dataSource.numberOfMenuItems()-1) : matchingIndex) + + let calculatedIndexPath = NSIndexPath(forRow: matchingIndex, inSection: 0) + + if preSelectedIndexPath != calculatedIndexPath { + if preSelectedIndexPath != nil { + dataSource.unselectRowAtIndexPath!(preSelectedIndexPath) + } + preSelectedIndexPath = calculatedIndexPath + dataSource.preselectRowAtIndexPath?(preSelectedIndexPath) + } + + updateIndicatorsForIndexPath(preSelectedIndexPath) + } + + if(gesture.state == .Ended){ + menuOriginalY = location.y + //Unselect the previously selected cell, but first, update the selectedIndexPath + let indexPathForDeselection = selectedIndexPath + selectedIndexPath = preSelectedIndexPath + dataSource.unselectRowAtIndexPath!(indexPathForDeselection) + didSelectRowAtIndexPath(selectedIndexPath, fromContentController:true) + } + + if gesture.state == .Failed { CariocaMenu.Log("Failed : \(gesture)") } + if gesture.state == .Possible { CariocaMenu.Log("Possible : \(gesture)") } + if gesture.state == .Cancelled { CariocaMenu.Log("cancelled : \(gesture)") } + } + + /** + Calls the delegate actions for row selection + - parameters: + - indexPath: The selected index path + - fromContentController: Bool value precising the source of selection + */ + public func didSelectRowAtIndexPath(indexPath:NSIndexPath, fromContentController:Bool){ + if preSelectedIndexPath != nil { + dataSource.unselectRowAtIndexPath!(preSelectedIndexPath) + preSelectedIndexPath = nil + } + //Unselect the previously selected cell, but first, update the selectedIndexPath + let indexPathForDeselection = selectedIndexPath + selectedIndexPath = indexPath + if(!fromContentController){ + dataSource.selectRowAtIndexPath(indexPath) + }else{ + dataSource.unselectRowAtIndexPath!(indexPathForDeselection) + dataSource.setSelectedIndexPath!(indexPath) + } + delegate?.cariocaMenuDidSelect!(self, indexPath: indexPath) + updateIndicatorsForIndexPath(indexPath) + if(fromContentController){ + hideMenu() + } + } + + ///Gestures management + public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return true + } + + ///Makes sure the containerView is on top of the hostView + func moveToTop() { + hostView?.bringSubviewToFront(containerView) + if gestureHelperViewLeft != nil{ + hostView?.bringSubviewToFront(gestureHelperViewLeft) + } + if gestureHelperViewRight != nil{ + hostView?.bringSubviewToFront(gestureHelperViewRight) + } + hostView?.bringSubviewToFront(leftIndicatorView) + hostView?.bringSubviewToFront(rightIndicatorView) + } + + ///Adds blur to the container view (real blur for iOS > 7) + private func addBlur() { + if (NSClassFromString("UIVisualEffectView") != nil) { + let visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffectStyle.ExtraLight)) as UIVisualEffectView + visualEffectView.frame = containerView.bounds + visualEffectView.autoresizingMask = [.FlexibleHeight, .FlexibleWidth] + containerView.addSubview(visualEffectView) + } + else { + // TODO: add real blur for < iOS8 + let visualEffectView = UIView(frame: containerView.bounds) + visualEffectView.backgroundColor = UIColor.whiteColor().colorWithAlphaComponent(0.7) + visualEffectView.autoresizingMask = [.FlexibleHeight, .FlexibleWidth] + containerView.addSubview(visualEffectView) + } + } + +//MARK: - Menu visibility + + ///Shows the menu + public func showMenu() { + gestureHelperViewLeft?.hidden = true + gestureHelperViewRight?.hidden = true + containerView.hidden = false + containerView.alpha = 1 + hostView!.layoutIfNeeded() + } + + ///Hides the menu + public func hideMenu() { + + getIndicatorForEdge(openingEdge).restoreOnOriginalEdge(boomerang, completion:{ + + let edgeToCheckAfterFirstAnimation:CariocaMenuEdge = self.boomerang == .VerticalAndHorizontal ? CariocaMenu.getBoomerangHorizontalValue() : self.openingEdge + + //show back only if it's on the same edge (always true if no horizontal boomerang) + if edgeToCheckAfterFirstAnimation != self.openingEdge { + let otherIndicator = self.getIndicatorForEdge(self.openingEdge == .RightEdge ? .LeftEdge : .RightEdge) + let offsetSaved = CariocaMenu.getBoomerangVerticalValue() + otherIndicator.updateY(offsetSaved) + otherIndicator.show() + self.openingEdge = edgeToCheckAfterFirstAnimation + } + }) + + delegate?.cariocaMenuWillClose!(self) + + UIView.animateWithDuration(0.5, animations: { () -> Void in + self.containerView.alpha = 0 + }, completion: { (finished) -> Void in + self.containerView.hidden = true + self.gestureHelperViewLeft?.hidden = false + self.gestureHelperViewRight?.hidden = false + self.delegate?.cariocaMenuDidClose!(self) + }) + } + +//MARK: - Gesture helper views + + /// + /** + Adds Gesture helper views in the container view. Recommended when the whole view scrolls (`UIWebView`,`MKMapView`,...) + - parameters: + - edges: An array of `CariocaMenuEdge` on which to show the helpers + - width: The width of the helper view. Maximum value should be `40`, but you're free to put what you want. + */ + public func addGestureHelperViews(edges:Array, width:CGFloat) { + + if(edges.contains(.LeftEdge)){ + if(gestureHelperViewLeft != nil){ + gestureHelperViewLeft.removeFromSuperview() + } + gestureHelperViewLeft = prepareGestureHelperView(.Leading, width:width) + } + + if(edges.contains(.RightEdge)){ + if(gestureHelperViewRight != nil){ + gestureHelperViewRight.removeFromSuperview() + } + gestureHelperViewRight = prepareGestureHelperView(.Trailing, width:width) + } + + hostView?.bringSubviewToFront(leftIndicatorView) + hostView?.bringSubviewToFront(rightIndicatorView) + } + + /** + Generates a gesture helper view with autolayout constraints + - parameters: + - edgeAttribute: `.Leading` or `.Trailing` + - width: The width of the helper view. + - returns: `UIView` The helper view constrained to the hostView edge + */ + private func prepareGestureHelperView(edgeAttribute:NSLayoutAttribute, width:CGFloat)->UIView{ + + let view = UIView() + view.backgroundColor = UIColor.clearColor() + hostView?.addSubview(view) + view.translatesAutoresizingMaskIntoConstraints = false; + + hostView?.addConstraints([ + getEqualConstraint(view, toItem: hostView!, attribute: edgeAttribute), + NSLayoutConstraint(item: view, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: width), + getEqualConstraint(view, toItem: hostView!, attribute: .Bottom), + getEqualConstraint(view, toItem: hostView!, attribute: .Top) + ]) + + view.setNeedsLayout() + return view + } + +//MARK: - Indicators + + /** + Adds an indicator on a specific edge of the screen + - parameters: + - edge: LeftEdge or RightEdge + */ + private func addIndicator(edge:CariocaMenuEdge){ + + //TODO: Check if the indicator already exists + let indicator = CariocaMenuIndicatorView(indicatorEdge: edge, size:CGSizeMake(47, 40), shapeColor:UIColor(red:0.07, green:0.73, blue:0.86, alpha:1)) + indicator.addInView(hostView!, edge: edge) + + if(edge == .LeftEdge){ + leftIndicatorView = indicator + }else{ + rightIndicatorView = indicator + } + + let tapGesture = UITapGestureRecognizer(target: self, action: Selector("tappedOnIndicatorView:")) + indicator.addGestureRecognizer(tapGesture) + } + + ///Manages the tap on an indicator view + func tappedOnIndicatorView(tap:UIGestureRecognizer){ + let indicator = tap.view as! CariocaMenuIndicatorView + openingEdge = indicator.edge + if(menuOriginalY == 0 || boomerang == .Vertical || boomerang == .VerticalAndHorizontal){ + adaptMenuYForIndicatorY(indicator, afterDragging:false) + } + showMenu() + showIndicatorOnTopOfMenu(openingEdge) + dataSource.preselectRowAtIndexPath?(selectedIndexPath) + } + + /** + Adapts the menu Y position depending on the position of the indicator (takes care to not move the menu off screen) + - parameters: + - indicator: The indicator to adapt + - afterDragging: Bool indicating if the new vertical value must be saved for the boomerangs + */ + private func adaptMenuYForIndicatorY(indicator:CariocaMenuIndicatorView, afterDragging:Bool){ + //preset the menu Y + //the indicator Y - the selected index Y - the space to center the indicator ((dataSource.heightByMenuItem() - indicatorHeight)/2) + let indicatorSpace = (dataSource.heightByMenuItem()-indicator.size.height)/2 + var menuY = (indicator.topConstraint?.constant)! - (CGFloat(selectedIndexPath.row) * dataSource.heightByMenuItem()) - indicatorSpace + + if isAlwaysOnScreen { + //remove the status bar + let topMinimum:CGFloat = 20.0 + let bottomMaximum = containerView.frame.height - menuHeight + //check to not hide the menu + menuY = (menuY < topMinimum) ? topMinimum : ((menuY > bottomMaximum) ? bottomMaximum : menuY) + } + + menuOriginalY = menuY + menuTopEdgeConstraint?.constant = CGFloat(menuOriginalY) + updateIndicatorsForIndexPath(selectedIndexPath) + dataSource.setCellIdentifierForEdge!((openingEdge == .LeftEdge) ? "cellRight" : "cellLeft") + + if afterDragging { + indicatorOffset = (indicator.topConstraint?.constant)! + NSUserDefaults.standardUserDefaults().setDouble(Double(indicatorOffset), forKey: CariocaMenuUserDefaultsBoomerangVerticalKey) + NSUserDefaults.standardUserDefaults().synchronize() + } + } + + /** + Shows the indicator on a precise position + - parameters: + - edge: Left or right edge + - position: Top, Center or Bottom + - offset: A random offset value. Should be negative when position is equal to `.Bottom` + */ + func showIndicator(edge:CariocaMenuEdge, position:CariocaMenuIndicatorViewPosition, offset:CGFloat){ + indicatorOffset = getIndicatorForEdge(edge).showAt(position, offset: offset) + NSUserDefaults.standardUserDefaults().setDouble(Double(indicatorOffset), forKey: CariocaMenuUserDefaultsBoomerangVerticalKey) + NSUserDefaults.standardUserDefaults().setValue(edge.rawValue, forKey: CariocaMenuUserDefaultsBoomerangHorizontalKey) + NSUserDefaults.standardUserDefaults().synchronize() + openingEdge = edge + updateIndicatorsImage(dataSource.iconForRowAtIndexPath(selectedIndexPath)) + } + + ///Shows the indicator on top of the selected menu indexPath + private func showIndicatorOnTopOfMenu(edge:CariocaMenuEdge){ + getIndicatorForEdge(edge).moveYOverMenu(indicatorOffset, containerWidth:containerView.frame.size.width) + } + + /** + Returns the right indicator for the asked edge + - parameters: + - edge: Left or Right edge + - returns: `CariocaMenuIndicatorView` The matching indicator + */ + private func getIndicatorForEdge(edge:CariocaMenuEdge)->CariocaMenuIndicatorView { + return (edge == .RightEdge) ? rightIndicatorView : leftIndicatorView + } + + /** + Updates the image inside the indicator, to match the menu item + - parameters: + - image: The UIImage to display in the indicator + */ + func updateIndicatorsImage(image:UIImage){ + leftIndicatorView.updateImage(image) + rightIndicatorView.updateImage(image) + } + + /** + Updates the indicator position to match the position of a specific indexPath in the menu + - parameters: + - indexPath: The concerned indexPath + */ + private func updateIndicatorsForIndexPath(indexPath:NSIndexPath){ + let indicator = getIndicatorForEdge(openingEdge) + //menuTop + index position + center Y for indicator + indicatorOffset = CGFloat((menuTopEdgeConstraint?.constant)!) + (CGFloat(indexPath.row) * dataSource.heightByMenuItem()) + ((dataSource.heightByMenuItem() - indicator.size.height) / 2) + indicator.updateY(indicatorOffset) + updateIndicatorsImage(dataSource.iconForRowAtIndexPath(indexPath)) + } + +//MARK: - Edges + /** + Disables a specific edge for the menu (Both edges are active by default) + - parameters: + - edge: The edge to disable (Left or Right) + */ + public func disableEdge(edge:CariocaMenuEdge){ + if (hostView != nil){ + hostView?.removeGestureRecognizer((edge == .LeftEdge) ? sidePanLeft : sidePanRight) + } + } + +//MARK: - Constraints + /** + Generates an Equal constraint + - returns: `NSlayoutConstraint` an equal constraint for the specified parameters + */ + private func getEqualConstraint(item: AnyObject, toItem: AnyObject, attribute: NSLayoutAttribute) -> NSLayoutConstraint{ + return NSLayoutConstraint(item: item, attribute: attribute, relatedBy: .Equal, toItem: toItem, attribute: attribute, multiplier: 1, constant: 0) + } + +//MARK: - Boomerangs + + /** + Gets the saved value for the vertical boomerang + - returns: `CGFloat` The boomerang vertical value + */ + class func getBoomerangVerticalValue()->CGFloat{ + let offset = NSUserDefaults.standardUserDefaults().doubleForKey(CariocaMenuUserDefaultsBoomerangVerticalKey) + return(CGFloat(offset)) + } + + /** + Gets the saved value for the horizontal boomerang + - returns: `CariocaMenuEdge` the boomerang matching edge + */ + class func getBoomerangHorizontalValue()->CariocaMenuEdge{ + let int = NSUserDefaults.standardUserDefaults().integerForKey(CariocaMenuUserDefaultsBoomerangHorizontalKey) + return int == 1 ? .RightEdge : .LeftEdge + } + + ///Resets the boomerang saved values + class func resetBoomerangValues() { + NSUserDefaults.standardUserDefaults().setValue(nil, forKey: CariocaMenuUserDefaultsBoomerangVerticalKey) + NSUserDefaults.standardUserDefaults().setValue(nil, forKey: CariocaMenuUserDefaultsBoomerangHorizontalKey) + NSUserDefaults.standardUserDefaults().synchronize() + } + + ///Enables/disables the indicator drag gesture + private func updateDraggableIndicators(){ + + if isDraggableVertically { + longPressForDragLeft = UILongPressGestureRecognizer(target: self, action: Selector("longPressedForDrag:")) + longPressForDragLeft?.minimumPressDuration = 0.7 + leftIndicatorView.addGestureRecognizer(longPressForDragLeft!) + longPressForDragRight = UILongPressGestureRecognizer(target: self, action: Selector("longPressedForDrag:")) + longPressForDragRight?.minimumPressDuration = 0.7 + rightIndicatorView.addGestureRecognizer(longPressForDragRight!) + } else { + + if longPressForDragLeft != nil { + leftIndicatorView.removeGestureRecognizer(longPressForDragLeft!) + longPressForDragLeft = nil + } + if longPressForDragRight != nil { + rightIndicatorView.removeGestureRecognizer(longPressForDragRight!) + longPressForDragRight = nil + } + } + } + +//MARK: - Logs + ///Logs a string in the console + ///- parameters: + /// - log: String to log + class func Log(log:String) {print("CariocaMenu :: \(log)")} +} + +//MARK: - IndicatorView Class +//MARK: + +class CariocaMenuIndicatorView : UIView{ + + /** + Initializes an indicator for the menu + - parameters: + - indicatoreEdge: Left or Right edge + - size: The size of the indicator + - backgroundColor: The background color of the indicator + - returns: `CariocaMenuIndicatorView` An indicator + */ + init(indicatorEdge: CariocaMenuEdge, size:CGSize, shapeColor: UIColor) { + edge = indicatorEdge + imageView = UIImageView() + self.size = size + self.shapeColor = shapeColor + super.init(frame: CGRectMake(0, 0, size.width, size.height)) + self.translatesAutoresizingMaskIntoConstraints = false + self.backgroundColor = UIColor.clearColor() + } + + //Don't know the utility of this code, but seems to be required + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + ///The edge of the indicator. One indicator by edge maximum + var edge:CariocaMenuEdge + ///The size of the indicator. Will be used for calculations, needs to be public + var size:CGSize + ///The color of the shape + private var shapeColor:UIColor + ///The edge constraint, will depend on the edge. (Trailing or Leading) + private var edgeConstraint:NSLayoutConstraint? + ///The top constraint to adjust the vertical position + var topConstraint:NSLayoutConstraint? + ///The imageView to display your nicest icons. + ///- warning: ๐Ÿ‘ฎDon't steal icons.๐Ÿ‘ฎ + private var imageView:UIImageView + + override func drawRect(frame: CGRect) { + + //This shape was drawed with PaintCode App + let ovalPath = UIBezierPath() + + if(edge == .LeftEdge){ + ovalPath.moveToPoint(CGPointMake(frame.maxX, frame.minY + 0.50000 * frame.height)) + ovalPath.addCurveToPoint(CGPointMake(frame.maxX - 20, frame.minY), controlPoint1: CGPointMake(frame.maxX, frame.minY + 0.22386 * frame.height), controlPoint2: CGPointMake(frame.maxX - 8.95, frame.minY)) + ovalPath.addCurveToPoint(CGPointMake(frame.minX + 1, frame.minY + 0.50000 * frame.height), controlPoint1: CGPointMake(frame.maxX - 31.05, frame.minY), controlPoint2: CGPointMake(frame.minX + 1, frame.minY + 0.30000 * frame.height)) + ovalPath.addCurveToPoint(CGPointMake(frame.maxX - 20, frame.maxY), controlPoint1: CGPointMake(frame.minX + 1, frame.minY + 0.70000 * frame.height), controlPoint2: CGPointMake(frame.maxX - 31.05, frame.maxY)) + ovalPath.addCurveToPoint(CGPointMake(frame.maxX, frame.minY + 0.50000 * frame.height), controlPoint1: CGPointMake(frame.maxX - 8.95, frame.maxY), controlPoint2: CGPointMake(frame.maxX, frame.minY + 0.77614 * frame.height)) + ovalPath.closePath() + + }else{ + //right + ovalPath.moveToPoint(CGPointMake(frame.minX + 1, frame.minY + 0.50000 * frame.height)) + ovalPath.addCurveToPoint(CGPointMake(frame.minX + 21, frame.minY), controlPoint1: CGPointMake(frame.minX + 1, frame.minY + 0.22386 * frame.height), controlPoint2: CGPointMake(frame.minX + 9.95, frame.minY)) + ovalPath.addCurveToPoint(CGPointMake(frame.maxX, frame.minY + 0.50000 * frame.height), controlPoint1: CGPointMake(frame.minX + 32.05, frame.minY), controlPoint2: CGPointMake(frame.maxX, frame.minY + 0.30000 * frame.height)) + ovalPath.addCurveToPoint(CGPointMake(frame.minX + 21, frame.maxY), controlPoint1: CGPointMake(frame.maxX, frame.minY + 0.70000 * frame.height), controlPoint2: CGPointMake(frame.minX + 32.05, frame.maxY)) + ovalPath.addCurveToPoint(CGPointMake(frame.minX + 1, frame.minY + 0.50000 * frame.height), controlPoint1: CGPointMake(frame.minX + 9.95, frame.maxY), controlPoint2: CGPointMake(frame.minX + 1, frame.minY + 0.77614 * frame.height)) + ovalPath.closePath() + } + + ovalPath.closePath() + shapeColor.setFill() + ovalPath.fill() + } + + //MARK: - Indicator methods + + /** + Adds the indicator in the hostView + - parameters: + - hostView: The view that will contain the indicator + - edge: The edge on which to stick the indicator + */ + func addInView(hostView:UIView, edge:CariocaMenuEdge) { + + hidden = true + hostView.addSubview(self) + + var attrSideEdge:NSLayoutAttribute = (edge == .RightEdge) ? .Trailing : .Leading + + topConstraint = NSLayoutConstraint(item: self, attribute: .Top, relatedBy: .Equal, toItem: hostView, attribute: .Top, multiplier: 1, constant: 0) + + //hide the indicator, will appear from the outside of the screen + edgeConstraint = NSLayoutConstraint(item: self, attribute: attrSideEdge, relatedBy: .Equal, toItem: hostView, attribute: attrSideEdge, multiplier: 1, constant: getEdgeConstantValue(((size.width + 10) * -1))) + + hostView.addConstraints([ + edgeConstraint!, + NSLayoutConstraint(item: self, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: size.width), + NSLayoutConstraint(item: self, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: size.height), + topConstraint! + ]) + + hostView.layoutIfNeeded() + + //add Icon imageView + imageView.contentMode = UIViewContentMode.ScaleAspectFit + imageView.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(imageView) + + //constraints for imageView + attrSideEdge = (edge == .RightEdge) ? .Leading : .Trailing + let valSideEdge:CGFloat = (edge == .RightEdge) ? 10.0 : -10.0 + + self.addConstraints([ + NSLayoutConstraint(item: imageView, attribute: attrSideEdge, relatedBy: .Equal, toItem: self, attribute: attrSideEdge, multiplier: 1, constant: valSideEdge), + NSLayoutConstraint(item: imageView, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 24), + NSLayoutConstraint(item: imageView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 24), + NSLayoutConstraint(item: imageView, attribute: .CenterY, relatedBy: .Equal, toItem: self, attribute: .CenterY, multiplier: 1, constant: 0), + ]) + + imageView.layoutIfNeeded() + } + + /** + Shows the indicator at the demanded position + - parameters: + - position: Top, Center or Bottom + - offset: The offset to adjust the position. Should be negative `if position == .Bottom` + - returns: `CGFloat` The top constraint constant value + - todo: Save the final value in %, to avoid problems with multiple orientations + */ + func showAt(position:CariocaMenuIndicatorViewPosition, offset:CGFloat) ->CGFloat{ + + var yValue:CGFloat = 0 + + if position == .Center { + yValue = CGFloat((superview!.frame.size.height) / 2.0) - size.height/2 + } + else if position == .Bottom { + yValue = CGFloat((superview!.frame.size.height)) - size.height + } + else if position == .Top { + yValue = 20 + } + + updateY(offset+yValue) + superview!.layoutIfNeeded() + superview!.bringSubviewToFront(self) + show() + + return (topConstraint?.constant)! + } + + /** + Updates the Y position of the indicator + - parameters: + - y: The new value for the top constraint + */ + func updateY(y:CGFloat){ + topConstraint?.constant = y + } + + /** + Restores the indicator on its initial position, depending on the boomerang type of the menu + - parameters: + - boomerang: The boomerang of the menu + - completion: A completionBlock called when the animation is finished. + */ + func restoreOnOriginalEdge(boomerang:CariocaMenuBoomerangType, completion: (() -> Void)){ + superview!.layoutIfNeeded() + + let isBoomerang = (boomerang != .None) + //different positions if boomerang or not + let position1 = isBoomerang ? getEdgeConstantValue(-80.0) : getEdgeConstantValue(-20.0) + let position2 = isBoomerang ? position1 : getEdgeConstantValue(nil) + + let edgeToCheckAfterFirstAnimation:CariocaMenuEdge = boomerang == .VerticalAndHorizontal ? CariocaMenu.getBoomerangHorizontalValue() : edge + + animateX(position1, speed1:0.2, position2: position2, speed2:0.2, completion:{ + + if isBoomerang{ + let offsetSaved = CariocaMenu.getBoomerangVerticalValue() + if offsetSaved != 0.0{ + self.updateY(offsetSaved) + self.superview!.layoutIfNeeded() + //show back only if it's on the same edge (always true if no horizontal boomerang) + if edgeToCheckAfterFirstAnimation == self.edge { + self.show() + } + completion() + } + } + }) + } + + /** + Adapts the Y position of the indicator, while being on top of the menu + - parameters: + - y: The new vertical position + - containerWidth: The width of the hostView used to animate the indicator X position + */ + func moveYOverMenu(y:CGFloat,containerWidth:CGFloat){ +// CariocaMenu.Log("moveYOverMenu \(y)") + topConstraint?.constant = y + superview!.layoutIfNeeded() + superview!.bringSubviewToFront(self) + hidden = false + + animateX(getEdgeConstantValue(containerWidth - self.size.width + 10), speed1 :0.2, position2: getEdgeConstantValue(containerWidth - (self.size.width + 1)), speed2 :0.2, completion:{ + + }) + } + + ///Hides the indicator + func hide(){ +// CariocaMenu.Log("hide \(self)") + UIView.animateWithDuration(0.2, animations: { () -> Void in + }) { (finished) -> Void in + self.hidden = true + } + } + + ///Shows the indicator + func show(){ +// CariocaMenu.Log("show \(self)") + hidden = false + animateX(getEdgeConstantValue(0.0), speed1 :0.2, position2: getEdgeConstantValue(nil), speed2:0.4, completion:{ + + }) + } + + ///Moves the indicator on the edge of the screen, when the user longPressed on it. + func moveInScreenForDragging(){ +// CariocaMenu.Log("moveInScreenForDragging\(self)") + animateX(getEdgeConstantValue(-5.0), speed1 :0.2, position2: getEdgeConstantValue(0.0), speed2:0.4, completion:{ + + }) + } + + /** + Updates the indicator's image + - parameters: + - image: An UIImage to display in the indicator + */ + func updateImage(image:UIImage){ + imageView.image = image + } + + //MARK: Internal methods + + /** + Animates the X position of the indicator, in two separate animations + - parameters: + - position1: The X position of the first animation + - spped1: The duration of the first animation + - position2: The X position of the second animation + - spped2: The duration of the second animation + - completion: the completionBlock called when the two animations are finished + */ + private func animateX(position1:CGFloat, speed1:Double, position2:CGFloat, speed2:Double, completion: (() -> Void)){ + + edgeConstraint?.constant = position1 + UIView.animateWithDuration(speed1,delay:0, options: [.CurveEaseIn], animations: { () -> Void in + self.superview!.layoutIfNeeded() + + }) { (finished) -> Void in + + self.edgeConstraint?.constant = position2 + UIView.animateWithDuration(speed2,delay:0, options: [.CurveEaseOut], animations: { () -> Void in + self.superview!.layoutIfNeeded() + + }) { (finished) -> Void in + completion() + } + } + } + + /** + Calculates the value to set to the edgeConstraint. (Negative or positive, depending on the edge) + - parameters: + - value: The value to transform + - returns: `CGFloat` The value to set to the constant of the edgeConstraint + */ + private func getEdgeConstantValue(value:CGFloat!)->CGFloat{ + let val = (value != nil) ? value : -5.0 + return (edge == .RightEdge) ? (val * -1) : val + } +} diff --git a/carioca/MyMenuContentController.swift b/carioca/MyMenuContentController.swift new file mode 100644 index 0000000..cf259a6 --- /dev/null +++ b/carioca/MyMenuContentController.swift @@ -0,0 +1,139 @@ + +import UIKit + +class MyMenuContentController: UITableViewController, CariocaMenuDataSource { + + var iconNames = Array() + var menuNames = Array() + weak var cariocaMenu:CariocaMenu? + var cellTypeIdentifier = "cellLeft" + + override func viewDidLoad() { + super.viewDidLoad() + + tableView.scrollsToTop = false + // Preserve selection between presentations + self.clearsSelectionOnViewWillAppear = true + + iconNames.append("hello") + iconNames.append("hamburger") + iconNames.append("map") + iconNames.append("settings") + iconNames.append("about") + + menuNames.append("Hello") + menuNames.append("Hamburger") + menuNames.append("Travel") + menuNames.append("Settings") + menuNames.append("About") + } + + // MARK: - Table view data source + + override func numberOfSectionsInTableView(tableView: UITableView) -> Int { + return 1 + } + + override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return menuNames.count + } + + override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { + + let cell = tableView.dequeueReusableCellWithIdentifier(cellTypeIdentifier, forIndexPath: indexPath) as! MyMenuTableViewCell + //set the title in the cell + cell.titleLabel.text = menuNames[indexPath.row] + + if (indexPath == cariocaMenu?.selectedIndexPath){ +// CariocaMenu.Log("cellForRow : selected") + cell.applyStyleSelected() + } + else{ +// CariocaMenu.Log("cellForRow : normal") + cell.applyStyleNormal() + } + + cell.iconImageView.image = UIImage(named: "\(iconNames[indexPath.row])_menu.png")! + + return cell + } + + override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { + return 60.0 + } + + override func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { + let footerView = UIView(frame: CGRectMake(0, 0, tableView.frame.size.width, 0)) + footerView.backgroundColor = UIColor.clearColor() + return footerView + } + +//MARK: - Cell styles and selection/preselection + + func unselectRowAtIndexPath(indexPath: NSIndexPath) -> Void { +// CariocaMenu.Log("unselectRowAtIndexPath \(indexPath.row)") + if (indexPath == cariocaMenu?.selectedIndexPath){ + getCellFor(indexPath).applyStyleSelected() + }else { + getCellFor(indexPath).applyStyleNormal() + } + } + + func preselectRowAtIndexPath(indexPath: NSIndexPath) -> Void { +// CariocaMenu.Log("preselectRowAtIndexPath \(indexPath.row)") + getCellFor(indexPath).applyStyleHighlighted() + } + + func setSelectedIndexPath(indexPath: NSIndexPath) -> Void { +// CariocaMenu.Log("setSelectedIndexPath \(indexPath.row)") + getCellFor(indexPath).applyStyleSelected() + } + + //Called when the user releases the gesture on a menu item + func selectRowAtIndexPath(indexPath: NSIndexPath) -> Void { +// CariocaMenu.Log("selectRowAtIndexPath \(indexPath.row)") + self.tableView(self.tableView, didSelectRowAtIndexPath: indexPath) + } + +// MARK: - Table view delegate + + override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { +// CariocaMenu.Log("didSelectRowAtIndexPath \(indexPath.row)") + //Transfer the event to the menu, so that he can manage the selection + cariocaMenu?.didSelectRowAtIndexPath(indexPath, fromContentController:true) + } + + // MARK: - Get the Cell + + private func getCellFor(indexPath:NSIndexPath)->MyMenuTableViewCell { + return self.tableView.cellForRowAtIndexPath(indexPath) as! MyMenuTableViewCell + } + + // MARK: - Data source protocol + + func getMenuView()->UIView{ + return self.view + } + + func heightByMenuItem()->CGFloat { + return self.tableView(self.tableView, heightForRowAtIndexPath: NSIndexPath(forItem: 0, inSection: 0)) + } + + func numberOfMenuItems()->Int { + return self.tableView(self.tableView, numberOfRowsInSection: 0) + } + + func iconForRowAtIndexPath(indexPath:NSIndexPath)->UIImage { + return UIImage(named: "\(iconNames[indexPath.row])_indicator.png")! + } + + func setCellIdentifierForEdge(identifier:String)->Void { + cellTypeIdentifier = identifier + self.tableView.reloadData() + } + // MARK: - + + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + } +} diff --git a/carioca/MyMenuTableViewCell.swift b/carioca/MyMenuTableViewCell.swift new file mode 100644 index 0000000..06dce51 --- /dev/null +++ b/carioca/MyMenuTableViewCell.swift @@ -0,0 +1,34 @@ + +import UIKit + +class MyMenuTableViewCell:UITableViewCell { + + private let textColorNormal = UIColor.lightGrayColor() + private let textColorHighlighted = UIColor(red:0.46, green:0.82, blue:0.89, alpha:1) + private let textColorSelected = UIColor(red:0.07, green:0.73, blue:0.86, alpha:1) + + @IBOutlet weak var iconImageView: UIImageView! + @IBOutlet weak var titleLabel: UILabel! + + override func drawRect(rect: CGRect) { + +// self.selectedBackgroundView = UIView(frame: CGRectZero) +// self.selectedBackgroundView.backgroundColor = UIColor.clearColor() + +// let highlight = UIView(frame: CGRectOffset(self.bounds,0, 0.0)) +// highlight.backgroundColor = UIColor(red:0.08, green:0.61, blue:0.84, alpha:0.4) +// self.selectedBackgroundView!.addSubview(highlight) + } + + func applyStyleNormal(){ + titleLabel.textColor = textColorNormal + } + + func applyStyleHighlighted(){ + titleLabel.textColor = textColorHighlighted + } + + func applyStyleSelected(){ + titleLabel.textColor = textColorSelected + } +} diff --git a/carioca/Ressources/Images/demoContent/arnaud.schloune.jpg b/carioca/Ressources/Images/demoContent/arnaud.schloune.jpg new file mode 100644 index 0000000..964c6d9 Binary files /dev/null and b/carioca/Ressources/Images/demoContent/arnaud.schloune.jpg differ diff --git a/carioca/Ressources/Images/demoContent/icon-small.png b/carioca/Ressources/Images/demoContent/icon-small.png new file mode 100644 index 0000000..72995c9 Binary files /dev/null and b/carioca/Ressources/Images/demoContent/icon-small.png differ diff --git a/carioca/Ressources/Images/demoContent/whiteGradientBottom.png b/carioca/Ressources/Images/demoContent/whiteGradientBottom.png new file mode 100644 index 0000000..6860153 Binary files /dev/null and b/carioca/Ressources/Images/demoContent/whiteGradientBottom.png differ diff --git a/carioca/Ressources/Images/demoContent/whiteGradientTop.png b/carioca/Ressources/Images/demoContent/whiteGradientTop.png new file mode 100644 index 0000000..31dbddc Binary files /dev/null and b/carioca/Ressources/Images/demoContent/whiteGradientTop.png differ diff --git a/carioca/Ressources/Images/icons/indicator/about_indicator.png b/carioca/Ressources/Images/icons/indicator/about_indicator.png new file mode 100644 index 0000000..84fe86b Binary files /dev/null and b/carioca/Ressources/Images/icons/indicator/about_indicator.png differ diff --git a/carioca/Ressources/Images/icons/indicator/hamburger_indicator.png b/carioca/Ressources/Images/icons/indicator/hamburger_indicator.png new file mode 100644 index 0000000..54f2d02 Binary files /dev/null and b/carioca/Ressources/Images/icons/indicator/hamburger_indicator.png differ diff --git a/carioca/Ressources/Images/icons/indicator/hello_indicator.png b/carioca/Ressources/Images/icons/indicator/hello_indicator.png new file mode 100644 index 0000000..5147bb9 Binary files /dev/null and b/carioca/Ressources/Images/icons/indicator/hello_indicator.png differ diff --git a/carioca/Ressources/Images/icons/indicator/map_indicator.png b/carioca/Ressources/Images/icons/indicator/map_indicator.png new file mode 100644 index 0000000..7d89f7e Binary files /dev/null and b/carioca/Ressources/Images/icons/indicator/map_indicator.png differ diff --git a/carioca/Ressources/Images/icons/indicator/settings_indicator.png b/carioca/Ressources/Images/icons/indicator/settings_indicator.png new file mode 100644 index 0000000..59ca69f Binary files /dev/null and b/carioca/Ressources/Images/icons/indicator/settings_indicator.png differ diff --git a/carioca/Ressources/Images/icons/menu/about_menu.png b/carioca/Ressources/Images/icons/menu/about_menu.png new file mode 100644 index 0000000..16ad42a Binary files /dev/null and b/carioca/Ressources/Images/icons/menu/about_menu.png differ diff --git a/carioca/Ressources/Images/icons/menu/hamburger_menu.png b/carioca/Ressources/Images/icons/menu/hamburger_menu.png new file mode 100644 index 0000000..ed2583e Binary files /dev/null and b/carioca/Ressources/Images/icons/menu/hamburger_menu.png differ diff --git a/carioca/Ressources/Images/icons/menu/hello_menu.png b/carioca/Ressources/Images/icons/menu/hello_menu.png new file mode 100644 index 0000000..8f0aaa4 Binary files /dev/null and b/carioca/Ressources/Images/icons/menu/hello_menu.png differ diff --git a/carioca/Ressources/Images/icons/menu/map_menu.png b/carioca/Ressources/Images/icons/menu/map_menu.png new file mode 100644 index 0000000..38ff2c9 Binary files /dev/null and b/carioca/Ressources/Images/icons/menu/map_menu.png differ diff --git a/carioca/Ressources/Images/icons/menu/settings_menu.png b/carioca/Ressources/Images/icons/menu/settings_menu.png new file mode 100644 index 0000000..d4136ce Binary files /dev/null and b/carioca/Ressources/Images/icons/menu/settings_menu.png differ diff --git a/carioca/Ressources/demoContent/arnaud.schloune.png b/carioca/Ressources/demoContent/arnaud.schloune.png new file mode 100644 index 0000000..0fefcb8 Binary files /dev/null and b/carioca/Ressources/demoContent/arnaud.schloune.png differ diff --git a/carioca/carioca-Bridging-Header.h b/carioca/carioca-Bridging-Header.h new file mode 100644 index 0000000..bee479d --- /dev/null +++ b/carioca/carioca-Bridging-Header.h @@ -0,0 +1,5 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#import "COSTouchVisualizerWindow.h" \ No newline at end of file diff --git a/cariocaTests/Info.plist b/cariocaTests/Info.plist new file mode 100644 index 0000000..ba72822 --- /dev/null +++ b/cariocaTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/cariocaTests/cariocaTests.swift b/cariocaTests/cariocaTests.swift new file mode 100644 index 0000000..5c2e859 --- /dev/null +++ b/cariocaTests/cariocaTests.swift @@ -0,0 +1,36 @@ +// +// cariocaTests.swift +// cariocaTests +// +// Created by Arnaud Schloune on 25/07/15. +// Copyright (c) 2015 Arnaud Schloune. All rights reserved. +// + +import UIKit +import XCTest + +class cariocaTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testExample() { + // This is an example of a functional test case. + XCTAssert(true, "Pass") + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measureBlock() { + // Put the code you want to measure the time of here. + } + } + +}