├── DLWidgetMenu.podspec ├── DLWidgetMenu.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ └── DLWidgetMenu.xccheckout │ └── xcuserdata │ └── jacek.xcuserdatad │ └── UserInterfaceState.xcuserstate ├── DLWidgetMenu ├── Base.lproj │ ├── Main_iPad.storyboard │ └── Main_iPhone.storyboard ├── Classes │ ├── DLWMLinearLayout.h │ ├── DLWMLinearLayout.m │ ├── DLWMMenu.h │ ├── DLWMMenu.m │ ├── DLWMMenuAnimator.h │ ├── DLWMMenuAnimator.m │ ├── DLWMMenuItem.h │ ├── DLWMMenuItem.m │ ├── DLWMRadialLayout.h │ ├── DLWMRadialLayout.m │ ├── DLWMSelectionMenuAnimator.h │ ├── DLWMSelectionMenuAnimator.m │ ├── DLWMSpiralLayout.h │ ├── DLWMSpiralLayout.m │ ├── DLWMSpringMenuAnimator.h │ └── DLWMSpringMenuAnimator.m ├── DLWMAppDelegate.h ├── DLWMAppDelegate.m ├── DLWMGenericMenuViewController.h ├── DLWMGenericMenuViewController.m ├── DLWMLinearMenuViewController.h ├── DLWMLinearMenuViewController.m ├── DLWMRadialMenuViewController.h ├── DLWMRadialMenuViewController.m ├── DLWMSpiralMenuViewController.h ├── DLWMSpiralMenuViewController.m ├── DLWidgetMenu-Info.plist ├── DLWidgetMenu-Prefix.pch ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── LaunchImage.launchimage │ │ └── Contents.json │ ├── linear.imageset │ │ ├── Contents.json │ │ ├── linear.png │ │ └── linear@2x.png │ ├── radial.imageset │ │ ├── Contents.json │ │ ├── radial.png │ │ └── radial@2x.png │ └── spiral.imageset │ │ ├── Contents.json │ │ ├── spiral.png │ │ └── spiral@2x.png ├── en.lproj │ └── InfoPlist.strings └── main.m ├── DLWidgetMenuTests ├── DLWidgetMenuTests-Info.plist ├── DLWidgetMenuTests.m └── en.lproj │ └── InfoPlist.strings ├── LICENSE ├── README.md ├── icons.psd └── screencast.gif /DLWidgetMenu.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = "DLWidgetMenu" 4 | s.version = "1.2" 5 | s.summary = "Versatile solution for displaying widget menus. Easily adjustable with custom layouts and/or animations." 6 | 7 | s.description = <<-DESC 8 | DLWidgetMenu is a versatile solution for displaying widget menus and can easily be extended with custom layouts and/or animations 9 | 10 | ## Features 11 | 12 | DLWidgetMenu** comes with three built-in layouts: 13 | 14 | * `DLWMLinearLayout` 15 | * `DLWMRadialLayout` 16 | * `DLWMSpiralLayout` 17 | 18 | **DLWidgetMenu** uses iOS7's **spring animations** (`DLWMSpringMenuAnimator`) by default, but can easily be provided with your own **custom animations** as well (by subclassing `DLWMMenuAnimator`). 19 | 20 | **DLWidgetMenu** observes its menu items for these **gesture recognizer events**: 21 | 22 | * single tap 23 | * double tap 24 | * long press 25 | * pan 26 | * pinch 27 | 28 | and **forwards them** directly to its **delegate**. 29 | DESC 30 | 31 | s.homepage = "https://github.com/regexident/DLWidgetMenu" 32 | s.license = { :type => 'BSD-3', :file => 'LICENSE' } 33 | s.author = { "Vincent Esche" => "regexident@gmail.com" } 34 | s.platform = :ios, '6.1' 35 | s.source = { :git => "https://github.com/regexident/DLWidgetMenu.git", :tag => '1.2' } 36 | s.source_files = 'DLWidgetMenu/Classes/*.{h,m}' 37 | s.requires_arc = true 38 | 39 | end 40 | -------------------------------------------------------------------------------- /DLWidgetMenu.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C26DB4A818294C7900888F54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C26DB4A718294C7900888F54 /* Foundation.framework */; }; 11 | C26DB4AA18294C7900888F54 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C26DB4A918294C7900888F54 /* CoreGraphics.framework */; }; 12 | C26DB4AC18294C7900888F54 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C26DB4AB18294C7900888F54 /* UIKit.framework */; }; 13 | C26DB4B218294C7900888F54 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C26DB4B018294C7900888F54 /* InfoPlist.strings */; }; 14 | C26DB4B418294C7900888F54 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C26DB4B318294C7900888F54 /* main.m */; }; 15 | C26DB4B818294C7900888F54 /* DLWMAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = C26DB4B718294C7900888F54 /* DLWMAppDelegate.m */; }; 16 | C26DB4BB18294C7900888F54 /* Main_iPhone.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C26DB4B918294C7900888F54 /* Main_iPhone.storyboard */; }; 17 | C26DB4C118294C7900888F54 /* DLWMRadialMenuViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C26DB4C018294C7900888F54 /* DLWMRadialMenuViewController.m */; }; 18 | C26DB4C418294C7900888F54 /* DLWMLinearMenuViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C26DB4C318294C7900888F54 /* DLWMLinearMenuViewController.m */; }; 19 | C26DB4C618294C7900888F54 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C26DB4C518294C7900888F54 /* Images.xcassets */; }; 20 | C26DB4CD18294C7900888F54 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C26DB4CC18294C7900888F54 /* XCTest.framework */; }; 21 | C26DB4CE18294C7900888F54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C26DB4A718294C7900888F54 /* Foundation.framework */; }; 22 | C26DB4CF18294C7900888F54 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C26DB4AB18294C7900888F54 /* UIKit.framework */; }; 23 | C26DB4D718294C7900888F54 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C26DB4D518294C7900888F54 /* InfoPlist.strings */; }; 24 | C26DB4D918294C7900888F54 /* DLWidgetMenuTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C26DB4D818294C7900888F54 /* DLWidgetMenuTests.m */; }; 25 | C26DB4E818294D1C00888F54 /* DLWMMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = C26DB4E718294D1C00888F54 /* DLWMMenu.m */; }; 26 | C26DB4EB18295D5500888F54 /* DLWMMenuItem.m in Sources */ = {isa = PBXBuildFile; fileRef = C26DB4EA18295D5500888F54 /* DLWMMenuItem.m */; }; 27 | C26DB4EE1829838D00888F54 /* DLWMLinearLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = C26DB4ED1829838D00888F54 /* DLWMLinearLayout.m */; }; 28 | C26DB4F118299B6400888F54 /* DLWMSpringMenuAnimator.m in Sources */ = {isa = PBXBuildFile; fileRef = C26DB4F018299B6400888F54 /* DLWMSpringMenuAnimator.m */; }; 29 | C26DB4FA1829B25100888F54 /* DLWMSelectionMenuAnimator.m in Sources */ = {isa = PBXBuildFile; fileRef = C26DB4F91829B25100888F54 /* DLWMSelectionMenuAnimator.m */; }; 30 | C26DB4FD1829BB6900888F54 /* DLWMMenuAnimator.m in Sources */ = {isa = PBXBuildFile; fileRef = C26DB4FC1829BB6900888F54 /* DLWMMenuAnimator.m */; }; 31 | C26DB5011829D85A00888F54 /* DLWMRadialLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = C26DB5001829D85A00888F54 /* DLWMRadialLayout.m */; }; 32 | C2E9564E187F062300F3D549 /* DLWMSpiralLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = C2E9564D187F062300F3D549 /* DLWMSpiralLayout.m */; }; 33 | C2E95651187F3A9A00F3D549 /* DLWMGenericMenuViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C2E95650187F3A9A00F3D549 /* DLWMGenericMenuViewController.m */; }; 34 | C2E95654187F40DD00F3D549 /* DLWMSpiralMenuViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C2E95653187F40DD00F3D549 /* DLWMSpiralMenuViewController.m */; }; 35 | /* End PBXBuildFile section */ 36 | 37 | /* Begin PBXContainerItemProxy section */ 38 | C26DB4D018294C7900888F54 /* PBXContainerItemProxy */ = { 39 | isa = PBXContainerItemProxy; 40 | containerPortal = C26DB49C18294C7900888F54 /* Project object */; 41 | proxyType = 1; 42 | remoteGlobalIDString = C26DB4A318294C7900888F54; 43 | remoteInfo = DLWidgetMenu; 44 | }; 45 | /* End PBXContainerItemProxy section */ 46 | 47 | /* Begin PBXFileReference section */ 48 | C26DB4A418294C7900888F54 /* DLWidgetMenu.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DLWidgetMenu.app; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | C26DB4A718294C7900888F54 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 50 | C26DB4A918294C7900888F54 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 51 | C26DB4AB18294C7900888F54 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 52 | C26DB4AF18294C7900888F54 /* DLWidgetMenu-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "DLWidgetMenu-Info.plist"; sourceTree = ""; }; 53 | C26DB4B118294C7900888F54 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 54 | C26DB4B318294C7900888F54 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 55 | C26DB4B518294C7900888F54 /* DLWidgetMenu-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "DLWidgetMenu-Prefix.pch"; sourceTree = ""; }; 56 | C26DB4B618294C7900888F54 /* DLWMAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DLWMAppDelegate.h; sourceTree = ""; }; 57 | C26DB4B718294C7900888F54 /* DLWMAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DLWMAppDelegate.m; sourceTree = ""; }; 58 | C26DB4BA18294C7900888F54 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main_iPhone.storyboard; sourceTree = ""; }; 59 | C26DB4BF18294C7900888F54 /* DLWMRadialMenuViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DLWMRadialMenuViewController.h; sourceTree = ""; }; 60 | C26DB4C018294C7900888F54 /* DLWMRadialMenuViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DLWMRadialMenuViewController.m; sourceTree = ""; }; 61 | C26DB4C218294C7900888F54 /* DLWMLinearMenuViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DLWMLinearMenuViewController.h; sourceTree = ""; }; 62 | C26DB4C318294C7900888F54 /* DLWMLinearMenuViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DLWMLinearMenuViewController.m; sourceTree = ""; }; 63 | C26DB4C518294C7900888F54 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 64 | C26DB4CB18294C7900888F54 /* DLWidgetMenuTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DLWidgetMenuTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 65 | C26DB4CC18294C7900888F54 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 66 | C26DB4D418294C7900888F54 /* DLWidgetMenuTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "DLWidgetMenuTests-Info.plist"; sourceTree = ""; }; 67 | C26DB4D618294C7900888F54 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 68 | C26DB4D818294C7900888F54 /* DLWidgetMenuTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DLWidgetMenuTests.m; sourceTree = ""; }; 69 | C26DB4E618294D1C00888F54 /* DLWMMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DLWMMenu.h; sourceTree = ""; }; 70 | C26DB4E718294D1C00888F54 /* DLWMMenu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DLWMMenu.m; sourceTree = ""; }; 71 | C26DB4E918295D5500888F54 /* DLWMMenuItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DLWMMenuItem.h; sourceTree = ""; }; 72 | C26DB4EA18295D5500888F54 /* DLWMMenuItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DLWMMenuItem.m; sourceTree = ""; }; 73 | C26DB4EC1829838D00888F54 /* DLWMLinearLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DLWMLinearLayout.h; sourceTree = ""; }; 74 | C26DB4ED1829838D00888F54 /* DLWMLinearLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DLWMLinearLayout.m; sourceTree = ""; }; 75 | C26DB4EF18299B6400888F54 /* DLWMSpringMenuAnimator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DLWMSpringMenuAnimator.h; sourceTree = ""; }; 76 | C26DB4F018299B6400888F54 /* DLWMSpringMenuAnimator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DLWMSpringMenuAnimator.m; sourceTree = ""; }; 77 | C26DB4F81829B25100888F54 /* DLWMSelectionMenuAnimator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DLWMSelectionMenuAnimator.h; sourceTree = ""; }; 78 | C26DB4F91829B25100888F54 /* DLWMSelectionMenuAnimator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DLWMSelectionMenuAnimator.m; sourceTree = ""; }; 79 | C26DB4FB1829BB6900888F54 /* DLWMMenuAnimator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DLWMMenuAnimator.h; sourceTree = ""; }; 80 | C26DB4FC1829BB6900888F54 /* DLWMMenuAnimator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DLWMMenuAnimator.m; sourceTree = ""; }; 81 | C26DB4FF1829D85A00888F54 /* DLWMRadialLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DLWMRadialLayout.h; sourceTree = ""; }; 82 | C26DB5001829D85A00888F54 /* DLWMRadialLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DLWMRadialLayout.m; sourceTree = ""; }; 83 | C2E9564C187F062300F3D549 /* DLWMSpiralLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DLWMSpiralLayout.h; sourceTree = ""; }; 84 | C2E9564D187F062300F3D549 /* DLWMSpiralLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DLWMSpiralLayout.m; sourceTree = ""; }; 85 | C2E9564F187F3A9A00F3D549 /* DLWMGenericMenuViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DLWMGenericMenuViewController.h; sourceTree = ""; }; 86 | C2E95650187F3A9A00F3D549 /* DLWMGenericMenuViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DLWMGenericMenuViewController.m; sourceTree = ""; }; 87 | C2E95652187F40DD00F3D549 /* DLWMSpiralMenuViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DLWMSpiralMenuViewController.h; sourceTree = ""; }; 88 | C2E95653187F40DD00F3D549 /* DLWMSpiralMenuViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DLWMSpiralMenuViewController.m; sourceTree = ""; }; 89 | /* End PBXFileReference section */ 90 | 91 | /* Begin PBXFrameworksBuildPhase section */ 92 | C26DB4A118294C7900888F54 /* Frameworks */ = { 93 | isa = PBXFrameworksBuildPhase; 94 | buildActionMask = 2147483647; 95 | files = ( 96 | C26DB4AA18294C7900888F54 /* CoreGraphics.framework in Frameworks */, 97 | C26DB4AC18294C7900888F54 /* UIKit.framework in Frameworks */, 98 | C26DB4A818294C7900888F54 /* Foundation.framework in Frameworks */, 99 | ); 100 | runOnlyForDeploymentPostprocessing = 0; 101 | }; 102 | C26DB4C818294C7900888F54 /* Frameworks */ = { 103 | isa = PBXFrameworksBuildPhase; 104 | buildActionMask = 2147483647; 105 | files = ( 106 | C26DB4CD18294C7900888F54 /* XCTest.framework in Frameworks */, 107 | C26DB4CF18294C7900888F54 /* UIKit.framework in Frameworks */, 108 | C26DB4CE18294C7900888F54 /* Foundation.framework in Frameworks */, 109 | ); 110 | runOnlyForDeploymentPostprocessing = 0; 111 | }; 112 | /* End PBXFrameworksBuildPhase section */ 113 | 114 | /* Begin PBXGroup section */ 115 | C26DB49B18294C7900888F54 = { 116 | isa = PBXGroup; 117 | children = ( 118 | C26DB4AD18294C7900888F54 /* DLWidgetMenu */, 119 | C26DB4D218294C7900888F54 /* DLWidgetMenuTests */, 120 | C26DB4A618294C7900888F54 /* Frameworks */, 121 | C26DB4A518294C7900888F54 /* Products */, 122 | ); 123 | sourceTree = ""; 124 | }; 125 | C26DB4A518294C7900888F54 /* Products */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | C26DB4A418294C7900888F54 /* DLWidgetMenu.app */, 129 | C26DB4CB18294C7900888F54 /* DLWidgetMenuTests.xctest */, 130 | ); 131 | name = Products; 132 | sourceTree = ""; 133 | }; 134 | C26DB4A618294C7900888F54 /* Frameworks */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | C26DB4A718294C7900888F54 /* Foundation.framework */, 138 | C26DB4A918294C7900888F54 /* CoreGraphics.framework */, 139 | C26DB4AB18294C7900888F54 /* UIKit.framework */, 140 | C26DB4CC18294C7900888F54 /* XCTest.framework */, 141 | ); 142 | name = Frameworks; 143 | sourceTree = ""; 144 | }; 145 | C26DB4AD18294C7900888F54 /* DLWidgetMenu */ = { 146 | isa = PBXGroup; 147 | children = ( 148 | C26DB4E518294D1C00888F54 /* Classes */, 149 | C26DB4B618294C7900888F54 /* DLWMAppDelegate.h */, 150 | C26DB4B718294C7900888F54 /* DLWMAppDelegate.m */, 151 | C26DB4B918294C7900888F54 /* Main_iPhone.storyboard */, 152 | C2E9564F187F3A9A00F3D549 /* DLWMGenericMenuViewController.h */, 153 | C2E95650187F3A9A00F3D549 /* DLWMGenericMenuViewController.m */, 154 | C26DB4C218294C7900888F54 /* DLWMLinearMenuViewController.h */, 155 | C26DB4C318294C7900888F54 /* DLWMLinearMenuViewController.m */, 156 | C26DB4BF18294C7900888F54 /* DLWMRadialMenuViewController.h */, 157 | C26DB4C018294C7900888F54 /* DLWMRadialMenuViewController.m */, 158 | C2E95652187F40DD00F3D549 /* DLWMSpiralMenuViewController.h */, 159 | C2E95653187F40DD00F3D549 /* DLWMSpiralMenuViewController.m */, 160 | C26DB4C518294C7900888F54 /* Images.xcassets */, 161 | C26DB4AE18294C7900888F54 /* Supporting Files */, 162 | ); 163 | path = DLWidgetMenu; 164 | sourceTree = ""; 165 | }; 166 | C26DB4AE18294C7900888F54 /* Supporting Files */ = { 167 | isa = PBXGroup; 168 | children = ( 169 | C26DB4AF18294C7900888F54 /* DLWidgetMenu-Info.plist */, 170 | C26DB4B018294C7900888F54 /* InfoPlist.strings */, 171 | C26DB4B318294C7900888F54 /* main.m */, 172 | C26DB4B518294C7900888F54 /* DLWidgetMenu-Prefix.pch */, 173 | ); 174 | name = "Supporting Files"; 175 | sourceTree = ""; 176 | }; 177 | C26DB4D218294C7900888F54 /* DLWidgetMenuTests */ = { 178 | isa = PBXGroup; 179 | children = ( 180 | C26DB4D818294C7900888F54 /* DLWidgetMenuTests.m */, 181 | C26DB4D318294C7900888F54 /* Supporting Files */, 182 | ); 183 | path = DLWidgetMenuTests; 184 | sourceTree = ""; 185 | }; 186 | C26DB4D318294C7900888F54 /* Supporting Files */ = { 187 | isa = PBXGroup; 188 | children = ( 189 | C26DB4D418294C7900888F54 /* DLWidgetMenuTests-Info.plist */, 190 | C26DB4D518294C7900888F54 /* InfoPlist.strings */, 191 | ); 192 | name = "Supporting Files"; 193 | sourceTree = ""; 194 | }; 195 | C26DB4E518294D1C00888F54 /* Classes */ = { 196 | isa = PBXGroup; 197 | children = ( 198 | C26DB4E918295D5500888F54 /* DLWMMenuItem.h */, 199 | C26DB4EA18295D5500888F54 /* DLWMMenuItem.m */, 200 | C26DB4E618294D1C00888F54 /* DLWMMenu.h */, 201 | C26DB4E718294D1C00888F54 /* DLWMMenu.m */, 202 | C26DB4EC1829838D00888F54 /* DLWMLinearLayout.h */, 203 | C26DB4ED1829838D00888F54 /* DLWMLinearLayout.m */, 204 | C26DB4FF1829D85A00888F54 /* DLWMRadialLayout.h */, 205 | C26DB5001829D85A00888F54 /* DLWMRadialLayout.m */, 206 | C2E9564C187F062300F3D549 /* DLWMSpiralLayout.h */, 207 | C2E9564D187F062300F3D549 /* DLWMSpiralLayout.m */, 208 | C26DB4FB1829BB6900888F54 /* DLWMMenuAnimator.h */, 209 | C26DB4FC1829BB6900888F54 /* DLWMMenuAnimator.m */, 210 | C26DB4EF18299B6400888F54 /* DLWMSpringMenuAnimator.h */, 211 | C26DB4F018299B6400888F54 /* DLWMSpringMenuAnimator.m */, 212 | C26DB4F81829B25100888F54 /* DLWMSelectionMenuAnimator.h */, 213 | C26DB4F91829B25100888F54 /* DLWMSelectionMenuAnimator.m */, 214 | ); 215 | path = Classes; 216 | sourceTree = ""; 217 | }; 218 | /* End PBXGroup section */ 219 | 220 | /* Begin PBXNativeTarget section */ 221 | C26DB4A318294C7900888F54 /* DLWidgetMenu */ = { 222 | isa = PBXNativeTarget; 223 | buildConfigurationList = C26DB4DC18294C7900888F54 /* Build configuration list for PBXNativeTarget "DLWidgetMenu" */; 224 | buildPhases = ( 225 | C26DB4A018294C7900888F54 /* Sources */, 226 | C26DB4A118294C7900888F54 /* Frameworks */, 227 | C26DB4A218294C7900888F54 /* Resources */, 228 | ); 229 | buildRules = ( 230 | ); 231 | dependencies = ( 232 | ); 233 | name = DLWidgetMenu; 234 | productName = DLWidgetMenu; 235 | productReference = C26DB4A418294C7900888F54 /* DLWidgetMenu.app */; 236 | productType = "com.apple.product-type.application"; 237 | }; 238 | C26DB4CA18294C7900888F54 /* DLWidgetMenuTests */ = { 239 | isa = PBXNativeTarget; 240 | buildConfigurationList = C26DB4DF18294C7900888F54 /* Build configuration list for PBXNativeTarget "DLWidgetMenuTests" */; 241 | buildPhases = ( 242 | C26DB4C718294C7900888F54 /* Sources */, 243 | C26DB4C818294C7900888F54 /* Frameworks */, 244 | C26DB4C918294C7900888F54 /* Resources */, 245 | ); 246 | buildRules = ( 247 | ); 248 | dependencies = ( 249 | C26DB4D118294C7900888F54 /* PBXTargetDependency */, 250 | ); 251 | name = DLWidgetMenuTests; 252 | productName = DLWidgetMenuTests; 253 | productReference = C26DB4CB18294C7900888F54 /* DLWidgetMenuTests.xctest */; 254 | productType = "com.apple.product-type.bundle.unit-test"; 255 | }; 256 | /* End PBXNativeTarget section */ 257 | 258 | /* Begin PBXProject section */ 259 | C26DB49C18294C7900888F54 /* Project object */ = { 260 | isa = PBXProject; 261 | attributes = { 262 | CLASSPREFIX = DLWM; 263 | LastUpgradeCheck = 0500; 264 | ORGANIZATIONNAME = "Definite Loop"; 265 | TargetAttributes = { 266 | C26DB4CA18294C7900888F54 = { 267 | TestTargetID = C26DB4A318294C7900888F54; 268 | }; 269 | }; 270 | }; 271 | buildConfigurationList = C26DB49F18294C7900888F54 /* Build configuration list for PBXProject "DLWidgetMenu" */; 272 | compatibilityVersion = "Xcode 3.2"; 273 | developmentRegion = English; 274 | hasScannedForEncodings = 0; 275 | knownRegions = ( 276 | en, 277 | Base, 278 | ); 279 | mainGroup = C26DB49B18294C7900888F54; 280 | productRefGroup = C26DB4A518294C7900888F54 /* Products */; 281 | projectDirPath = ""; 282 | projectRoot = ""; 283 | targets = ( 284 | C26DB4A318294C7900888F54 /* DLWidgetMenu */, 285 | C26DB4CA18294C7900888F54 /* DLWidgetMenuTests */, 286 | ); 287 | }; 288 | /* End PBXProject section */ 289 | 290 | /* Begin PBXResourcesBuildPhase section */ 291 | C26DB4A218294C7900888F54 /* Resources */ = { 292 | isa = PBXResourcesBuildPhase; 293 | buildActionMask = 2147483647; 294 | files = ( 295 | C26DB4C618294C7900888F54 /* Images.xcassets in Resources */, 296 | C26DB4BB18294C7900888F54 /* Main_iPhone.storyboard in Resources */, 297 | C26DB4B218294C7900888F54 /* InfoPlist.strings in Resources */, 298 | ); 299 | runOnlyForDeploymentPostprocessing = 0; 300 | }; 301 | C26DB4C918294C7900888F54 /* Resources */ = { 302 | isa = PBXResourcesBuildPhase; 303 | buildActionMask = 2147483647; 304 | files = ( 305 | C26DB4D718294C7900888F54 /* InfoPlist.strings in Resources */, 306 | ); 307 | runOnlyForDeploymentPostprocessing = 0; 308 | }; 309 | /* End PBXResourcesBuildPhase section */ 310 | 311 | /* Begin PBXSourcesBuildPhase section */ 312 | C26DB4A018294C7900888F54 /* Sources */ = { 313 | isa = PBXSourcesBuildPhase; 314 | buildActionMask = 2147483647; 315 | files = ( 316 | C2E95651187F3A9A00F3D549 /* DLWMGenericMenuViewController.m in Sources */, 317 | C2E95654187F40DD00F3D549 /* DLWMSpiralMenuViewController.m in Sources */, 318 | C26DB4FD1829BB6900888F54 /* DLWMMenuAnimator.m in Sources */, 319 | C26DB4C418294C7900888F54 /* DLWMLinearMenuViewController.m in Sources */, 320 | C26DB4FA1829B25100888F54 /* DLWMSelectionMenuAnimator.m in Sources */, 321 | C26DB4F118299B6400888F54 /* DLWMSpringMenuAnimator.m in Sources */, 322 | C26DB4EE1829838D00888F54 /* DLWMLinearLayout.m in Sources */, 323 | C26DB4C118294C7900888F54 /* DLWMRadialMenuViewController.m in Sources */, 324 | C2E9564E187F062300F3D549 /* DLWMSpiralLayout.m in Sources */, 325 | C26DB4B818294C7900888F54 /* DLWMAppDelegate.m in Sources */, 326 | C26DB4EB18295D5500888F54 /* DLWMMenuItem.m in Sources */, 327 | C26DB4E818294D1C00888F54 /* DLWMMenu.m in Sources */, 328 | C26DB5011829D85A00888F54 /* DLWMRadialLayout.m in Sources */, 329 | C26DB4B418294C7900888F54 /* main.m in Sources */, 330 | ); 331 | runOnlyForDeploymentPostprocessing = 0; 332 | }; 333 | C26DB4C718294C7900888F54 /* Sources */ = { 334 | isa = PBXSourcesBuildPhase; 335 | buildActionMask = 2147483647; 336 | files = ( 337 | C26DB4D918294C7900888F54 /* DLWidgetMenuTests.m in Sources */, 338 | ); 339 | runOnlyForDeploymentPostprocessing = 0; 340 | }; 341 | /* End PBXSourcesBuildPhase section */ 342 | 343 | /* Begin PBXTargetDependency section */ 344 | C26DB4D118294C7900888F54 /* PBXTargetDependency */ = { 345 | isa = PBXTargetDependency; 346 | target = C26DB4A318294C7900888F54 /* DLWidgetMenu */; 347 | targetProxy = C26DB4D018294C7900888F54 /* PBXContainerItemProxy */; 348 | }; 349 | /* End PBXTargetDependency section */ 350 | 351 | /* Begin PBXVariantGroup section */ 352 | C26DB4B018294C7900888F54 /* InfoPlist.strings */ = { 353 | isa = PBXVariantGroup; 354 | children = ( 355 | C26DB4B118294C7900888F54 /* en */, 356 | ); 357 | name = InfoPlist.strings; 358 | sourceTree = ""; 359 | }; 360 | C26DB4B918294C7900888F54 /* Main_iPhone.storyboard */ = { 361 | isa = PBXVariantGroup; 362 | children = ( 363 | C26DB4BA18294C7900888F54 /* Base */, 364 | ); 365 | name = Main_iPhone.storyboard; 366 | sourceTree = ""; 367 | }; 368 | C26DB4D518294C7900888F54 /* InfoPlist.strings */ = { 369 | isa = PBXVariantGroup; 370 | children = ( 371 | C26DB4D618294C7900888F54 /* en */, 372 | ); 373 | name = InfoPlist.strings; 374 | sourceTree = ""; 375 | }; 376 | /* End PBXVariantGroup section */ 377 | 378 | /* Begin XCBuildConfiguration section */ 379 | C26DB4DA18294C7900888F54 /* Debug */ = { 380 | isa = XCBuildConfiguration; 381 | buildSettings = { 382 | ALWAYS_SEARCH_USER_PATHS = NO; 383 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 384 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 385 | CLANG_CXX_LIBRARY = "libc++"; 386 | CLANG_ENABLE_MODULES = YES; 387 | CLANG_ENABLE_OBJC_ARC = YES; 388 | CLANG_WARN_BOOL_CONVERSION = YES; 389 | CLANG_WARN_CONSTANT_CONVERSION = YES; 390 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 391 | CLANG_WARN_EMPTY_BODY = YES; 392 | CLANG_WARN_ENUM_CONVERSION = YES; 393 | CLANG_WARN_INT_CONVERSION = YES; 394 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 395 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 396 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 397 | COPY_PHASE_STRIP = NO; 398 | GCC_C_LANGUAGE_STANDARD = gnu99; 399 | GCC_DYNAMIC_NO_PIC = NO; 400 | GCC_OPTIMIZATION_LEVEL = 0; 401 | GCC_PREPROCESSOR_DEFINITIONS = ( 402 | "DEBUG=1", 403 | "$(inherited)", 404 | ); 405 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 406 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 407 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 408 | GCC_WARN_UNDECLARED_SELECTOR = YES; 409 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 410 | GCC_WARN_UNUSED_FUNCTION = YES; 411 | GCC_WARN_UNUSED_VARIABLE = YES; 412 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 413 | ONLY_ACTIVE_ARCH = YES; 414 | SDKROOT = iphoneos; 415 | TARGETED_DEVICE_FAMILY = "1,2"; 416 | }; 417 | name = Debug; 418 | }; 419 | C26DB4DB18294C7900888F54 /* Release */ = { 420 | isa = XCBuildConfiguration; 421 | buildSettings = { 422 | ALWAYS_SEARCH_USER_PATHS = NO; 423 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 424 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 425 | CLANG_CXX_LIBRARY = "libc++"; 426 | CLANG_ENABLE_MODULES = YES; 427 | CLANG_ENABLE_OBJC_ARC = YES; 428 | CLANG_WARN_BOOL_CONVERSION = YES; 429 | CLANG_WARN_CONSTANT_CONVERSION = YES; 430 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 431 | CLANG_WARN_EMPTY_BODY = YES; 432 | CLANG_WARN_ENUM_CONVERSION = YES; 433 | CLANG_WARN_INT_CONVERSION = YES; 434 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 435 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 436 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 437 | COPY_PHASE_STRIP = YES; 438 | ENABLE_NS_ASSERTIONS = NO; 439 | GCC_C_LANGUAGE_STANDARD = gnu99; 440 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 441 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 442 | GCC_WARN_UNDECLARED_SELECTOR = YES; 443 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 444 | GCC_WARN_UNUSED_FUNCTION = YES; 445 | GCC_WARN_UNUSED_VARIABLE = YES; 446 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 447 | SDKROOT = iphoneos; 448 | TARGETED_DEVICE_FAMILY = "1,2"; 449 | VALIDATE_PRODUCT = YES; 450 | }; 451 | name = Release; 452 | }; 453 | C26DB4DD18294C7900888F54 /* Debug */ = { 454 | isa = XCBuildConfiguration; 455 | buildSettings = { 456 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 457 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 458 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 459 | GCC_PREFIX_HEADER = "DLWidgetMenu/DLWidgetMenu-Prefix.pch"; 460 | INFOPLIST_FILE = "DLWidgetMenu/DLWidgetMenu-Info.plist"; 461 | PRODUCT_NAME = "$(TARGET_NAME)"; 462 | WRAPPER_EXTENSION = app; 463 | }; 464 | name = Debug; 465 | }; 466 | C26DB4DE18294C7900888F54 /* Release */ = { 467 | isa = XCBuildConfiguration; 468 | buildSettings = { 469 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 470 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 471 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 472 | GCC_PREFIX_HEADER = "DLWidgetMenu/DLWidgetMenu-Prefix.pch"; 473 | INFOPLIST_FILE = "DLWidgetMenu/DLWidgetMenu-Info.plist"; 474 | PRODUCT_NAME = "$(TARGET_NAME)"; 475 | WRAPPER_EXTENSION = app; 476 | }; 477 | name = Release; 478 | }; 479 | C26DB4E018294C7900888F54 /* Debug */ = { 480 | isa = XCBuildConfiguration; 481 | buildSettings = { 482 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 483 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/DLWidgetMenu.app/DLWidgetMenu"; 484 | FRAMEWORK_SEARCH_PATHS = ( 485 | "$(SDKROOT)/Developer/Library/Frameworks", 486 | "$(inherited)", 487 | "$(DEVELOPER_FRAMEWORKS_DIR)", 488 | ); 489 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 490 | GCC_PREFIX_HEADER = "DLWidgetMenu/DLWidgetMenu-Prefix.pch"; 491 | GCC_PREPROCESSOR_DEFINITIONS = ( 492 | "DEBUG=1", 493 | "$(inherited)", 494 | ); 495 | INFOPLIST_FILE = "DLWidgetMenuTests/DLWidgetMenuTests-Info.plist"; 496 | PRODUCT_NAME = "$(TARGET_NAME)"; 497 | TEST_HOST = "$(BUNDLE_LOADER)"; 498 | WRAPPER_EXTENSION = xctest; 499 | }; 500 | name = Debug; 501 | }; 502 | C26DB4E118294C7900888F54 /* Release */ = { 503 | isa = XCBuildConfiguration; 504 | buildSettings = { 505 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 506 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/DLWidgetMenu.app/DLWidgetMenu"; 507 | FRAMEWORK_SEARCH_PATHS = ( 508 | "$(SDKROOT)/Developer/Library/Frameworks", 509 | "$(inherited)", 510 | "$(DEVELOPER_FRAMEWORKS_DIR)", 511 | ); 512 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 513 | GCC_PREFIX_HEADER = "DLWidgetMenu/DLWidgetMenu-Prefix.pch"; 514 | INFOPLIST_FILE = "DLWidgetMenuTests/DLWidgetMenuTests-Info.plist"; 515 | PRODUCT_NAME = "$(TARGET_NAME)"; 516 | TEST_HOST = "$(BUNDLE_LOADER)"; 517 | WRAPPER_EXTENSION = xctest; 518 | }; 519 | name = Release; 520 | }; 521 | /* End XCBuildConfiguration section */ 522 | 523 | /* Begin XCConfigurationList section */ 524 | C26DB49F18294C7900888F54 /* Build configuration list for PBXProject "DLWidgetMenu" */ = { 525 | isa = XCConfigurationList; 526 | buildConfigurations = ( 527 | C26DB4DA18294C7900888F54 /* Debug */, 528 | C26DB4DB18294C7900888F54 /* Release */, 529 | ); 530 | defaultConfigurationIsVisible = 0; 531 | defaultConfigurationName = Release; 532 | }; 533 | C26DB4DC18294C7900888F54 /* Build configuration list for PBXNativeTarget "DLWidgetMenu" */ = { 534 | isa = XCConfigurationList; 535 | buildConfigurations = ( 536 | C26DB4DD18294C7900888F54 /* Debug */, 537 | C26DB4DE18294C7900888F54 /* Release */, 538 | ); 539 | defaultConfigurationIsVisible = 0; 540 | defaultConfigurationName = Release; 541 | }; 542 | C26DB4DF18294C7900888F54 /* Build configuration list for PBXNativeTarget "DLWidgetMenuTests" */ = { 543 | isa = XCConfigurationList; 544 | buildConfigurations = ( 545 | C26DB4E018294C7900888F54 /* Debug */, 546 | C26DB4E118294C7900888F54 /* Release */, 547 | ); 548 | defaultConfigurationIsVisible = 0; 549 | defaultConfigurationName = Release; 550 | }; 551 | /* End XCConfigurationList section */ 552 | }; 553 | rootObject = C26DB49C18294C7900888F54 /* Project object */; 554 | } 555 | -------------------------------------------------------------------------------- /DLWidgetMenu.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DLWidgetMenu.xcodeproj/project.xcworkspace/xcshareddata/DLWidgetMenu.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | A5341130-DB9C-4F0E-AB5D-1A1E5F396D85 9 | IDESourceControlProjectName 10 | DLWidgetMenu 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 33EDE7BF-2BA7-49F3-908F-8AAE0089F904 14 | https://github.com/jmkk/DLWidgetMenu.git 15 | 16 | IDESourceControlProjectPath 17 | DLWidgetMenu.xcodeproj/project.xcworkspace 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 33EDE7BF-2BA7-49F3-908F-8AAE0089F904 21 | ../.. 22 | 23 | IDESourceControlProjectURL 24 | https://github.com/jmkk/DLWidgetMenu.git 25 | IDESourceControlProjectVersion 26 | 110 27 | IDESourceControlProjectWCCIdentifier 28 | 33EDE7BF-2BA7-49F3-908F-8AAE0089F904 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 33EDE7BF-2BA7-49F3-908F-8AAE0089F904 36 | IDESourceControlWCCName 37 | DLWidgetMenu 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /DLWidgetMenu.xcodeproj/project.xcworkspace/xcuserdata/jacek.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regexident/DLWidgetMenu/9e937801cd583fa08d331128babcefe012a7daae/DLWidgetMenu.xcodeproj/project.xcworkspace/xcuserdata/jacek.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /DLWidgetMenu/Base.lproj/Main_iPhone.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 30 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 72 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 93 | 100 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 121 | 128 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 199 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 244 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 266 | 273 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 354 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 408 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 431 | 438 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | -------------------------------------------------------------------------------- /DLWidgetMenu/Classes/DLWMLinearLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // DLWMLinearLayout.h 3 | // DLWidgetMenu 4 | // 5 | // Created by Vincent Esche on 05/11/13. 6 | // Copyright (c) 2013 Vincent Esche. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "DLWMMenu.h" 12 | 13 | @interface DLWMLinearLayout : NSObject 14 | 15 | @property (readwrite, assign, nonatomic) CGFloat angle; 16 | @property (readwrite, assign, nonatomic) CGFloat itemSpacing; 17 | @property (readwrite, assign, nonatomic) CGFloat centerSpacing; 18 | 19 | - (id)initWithAngle:(CGFloat)angle itemSpacing:(CGFloat)itemSpacing centerSpacing:(CGFloat)centerSpacing; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /DLWidgetMenu/Classes/DLWMLinearLayout.m: -------------------------------------------------------------------------------- 1 | // 2 | // DLWMLinearLayout.m 3 | // DLWidgetMenu 4 | // 5 | // Created by Vincent Esche on 05/11/13. 6 | // Copyright (c) 2013 Vincent Esche. All rights reserved. 7 | // 8 | 9 | #import "DLWMLinearLayout.h" 10 | 11 | #import "DLWMMenuItem.h" 12 | 13 | @implementation DLWMLinearLayout 14 | 15 | - (id)init { 16 | self = [super init]; 17 | if (self) { 18 | self.angle = 0.0; 19 | self.itemSpacing = 40.0; 20 | self.centerSpacing = 40.0; 21 | } 22 | return self; 23 | } 24 | 25 | - (id)initWithAngle:(CGFloat)angle itemSpacing:(CGFloat)itemSpacing centerSpacing:(CGFloat)centerSpacing { 26 | self = [self init]; 27 | if (self) { 28 | self.angle = angle; 29 | self.itemSpacing = itemSpacing; 30 | self.centerSpacing = centerSpacing; 31 | } 32 | return self; 33 | } 34 | 35 | - (void)layoutItems:(NSArray *)items forCenterPoint:(CGPoint)centerPoint inMenu:(DLWMMenu *)menu { 36 | CGFloat centerSpacing = self.centerSpacing; 37 | CGFloat itemSpacing = self.itemSpacing; 38 | CGFloat angle = self.angle; 39 | 40 | [items enumerateObjectsUsingBlock:^(DLWMMenuItem *item, NSUInteger index, BOOL *stop) { 41 | CGFloat offset = centerSpacing + itemSpacing * index; 42 | CGFloat x = centerPoint.x + (offset * cosf(angle)); 43 | CGFloat y = centerPoint.y + (offset * sinf(angle)); 44 | CGPoint itemCenter = CGPointMake(x, y); 45 | item.layoutLocation = itemCenter; 46 | }]; 47 | } 48 | 49 | - (void)setAngle:(CGFloat)angle { 50 | _angle = angle; 51 | [self announceChange]; 52 | } 53 | 54 | - (void)setItemSpacing:(CGFloat)itemSpacing { 55 | _itemSpacing = itemSpacing; 56 | [self announceChange]; 57 | } 58 | 59 | - (void)setCenterSpacing:(CGFloat)centerSpacing { 60 | _centerSpacing = centerSpacing; 61 | [self announceChange]; 62 | } 63 | 64 | - (void)announceChange { 65 | [[NSNotificationCenter defaultCenter] postNotificationName:DLWMMenuLayoutChangedNotification object:self]; 66 | } 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /DLWidgetMenu/Classes/DLWMMenu.h: -------------------------------------------------------------------------------- 1 | // 2 | // DLWMMenu.h 3 | // DLWidgetMenu 4 | // 5 | // Created by Vincent Esche on 05/11/13. 6 | // Copyright (c) 2013 Vincent Esche. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "DLWMMenuItem.h" 12 | 13 | extern const CGFloat DLWMFullCircle; 14 | 15 | @class DLWMMenuAnimator; 16 | 17 | @class DLWMMenu; 18 | 19 | @protocol DLWMMenuDataSource 20 | 21 | - (NSUInteger)numberOfObjectsInMenu:(DLWMMenu *)menu; 22 | - (id)objectAtIndex:(NSUInteger)index inMenu:(DLWMMenu *)menu; 23 | 24 | @end 25 | 26 | @protocol DLWMMenuItemSource 27 | 28 | - (UIView *)viewForObject:(id)object atIndex:(NSUInteger)index inMenu:(DLWMMenu *)menu; 29 | 30 | @end 31 | 32 | @protocol DLWMMenuItemDelegate 33 | 34 | @optional 35 | 36 | - (void)willOpenItem:(DLWMMenuItem *)item inMenu:(DLWMMenu *)menu withDuration:(NSTimeInterval)duration; 37 | - (void)willCloseItem:(DLWMMenuItem *)item inMenu:(DLWMMenu *)menu withDuration:(NSTimeInterval)duration; 38 | 39 | - (void)didOpenItem:(DLWMMenuItem *)item inMenu:(DLWMMenu *)menu withDuration:(NSTimeInterval)duration; 40 | - (void)didCloseItem:(DLWMMenuItem *)item inMenu:(DLWMMenu *)menu withDuration:(NSTimeInterval)duration; 41 | 42 | @end 43 | 44 | @protocol DLWMMenuDelegate 45 | 46 | @optional 47 | 48 | - (void)receivedSingleTap:(UITapGestureRecognizer *)recognizer onItem:(DLWMMenuItem *)item inMenu:(DLWMMenu *)menu; 49 | - (void)receivedDoubleTap:(UITapGestureRecognizer *)recognizer onItem:(DLWMMenuItem *)item inMenu:(DLWMMenu *)menu; 50 | - (void)receivedLongPress:(UILongPressGestureRecognizer *)recognizer onItem:(DLWMMenuItem *)item inMenu:(DLWMMenu *)menu; 51 | - (void)receivedPinch:(UIPinchGestureRecognizer *)recognizer onItem:(DLWMMenuItem *)item inMenu:(DLWMMenu *)menu; 52 | - (void)receivedPan:(UIPanGestureRecognizer *)recognizer onItem:(DLWMMenuItem *)item inMenu:(DLWMMenu *)menu; 53 | 54 | - (void)receivedSingleTap:(UITapGestureRecognizer *)recognizer outsideOfMenu:(DLWMMenu *)menu; 55 | 56 | - (void)willOpenMenu:(DLWMMenu *)menu withDuration:(NSTimeInterval)duration; 57 | - (void)didOpenMenu:(DLWMMenu *)menu; 58 | 59 | - (void)willCloseMenu:(DLWMMenu *)menu withDuration:(NSTimeInterval)duration; 60 | - (void)didCloseMenu:(DLWMMenu *)menu; 61 | 62 | @end 63 | 64 | extern NSString * const DLWMMenuLayoutChangedNotification; 65 | 66 | @protocol DLWMMenuLayout 67 | 68 | - (void)layoutItems:(NSArray *)items forCenterPoint:(CGPoint)centerPoint inMenu:(DLWMMenu *)menu; 69 | 70 | @end 71 | 72 | typedef void(^DLWMMenuAnimatorAnimationsBlock)(DLWMMenuItem *item, NSUInteger index, DLWMMenu *menu); 73 | typedef void(^DLWMMenuAnimatorCompletionBlock)(DLWMMenuItem *item, NSUInteger index, DLWMMenu *menu, BOOL finished); 74 | 75 | @protocol DLWMMenuAnimator 76 | 77 | - (void)animateItem:(DLWMMenuItem *)item atIndex:(NSUInteger)index inMenu:(DLWMMenu *)menu animated:(BOOL)animated completion:(DLWMMenuAnimatorCompletionBlock)completion; 78 | 79 | @end 80 | 81 | typedef NS_OPTIONS(NSUInteger, DLWMMenuState) { 82 | DLWMMenuStateClosed = (0x0 << 0), 83 | DLWMMenuStateClosing = (0x1 << 0), 84 | DLWMMenuStateOpening = (0x1 << 1), 85 | DLWMMenuStateOpened = (0x1 << 2) 86 | }; 87 | 88 | @interface DLWMMenu : UIView 89 | 90 | @property (readonly, assign, nonatomic) CGPoint centerPointWhileOpen; 91 | 92 | @property (readonly, strong, nonatomic) DLWMMenuItem *mainItem; 93 | @property (readonly, strong, nonatomic) NSArray *items; 94 | 95 | @property (readwrite, weak, nonatomic) id dataSource; 96 | @property (readwrite, weak, nonatomic) id itemSource; 97 | @property (readwrite, weak, nonatomic) id delegate; 98 | @property (readwrite, weak, nonatomic) id itemDelegate; 99 | 100 | @property (readwrite, strong, nonatomic) id layout; 101 | 102 | @property (readwrite, strong, nonatomic) id openAnimator; 103 | @property (readwrite, strong, nonatomic) id closeAnimator; 104 | 105 | @property (readwrite, assign, nonatomic) DLWMMenuState state; 106 | 107 | @property (readwrite, assign, nonatomic) BOOL enabled; 108 | @property (readwrite, assign, nonatomic) BOOL debuggingEnabled; 109 | 110 | @property (readwrite, strong, nonatomic) id representedObject; 111 | 112 | @property (readwrite, assign, nonatomic) NSTimeInterval openAnimationDelayBetweenItems; 113 | @property (readwrite, assign, nonatomic) NSTimeInterval closeAnimationDelayBetweenItems; 114 | 115 | - (id)initWithMainItemView:(UIView *)mainItemView 116 | dataSource:(id)dataSource 117 | itemSource:(id)itemSource 118 | delegate:(id)delegate 119 | itemDelegate:(id)itemDelegate 120 | layout:(id)layout 121 | representedObject:(id)representedObject; 122 | 123 | - (void)reloadData; 124 | 125 | - (void)setEnabled:(BOOL)enabled animated:(BOOL)animated; 126 | 127 | - (BOOL)isClosed; 128 | - (BOOL)isClosing; 129 | - (BOOL)isClosedOrClosing; 130 | 131 | - (BOOL)isOpened; 132 | - (BOOL)isOpening; 133 | - (BOOL)isOpenedOrOpening; 134 | 135 | - (BOOL)isAnimating; 136 | 137 | - (void)open; 138 | - (void)openAnimated:(BOOL)animated; 139 | 140 | - (void)close; 141 | - (void)closeAnimated:(BOOL)animated; 142 | 143 | - (void)closeWithSpecialAnimator:(DLWMMenuAnimator *)itemAnimator forItem:(DLWMMenuItem *)item; 144 | - (void)closeWithSpecialAnimator:(DLWMMenuAnimator *)itemAnimator forItem:(DLWMMenuItem *)item animated:(BOOL)animated; 145 | 146 | - (void)moveTo:(CGPoint)centerPoint; 147 | - (void)moveTo:(CGPoint)centerPoint animated:(BOOL)animated; 148 | 149 | - (NSUInteger)indexOfItem:(DLWMMenuItem *)item; 150 | 151 | @end 152 | -------------------------------------------------------------------------------- /DLWidgetMenu/Classes/DLWMMenu.m: -------------------------------------------------------------------------------- 1 | // 2 | // DLWMMenu.m 3 | // DLWidgetMenu 4 | // 5 | // Created by Vincent Esche on 05/11/13. 6 | // Copyright (c) 2013 Vincent Esche. All rights reserved. 7 | // 8 | 9 | #import "DLWMMenu.h" 10 | 11 | #import "DLWMMenuItem.h" 12 | #import "DLWMMenuAnimator.h" 13 | #import "DLWMSpringMenuAnimator.h" 14 | 15 | const CGFloat DLWMFullCircle = M_PI * 2; 16 | 17 | NSString * const DLWMMenuLayoutChangedNotification = @"DLWMMenuLayoutChangedNotification"; 18 | 19 | @interface DLWMMenu () 20 | 21 | @property (readwrite, assign, nonatomic) CGPoint centerPointWhileOpen; 22 | 23 | @property (readwrite, strong, nonatomic) DLWMMenuItem *mainItem; 24 | @property (readwrite, strong, nonatomic) NSArray *items; 25 | 26 | @property (readwrite, strong, nonatomic) NSTimer *timer; 27 | 28 | @end 29 | 30 | @implementation DLWMMenu 31 | 32 | - (id)initWithFrame:(CGRect)frame { 33 | self = [super initWithFrame:frame]; 34 | if (self) { 35 | if (![self commonInit_DLWMMenu]) { 36 | return nil; 37 | } 38 | } 39 | return self; 40 | } 41 | 42 | - (id)initWithCoder:(NSCoder *)decoder { 43 | self = [super initWithCoder:decoder]; 44 | if (self) { 45 | if (![self commonInit_DLWMMenu]) { 46 | return nil; 47 | } 48 | } 49 | return self; 50 | } 51 | 52 | - (BOOL)commonInit_DLWMMenu { 53 | self.items = [NSMutableArray array]; 54 | 55 | self.enabled = YES; 56 | 57 | DLWMMenuAnimator * openAnimator = nil; 58 | if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1) { 59 | openAnimator = [DLWMMenuAnimator new]; 60 | } else { 61 | openAnimator = [DLWMSpringMenuAnimator new]; 62 | } 63 | 64 | self.openAnimator = openAnimator; 65 | self.closeAnimator = [[DLWMMenuAnimator alloc] init]; 66 | self.openAnimationDelayBetweenItems = 0.025; 67 | self.closeAnimationDelayBetweenItems = 0.025; 68 | 69 | UITapGestureRecognizer *singleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(receivedSingleTapOutside:)]; 70 | [self addGestureRecognizer:singleTapRecognizer]; 71 | 72 | return YES; 73 | } 74 | 75 | - (id)initWithMainItemView:(UIView *)mainItemView 76 | dataSource:(id)dataSource 77 | itemSource:(id)itemSource 78 | delegate:(id)delegate 79 | itemDelegate:(id)itemDelegate 80 | layout:(id)layout 81 | representedObject:(id)representedObject { 82 | self = [self initWithFrame:mainItemView.frame]; 83 | if (self) { 84 | NSAssert(dataSource, @"Method argument 'dataSource' must not be nil."); 85 | NSAssert(itemSource, @"Method argument 'itemSource' must not be nil."); 86 | NSAssert(layout, @"Method argument 'layout' must not be nil."); 87 | self.state = DLWMMenuStateClosed; 88 | self.representedObject = representedObject; 89 | self.centerPointWhileOpen = mainItemView.center; 90 | self.mainItem = [[DLWMMenuItem alloc] initWithContentView:mainItemView representedObject:representedObject]; 91 | self.dataSource = dataSource; 92 | self.itemSource = itemSource; 93 | self.delegate = delegate; 94 | self.itemDelegate = itemDelegate; 95 | self.layout = layout; 96 | 97 | [self reloadData]; 98 | [self adjustGeometryForState:DLWMMenuStateClosed]; 99 | } 100 | return self; 101 | } 102 | 103 | - (void)adjustGeometryForState:(DLWMMenuState)state { 104 | CGPoint itemLocation; 105 | if (state == DLWMMenuStateClosed) { 106 | CGRect itemBounds = self.mainItem.bounds; 107 | itemLocation = CGPointMake(CGRectGetMidX(itemBounds), CGRectGetMidY(itemBounds)); 108 | CGPoint menuCenter = self.mainItem.center; 109 | self.bounds = itemBounds; 110 | self.center = menuCenter; 111 | } else { 112 | CGRect menuFrame = self.superview.bounds; 113 | itemLocation = self.center; 114 | self.frame = menuFrame; 115 | } 116 | self.mainItem.center = itemLocation; 117 | self.mainItem.layoutLocation = itemLocation; 118 | self.centerPointWhileOpen = itemLocation; 119 | } 120 | 121 | #pragma mark - Custom Accessors 122 | 123 | - (void)setMainItem:(DLWMMenuItem *)mainItem { 124 | NSAssert(mainItem, @"Method argument 'mainItem' must not be nil."); 125 | if (_mainItem) { 126 | [_mainItem removeFromSuperview]; 127 | [self removeGestureRecognizersFromMenuItem:_mainItem]; 128 | } 129 | _mainItem = mainItem; 130 | [self addGestureRecognizersToMenuItem:mainItem]; 131 | mainItem.userInteractionEnabled = YES; 132 | mainItem.center = self.centerPointWhileOpen; 133 | [self addSubview:mainItem]; 134 | } 135 | 136 | - (void)setDataSource:(id)dataSource { 137 | NSAssert(dataSource, @"Method argument 'dataSource' must not be nil."); 138 | _dataSource = dataSource; 139 | [self reloadData]; 140 | } 141 | 142 | - (void)setItemSource:(id)itemSource { 143 | NSAssert(itemSource, @"Method argument 'itemSource' must not be nil."); 144 | _itemSource = itemSource; 145 | [self reloadData]; 146 | } 147 | 148 | - (void)setLayout:(id)layout { 149 | NSAssert(layout, @"Method argument 'layout' must not be nil."); 150 | if (_layout) { 151 | [[NSNotificationCenter defaultCenter] removeObserver:self name:DLWMMenuLayoutChangedNotification object:_layout]; 152 | } 153 | _layout = layout; 154 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(layoutDidChange:) name:DLWMMenuLayoutChangedNotification object:_layout]; 155 | [UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ 156 | [self layoutItemsWithCenter:self.centerPointWhileOpen animated:YES]; 157 | } completion:nil]; 158 | } 159 | 160 | - (void)setEnabled:(BOOL)enabled { 161 | [self setEnabled:enabled animated:YES]; 162 | } 163 | 164 | - (void)setEnabled:(BOOL)enabled animated:(BOOL)animated { 165 | BOOL oldEnabled = _enabled; 166 | [self willChangeValueForKey:NSStringFromSelector(@selector(enabled))]; 167 | _enabled = enabled; 168 | [self didChangeValueForKey:NSStringFromSelector(@selector(enabled))]; 169 | 170 | if (enabled != oldEnabled) { 171 | NSTimeInterval duration = (animated) ? 0.5 : 0.0; 172 | [UIView animateWithDuration:duration animations:^{ 173 | self.alpha = (enabled) ? 1.0 : 0.33; 174 | }]; 175 | } 176 | } 177 | 178 | - (void)setDebuggingEnabled:(BOOL)debuggingEnabled { 179 | _debuggingEnabled = debuggingEnabled; 180 | self.backgroundColor = (debuggingEnabled) ? [[UIColor redColor] colorWithAlphaComponent:0.5] : nil; 181 | } 182 | 183 | #pragma mark - Observing 184 | 185 | - (void)layoutDidChange:(NSNotification *)notification { 186 | [self layoutItemsWithCenter:self.centerPointWhileOpen animated:YES]; 187 | } 188 | 189 | #pragma mark - Reloading 190 | 191 | - (void)reloadData { 192 | id dataSource = self.dataSource; 193 | id itemSource = self.itemSource; 194 | NSUInteger itemCount = [dataSource numberOfObjectsInMenu:self]; 195 | NSUInteger currentItemCount = self.items.count; 196 | NSUInteger minCount = MIN(itemCount, currentItemCount); 197 | 198 | // Remove all items not needed any more: 199 | if (itemCount < currentItemCount) { 200 | for (NSUInteger i = 0; i < currentItemCount - itemCount; i++) { 201 | [self removeLastItem]; 202 | } 203 | } 204 | 205 | // Update existing items: 206 | NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, minCount)]; 207 | [self.items enumerateObjectsAtIndexes:indexes options:0 usingBlock:^(DLWMMenuItem *item, NSUInteger index, BOOL *stop) { 208 | id object = [dataSource objectAtIndex:index inMenu:self]; 209 | UIView *contentView = [itemSource viewForObject:object atIndex:index inMenu:self]; 210 | item.contentView = contentView; 211 | item.representedObject = object; 212 | }]; 213 | 214 | // Add all additional items: 215 | if (itemCount > currentItemCount) { 216 | for (NSUInteger i = currentItemCount; i < itemCount; i++) { 217 | id object = [dataSource objectAtIndex:i inMenu:self]; 218 | UIView *contentView = [itemSource viewForObject:object atIndex:i inMenu:self]; 219 | DLWMMenuItem *item = [[DLWMMenuItem alloc] initWithContentView:contentView representedObject:object]; 220 | item.layoutLocation = [self isClosed] ? self.center : self.centerPointWhileOpen; 221 | [self addItem:item]; 222 | } 223 | } 224 | [self layoutItemsWithCenter:self.centerPointWhileOpen animated:YES]; 225 | } 226 | 227 | #pragma mark - Opening/Closing 228 | 229 | - (void)open { 230 | [self openAnimated:YES]; 231 | } 232 | 233 | - (void)openAnimated:(BOOL)animated { 234 | if ([self isOpenedOrOpening]) { 235 | return; 236 | } 237 | [self.timer invalidate]; 238 | self.timer = nil; 239 | 240 | self.state = DLWMMenuStateOpening; 241 | [self adjustGeometryForState:self.state]; 242 | 243 | NSArray *items = self.items; 244 | DLWMMenuAnimator *animator = self.openAnimator ?: [DLWMMenuAnimator sharedInstantAnimator]; 245 | NSTimeInterval openAnimationDelayBetweenItems = self.openAnimationDelayBetweenItems; 246 | NSTimeInterval totalDuration = (items.count - 1) * openAnimationDelayBetweenItems + animator.duration; 247 | if ([self.delegate respondsToSelector:@selector(willOpenMenu:withDuration:)]) { 248 | [self.delegate willOpenMenu:self withDuration:totalDuration]; 249 | } 250 | [self.layout layoutItems:items forCenterPoint:self.centerPointWhileOpen inMenu:self]; 251 | [items enumerateObjectsUsingBlock:^(DLWMMenuItem *item, NSUInteger index, BOOL *stop) { 252 | double delayInSeconds = openAnimationDelayBetweenItems * index; 253 | dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); 254 | dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ 255 | if ([self.itemDelegate respondsToSelector:@selector(willOpenItem:inMenu:withDuration:)]) { 256 | [self.itemDelegate willOpenItem:item inMenu:self withDuration:animator.duration]; 257 | } 258 | [animator animateItem:item atIndex:index inMenu:self animated:animated completion:^(DLWMMenuItem *menuItem, NSUInteger itemIndex, DLWMMenu *menu, BOOL finished) { 259 | if ([self.itemDelegate respondsToSelector:@selector(didOpenItem:inMenu:withDuration:)]) { 260 | [self.itemDelegate didOpenItem:menuItem inMenu:self withDuration:animator.duration]; 261 | } 262 | }]; 263 | }); 264 | }]; 265 | self.timer = [NSTimer scheduledTimerWithTimeInterval:totalDuration 266 | target:self 267 | selector:@selector(handleDidOpenMenu:) 268 | userInfo:nil 269 | repeats:NO]; 270 | } 271 | 272 | - (void)handleDidOpenMenu:(NSTimer *)timer { 273 | self.timer = nil; 274 | self.state = DLWMMenuStateOpened; 275 | if ([self.delegate respondsToSelector:@selector(didOpenMenu:)]) { 276 | [self.delegate didOpenMenu:self]; 277 | } 278 | } 279 | 280 | - (void)close { 281 | [self closeAnimated:YES]; 282 | } 283 | 284 | - (void)closeAnimated:(BOOL)animated { 285 | [self closeWithSpecialAnimator:nil forItem:nil animated:animated]; 286 | } 287 | 288 | - (void)closeWithSpecialAnimator:(DLWMMenuAnimator *)itemAnimator forItem:(DLWMMenuItem *)item { 289 | [self closeWithSpecialAnimator:itemAnimator forItem:item animated:YES]; 290 | } 291 | 292 | - (void)closeWithSpecialAnimator:(DLWMMenuAnimator *)specialAnimator forItem:(DLWMMenuItem *)specialItem animated:(BOOL)animated { 293 | if ([self isClosedOrClosing]) { 294 | return; 295 | } 296 | [self.timer invalidate]; 297 | self.timer = nil; 298 | if (specialItem == self.mainItem) { 299 | specialItem = nil; 300 | } 301 | NSArray *items = self.items; 302 | __block DLWMMenuAnimator *animator = self.closeAnimator ?: [DLWMMenuAnimator sharedInstantAnimator]; 303 | NSTimeInterval closeAnimationDelayBetweenItems = self.closeAnimationDelayBetweenItems; 304 | NSTimeInterval totalDuration = (items.count - 1) * closeAnimationDelayBetweenItems + animator.duration; 305 | if ([self.delegate respondsToSelector:@selector(willCloseMenu:withDuration:)]) { 306 | [self.delegate willCloseMenu:self withDuration:totalDuration]; 307 | } 308 | self.state = DLWMMenuStateClosing; 309 | if (specialItem) { 310 | // make sure special items is the first one being animated 311 | items = [@[specialItem] arrayByAddingObjectsFromArray:[self itemsWithoutItem:specialItem]]; 312 | } 313 | [items enumerateObjectsUsingBlock:^(DLWMMenuItem *item, NSUInteger index, BOOL *stop) { 314 | DLWMMenuAnimator *itemAnimator = animator; 315 | if (item == specialItem) { 316 | itemAnimator = specialAnimator ?: [DLWMMenuAnimator sharedInstantAnimator]; 317 | } 318 | double delayInSeconds = closeAnimationDelayBetweenItems * index; 319 | dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); 320 | dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ 321 | if ([self.itemDelegate respondsToSelector:@selector(willCloseItem:inMenu:withDuration:)]) { 322 | [self.itemDelegate willCloseItem:item inMenu:self withDuration:itemAnimator.duration]; 323 | } 324 | [itemAnimator animateItem:item atIndex:index inMenu:self animated:animated completion:^(DLWMMenuItem *menuItem, NSUInteger itemIndex, DLWMMenu *menu, BOOL finished) { 325 | if ([self.itemDelegate respondsToSelector:@selector(didCloseItem:inMenu:withDuration:)]) { 326 | [self.itemDelegate didCloseItem:menuItem inMenu:self withDuration:itemAnimator.duration]; 327 | } 328 | }]; 329 | }); 330 | }]; 331 | self.timer = [NSTimer scheduledTimerWithTimeInterval:totalDuration 332 | target:self 333 | selector:@selector(handleDidCloseMenu:) 334 | userInfo:nil 335 | repeats:NO]; 336 | } 337 | 338 | - (void)handleDidCloseMenu:(NSTimer *)timer { 339 | self.timer = nil; 340 | 341 | self.state = DLWMMenuStateClosed; 342 | [self adjustGeometryForState:self.state]; 343 | 344 | if ([self.delegate respondsToSelector:@selector(didCloseMenu:)]) { 345 | [self.delegate didCloseMenu:self]; 346 | } 347 | } 348 | 349 | - (NSArray *)itemsWithoutItem:(DLWMMenuItem *)item { 350 | NSArray *items = self.items; 351 | if (item) { 352 | items = [items filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(DLWMMenuItem *menuItem, NSDictionary *bindings) { 353 | return menuItem != item; 354 | }]]; 355 | } 356 | return items; 357 | } 358 | 359 | #pragma mark - UIGestureRecognizer Handlers 360 | 361 | - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { 362 | return self.enabled; 363 | } 364 | 365 | - (void)receivedPinch:(UIPinchGestureRecognizer *)recognizer { 366 | if (!self.enabled) { 367 | return; 368 | } 369 | if ([self.delegate respondsToSelector:@selector(receivedPinch:onItem:inMenu:)]) { 370 | [self.delegate receivedPinch:recognizer onItem:(DLWMMenuItem *)recognizer.view inMenu:self]; 371 | } 372 | } 373 | 374 | - (void)receivedPan:(UIPanGestureRecognizer *)recognizer { 375 | if (!self.enabled) { 376 | return; 377 | } 378 | if ([self.delegate respondsToSelector:@selector(receivedPan:onItem:inMenu:)]) { 379 | [self.delegate receivedPan:recognizer onItem:(DLWMMenuItem *)recognizer.view inMenu:self]; 380 | } 381 | } 382 | 383 | - (void)receivedLongPress:(UILongPressGestureRecognizer *)recognizer { 384 | if (!self.enabled) { 385 | return; 386 | } 387 | if ([self.delegate respondsToSelector:@selector(receivedLongPress:onItem:inMenu:)]) { 388 | [self.delegate receivedLongPress:recognizer onItem:(DLWMMenuItem *)recognizer.view inMenu:self]; 389 | } 390 | } 391 | 392 | - (void)receivedDoubleTap:(UITapGestureRecognizer *)recognizer { 393 | if (!self.enabled) { 394 | return; 395 | } 396 | if ([self.delegate respondsToSelector:@selector(receivedDoubleTap:onItem:inMenu:)]) { 397 | [self.delegate receivedDoubleTap:recognizer onItem:(DLWMMenuItem *)recognizer.view inMenu:self]; 398 | } 399 | } 400 | 401 | - (void)receivedSingleTap:(UITapGestureRecognizer *)recognizer { 402 | if (!self.enabled) { 403 | return; 404 | } 405 | if ([self.delegate respondsToSelector:@selector(receivedSingleTap:onItem:inMenu:)]) { 406 | [self.delegate receivedSingleTap:recognizer onItem:(DLWMMenuItem *)recognizer.view inMenu:self]; 407 | } 408 | } 409 | 410 | - (void)receivedSingleTapOutside:(UITapGestureRecognizer *)recognizer { 411 | if (!self.enabled) { 412 | return; 413 | } 414 | if ([self.delegate respondsToSelector:@selector(receivedSingleTap:outsideOfMenu:)]) { 415 | [self.delegate receivedSingleTap:recognizer outsideOfMenu:self]; 416 | } 417 | } 418 | 419 | #pragma mark - States 420 | 421 | - (BOOL)isClosed { 422 | return self.state == DLWMMenuStateClosed; 423 | } 424 | 425 | - (BOOL)isClosing { 426 | return self.state == DLWMMenuStateClosing; 427 | } 428 | 429 | - (BOOL)isClosedOrClosing { 430 | return self.state == DLWMMenuStateClosed || self.state == DLWMMenuStateClosing; 431 | } 432 | 433 | - (BOOL)isOpened { 434 | return self.state == DLWMMenuStateOpened; 435 | } 436 | 437 | - (BOOL)isOpening { 438 | return self.state == DLWMMenuStateOpening; 439 | } 440 | 441 | - (BOOL)isOpenedOrOpening { 442 | return self.state == DLWMMenuStateOpened || self.state == DLWMMenuStateOpening; 443 | } 444 | 445 | - (BOOL)isAnimating { 446 | return self.state == DLWMMenuStateOpening || self.state == DLWMMenuStateClosing; 447 | } 448 | 449 | #pragma mark - Add/Remove Items 450 | 451 | - (void)addGestureRecognizersToMenuItem:(DLWMMenuItem *)menuItem { 452 | NSAssert(menuItem, @"Method argument 'menuItem' must not be nil."); 453 | 454 | UIPinchGestureRecognizer *pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(receivedPinch:)]; 455 | [menuItem addGestureRecognizer:pinchRecognizer]; 456 | 457 | UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(receivedPan:)]; 458 | [menuItem addGestureRecognizer:panRecognizer]; 459 | 460 | UILongPressGestureRecognizer *longPressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(receivedLongPress:)]; 461 | [menuItem addGestureRecognizer:longPressRecognizer]; 462 | 463 | UITapGestureRecognizer *doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(receivedDoubleTap:)]; 464 | [doubleTapRecognizer setNumberOfTapsRequired:2]; 465 | [menuItem addGestureRecognizer:doubleTapRecognizer]; 466 | 467 | UITapGestureRecognizer *singleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(receivedSingleTap:)]; 468 | [singleTapRecognizer requireGestureRecognizerToFail:doubleTapRecognizer]; 469 | [menuItem addGestureRecognizer:singleTapRecognizer]; 470 | } 471 | 472 | - (void)removeGestureRecognizersFromMenuItem:(DLWMMenuItem *)menuItem { 473 | for (UIGestureRecognizer *recognizer in menuItem.gestureRecognizers) { 474 | [menuItem removeGestureRecognizer:recognizer]; 475 | } 476 | } 477 | 478 | - (void)addItem:(DLWMMenuItem *)item { 479 | NSAssert(item, @"Method argument 'menuItem' must not be nil."); 480 | [((NSMutableArray *)self.items) addObject:item]; 481 | [self addGestureRecognizersToMenuItem:item]; 482 | item.userInteractionEnabled = YES; 483 | BOOL hidden = [self isClosed]; 484 | item.hidden = hidden; 485 | if (!hidden) { 486 | item.alpha = 0.0; 487 | [UIView animateWithDuration:0.25 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ 488 | item.alpha = 1.0; 489 | } completion:nil]; 490 | } 491 | item.center = self.centerPointWhileOpen; 492 | [self insertSubview:item belowSubview:self.mainItem]; 493 | } 494 | 495 | - (void)removeItem:(DLWMMenuItem *)item { 496 | NSAssert(item, @"Method argument 'menuItem' must not be nil."); 497 | NSAssert(item.superview == self, @"Method argument 'menuItem' must be member of menu."); 498 | [item removeFromSuperview]; 499 | [self removeGestureRecognizersFromMenuItem:item]; 500 | [((NSMutableArray *)self.items) removeObject:item]; 501 | } 502 | 503 | - (void)removeLastItem { 504 | DLWMMenuItem *item = [self.items lastObject]; 505 | if (item) { 506 | [self removeItem:item]; 507 | } 508 | } 509 | 510 | - (void)moveTo:(CGPoint)centerPoint { 511 | [self moveTo:centerPoint animated:YES]; 512 | } 513 | 514 | - (void)moveTo:(CGPoint)centerPoint animated:(BOOL)animated { 515 | // Moving the items' layoutLocation so that layouts which 516 | // rely on an item's previous location can be supported. 517 | // A potential candidate would be a force-directed layout. 518 | [self layoutItemsWithCenter:centerPoint animated:animated]; 519 | if ([self isClosed]) { 520 | NSTimeInterval duration = (animated) ? 0.5 : 0.0; 521 | [UIView animateWithDuration:duration delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ 522 | self.center = centerPoint; 523 | } completion:nil]; 524 | } else { 525 | self.centerPointWhileOpen = centerPoint; 526 | } 527 | } 528 | 529 | - (void)layoutItemsWithCenter:(CGPoint)centerPoint animated:(BOOL)animated { 530 | NSArray *items = self.items; 531 | if (!items.count) { 532 | return; 533 | } 534 | [self.layout layoutItems:items forCenterPoint:centerPoint inMenu:self]; 535 | if ([self isOpenedOrOpening]) { 536 | NSTimeInterval duration = (animated) ? 0.5 : 0.0; 537 | [UIView animateWithDuration:duration delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ 538 | self.mainItem.center = centerPoint; 539 | [items enumerateObjectsUsingBlock:^(DLWMMenuItem *item, NSUInteger index, BOOL *stop) { 540 | item.center = item.layoutLocation; 541 | }]; 542 | } completion:nil]; 543 | } 544 | } 545 | 546 | - (NSUInteger)indexOfItem:(DLWMMenuItem *)item { 547 | return [self.items indexOfObjectIdenticalTo:item]; 548 | } 549 | 550 | @end 551 | -------------------------------------------------------------------------------- /DLWidgetMenu/Classes/DLWMMenuAnimator.h: -------------------------------------------------------------------------------- 1 | // 2 | // DLWMMenuAnimator.h 3 | // DLWidgetMenu 4 | // 5 | // Created by Vincent Esche on 06/11/13. 6 | // Copyright (c) 2013 Vincent Esche. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "DLWMMenu.h" 12 | 13 | @interface DLWMMenuAnimator : NSObject 14 | 15 | @property (readwrite, assign, nonatomic) NSTimeInterval duration; 16 | @property (readwrite, assign, nonatomic) UIViewAnimationOptions options; 17 | 18 | - (id)init; 19 | - (id)initWithDuration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options; 20 | 21 | + (DLWMMenuAnimator *)sharedInstantAnimator; 22 | 23 | - (void)animateItem:(DLWMMenuItem *)item atIndex:(NSUInteger)index inMenu:(DLWMMenu *)menu animated:(BOOL)animated completion:(DLWMMenuAnimatorCompletionBlock)completion; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /DLWidgetMenu/Classes/DLWMMenuAnimator.m: -------------------------------------------------------------------------------- 1 | // 2 | // DLWMMenuAnimator.m 3 | // DLWidgetMenu 4 | // 5 | // Created by Vincent Esche on 06/11/13. 6 | // Copyright (c) 2013 Vincent Esche. All rights reserved. 7 | // 8 | 9 | #import "DLWMMenuAnimator.h" 10 | 11 | @implementation DLWMMenuAnimator 12 | 13 | - (id)init { 14 | self = [super init]; 15 | if (self) { 16 | self.duration = 0.4; 17 | self.options = UIViewAnimationOptionCurveEaseIn; 18 | } 19 | return self; 20 | } 21 | 22 | - (id)initWithDuration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options { 23 | self = [self init]; 24 | if (self) { 25 | self.duration = duration; 26 | self.options = options; 27 | } 28 | return self; 29 | } 30 | 31 | + (DLWMMenuAnimator *)sharedInstantAnimator { 32 | static DLWMMenuAnimator *sharedAnimator = nil; 33 | static dispatch_once_t onceToken; 34 | dispatch_once(&onceToken, ^{ 35 | if (!sharedAnimator) { 36 | sharedAnimator = [[DLWMMenuAnimator alloc] initWithDuration:0.0 options:UIViewAnimationOptionCurveLinear]; 37 | } 38 | }); 39 | return sharedAnimator; 40 | } 41 | 42 | - (void)willAnimateItem:(DLWMMenuItem *)item atIndex:(NSUInteger)index inMenu:(DLWMMenu *)menu { 43 | if ([menu isOpenedOrOpening]) { 44 | item.hidden = NO; 45 | item.alpha = 0.0; 46 | item.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.5, 0.5); 47 | item.center = menu.centerPointWhileOpen; 48 | } 49 | } 50 | 51 | - (void)animateItem:(DLWMMenuItem *)item atIndex:(NSUInteger)index inMenu:(DLWMMenu *)menu { 52 | item.center = ([menu isOpenedOrOpening]) ? item.layoutLocation : menu.centerPointWhileOpen; 53 | if ([menu isOpenedOrOpening]) { 54 | item.alpha = 1.0; 55 | item.transform = CGAffineTransformIdentity; 56 | } else { 57 | item.alpha = 0.0; 58 | item.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.5, 0.5); 59 | } 60 | } 61 | 62 | - (void)didAnimateItem:(DLWMMenuItem *)item atIndex:(NSUInteger)index inMenu:(DLWMMenu *)menu finished:(BOOL)finished { 63 | if (finished && [menu isClosedOrClosing]) { 64 | item.hidden = YES; 65 | item.transform = CGAffineTransformIdentity; 66 | } 67 | } 68 | 69 | - (void)animateItem:(DLWMMenuItem *)item atIndex:(NSUInteger)index inMenu:(DLWMMenu *)menu animated:(BOOL)animated completion:(DLWMMenuAnimatorCompletionBlock)completion { 70 | [self willAnimateItem:item atIndex:index inMenu:menu]; 71 | [UIView animateWithDuration:self.duration delay:0.0 options:self.options animations:^{ 72 | [self animateItem:item atIndex:index inMenu:menu]; 73 | } completion:^(BOOL finished){ 74 | [self didAnimateItem:item atIndex:index inMenu:menu finished:finished]; 75 | if (completion) { 76 | completion(item, index, menu, finished); 77 | } 78 | }]; 79 | } 80 | 81 | @end 82 | -------------------------------------------------------------------------------- /DLWidgetMenu/Classes/DLWMMenuItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // DLWMMenuItem.h 3 | // DLWidgetMenu 4 | // 5 | // Created by Vincent Esche on 05/11/13. 6 | // Copyright (c) 2013 Vincent Esche. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DLWMMenuItem : UIView 12 | 13 | @property (readwrite, strong, nonatomic) UIView *contentView; 14 | @property (readwrite, strong, nonatomic) id representedObject; 15 | @property (readwrite, assign, nonatomic) CGPoint layoutLocation; 16 | 17 | - (id)initWithContentView:(UIView *)contentView representedObject:(id)representedObject; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /DLWidgetMenu/Classes/DLWMMenuItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // DLWMMenuItem.m 3 | // DLWidgetMenu 4 | // 5 | // Created by Vincent Esche on 05/11/13. 6 | // Copyright (c) 2013 Vincent Esche. All rights reserved. 7 | // 8 | 9 | #import "DLWMMenuItem.h" 10 | 11 | @implementation DLWMMenuItem 12 | 13 | - (id)initWithFrame:(CGRect)frame { 14 | self = [super initWithFrame:frame]; 15 | if (self) { 16 | self.layoutLocation = self.center; 17 | } 18 | return self; 19 | } 20 | 21 | - (id)initWithCoder:(NSCoder *)decoder { 22 | self = [super initWithCoder:decoder]; 23 | if (self) { 24 | self.layoutLocation = self.center; 25 | } 26 | return self; 27 | } 28 | 29 | - (id)initWithContentView:(UIView *)contentView representedObject:(id)representedObject { 30 | self = [self initWithFrame:contentView.frame]; 31 | if (self) { 32 | self.contentView = contentView; 33 | self.representedObject = representedObject; 34 | } 35 | return self; 36 | } 37 | 38 | - (void)setContentView:(UIView *)contentView { 39 | [_contentView removeFromSuperview]; 40 | _contentView = contentView; 41 | self.bounds = contentView.bounds; 42 | [self addSubview:contentView]; 43 | } 44 | 45 | - (void)setLayoutLocation:(CGPoint)layoutLocation { 46 | _layoutLocation = layoutLocation; 47 | } 48 | 49 | - (void)layoutSubviews { 50 | self.contentView.frame = self.bounds; 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /DLWidgetMenu/Classes/DLWMRadialLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // DLWMRadialLayout.h 3 | // DLWidgetMenu 4 | // 5 | // Created by Vincent Esche on 05/11/13. 6 | // Copyright (c) 2013 Vincent Esche. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "DLWMMenu.h" 12 | 13 | @interface DLWMRadialLayout : NSObject 14 | 15 | @property (readwrite, assign, nonatomic) CGFloat radius; 16 | @property (readwrite, assign, nonatomic) CGFloat arc; // negative for counter-clockwise 17 | @property (readwrite, assign, nonatomic) CGFloat angle; 18 | @property (readwrite, assign, nonatomic) CGFloat minDistance; // 0.0 for non-layered 19 | @property (readwrite, assign, nonatomic) BOOL uniformOuterLayer; 20 | 21 | - (id)initWithRadius:(CGFloat)radius arc:(CGFloat)arc angle:(CGFloat)angle minDistance:(CGFloat)minDistance; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /DLWidgetMenu/Classes/DLWMRadialLayout.m: -------------------------------------------------------------------------------- 1 | // 2 | // DLWMRadialLayout.m 3 | // DLWidgetMenu 4 | // 5 | // Created by Vincent Esche on 05/11/13. 6 | // Copyright (c) 2013 Vincent Esche. All rights reserved. 7 | // 8 | 9 | #import "DLWMRadialLayout.h" 10 | 11 | typedef struct { 12 | CGFloat radius; 13 | CGFloat arc; 14 | CGFloat angle; 15 | NSUInteger offset; 16 | NSUInteger itemCount; 17 | } DLWMRadialLayoutLayer; 18 | 19 | DLWMRadialLayoutLayer DLWMRadialLayoutLayerMake(CGFloat radius, CGFloat arc, CGFloat angle, NSUInteger offset, NSUInteger itemCount); 20 | 21 | @interface DLWMRadialLayout () 22 | 23 | @end 24 | 25 | @implementation DLWMRadialLayout 26 | 27 | - (id)initWithRadius:(CGFloat)radius arc:(CGFloat)arc angle:(CGFloat)angle minDistance:(CGFloat)minDistance { 28 | self = [self init]; 29 | if (self) { 30 | self.radius = radius; 31 | self.arc = arc; 32 | self.angle = angle; 33 | self.minDistance = minDistance; 34 | self.uniformOuterLayer = NO; 35 | } 36 | return self; 37 | } 38 | 39 | - (void)layoutItems:(NSArray *)items forCenterPoint:(CGPoint)centerPoint inMenu:(DLWMMenu *)menu { 40 | NSUInteger itemCount = items.count; 41 | 42 | CGFloat radius = self.radius; 43 | CGFloat arc = self.arc; 44 | CGFloat angle = self.angle; 45 | CGFloat minDistance = self.minDistance; 46 | BOOL uniformOuterLayer = self.uniformOuterLayer; 47 | 48 | BOOL isFullCircle = ABS(arc) == DLWMFullCircle; 49 | 50 | CGFloat innerItemDistance = 0.0; 51 | CGFloat previousEndAngle = angle; 52 | 53 | NSUInteger offset = 0; 54 | 55 | NSUInteger layerIndex = 0; 56 | while (offset < itemCount) { 57 | DLWMRadialLayoutLayer layer = DLWMRadialLayoutLayerMake(radius, arc, angle, offset, itemCount - offset); 58 | 59 | NSUInteger maxItemCount = layer.itemCount; 60 | 61 | if (isFullCircle && !uniformOuterLayer) { 62 | layer.angle = previousEndAngle; 63 | } 64 | 65 | if (layerIndex > 0) { 66 | layer.radius = radius + (layerIndex * MIN(minDistance * 1.0, innerItemDistance * 1.1)); 67 | } 68 | 69 | BOOL isOuterLayer; 70 | 71 | if (minDistance) { 72 | maxItemCount = round((layer.radius * ABS(layer.arc)) / minDistance); 73 | if (layer.arc == DLWMFullCircle) { 74 | maxItemCount--; 75 | } 76 | layer.itemCount = MIN(layer.itemCount, maxItemCount); 77 | } 78 | 79 | if (layerIndex == 0) { 80 | innerItemDistance = layer.radius * (ABS(layer.arc) / layer.itemCount); 81 | } 82 | 83 | isOuterLayer = (layer.itemCount == (itemCount - offset)); 84 | 85 | if (isOuterLayer && layerIndex != 0 && !uniformOuterLayer) { 86 | layer.arc = (layer.arc / maxItemCount) * (layer.itemCount - 1); 87 | } 88 | 89 | layer.arc = MIN(ABS(layer.arc), [[self class] arcForFullCircleWithItemCount:layer.itemCount]); 90 | layer.arc = copysign(layer.arc, arc); 91 | 92 | layer.arc = MIN(ABS(layer.arc), [[self class] arcForFullCircleWithItemCount:layer.itemCount]); 93 | layer.arc = copysign(layer.arc, arc); 94 | 95 | previousEndAngle = layer.angle + layer.arc; 96 | 97 | NSArray *layerItems = [items subarrayWithRange:NSMakeRange(offset, layer.itemCount)]; 98 | [self layoutItems:layerItems inArc:layer.arc fromAngle:layer.angle withRadius:layer.radius aroundCenterPoint:centerPoint inMenu:menu]; 99 | 100 | offset += layer.itemCount; 101 | layerIndex++; 102 | } 103 | } 104 | 105 | - (void)layoutItems:(NSArray *)items inArc:(CGFloat)arc fromAngle:(CGFloat)angle withRadius:(CGFloat)radius aroundCenterPoint:(CGPoint)centerPoint inMenu:(DLWMMenu *)menu { 106 | NSUInteger itemCount = items.count; 107 | [items enumerateObjectsUsingBlock:^(DLWMMenuItem *item, NSUInteger index, BOOL *stop) { 108 | CGFloat itemAngle = [[self class] angleForItemAtIndex:index ofCount:itemCount inArc:arc startintAtAngle:angle]; 109 | CGPoint itemCenter = CGPointMake(centerPoint.x + cosf(itemAngle) * radius, 110 | centerPoint.y + sinf(itemAngle) * radius); 111 | item.layoutLocation = itemCenter; 112 | }]; 113 | } 114 | 115 | + (CGFloat)arcForFullCircleWithItemCount:(NSUInteger)itemCount { 116 | return (DLWMFullCircle / MAX(1, itemCount)) * (itemCount - 1); 117 | } 118 | 119 | + (CGFloat)angleForItemAtIndex:(NSUInteger)itemIndex ofCount:(NSUInteger)itemCount inArc:(CGFloat)arc startintAtAngle:(CGFloat)angle { 120 | return angle + (arc / MAX(1, itemCount - 1)) * itemIndex; 121 | } 122 | 123 | - (void)setRadius:(CGFloat)radius { 124 | _radius = radius; 125 | [self announceChange]; 126 | } 127 | 128 | - (void)setArc:(CGFloat)arc { 129 | _arc = copysign(MIN(ABS(arc), DLWMFullCircle), arc); 130 | [self announceChange]; 131 | } 132 | 133 | - (void)setAngle:(CGFloat)angle { 134 | _angle = fmod(angle, DLWMFullCircle); 135 | [self announceChange]; 136 | } 137 | 138 | - (void)setMinDistance:(CGFloat)minDistance { 139 | _minDistance = minDistance; 140 | [self announceChange]; 141 | } 142 | 143 | - (void)setUniformOuterLayer:(BOOL)uniformOuterLayer { 144 | _uniformOuterLayer = uniformOuterLayer; 145 | [self announceChange]; 146 | } 147 | 148 | - (void)announceChange { 149 | [[NSNotificationCenter defaultCenter] postNotificationName:DLWMMenuLayoutChangedNotification object:self]; 150 | } 151 | 152 | @end 153 | 154 | DLWMRadialLayoutLayer DLWMRadialLayoutLayerMake(CGFloat radius, CGFloat arc, CGFloat angle, NSUInteger offset, NSUInteger itemCount) { 155 | return (DLWMRadialLayoutLayer){radius, arc, angle, offset, itemCount}; 156 | } 157 | -------------------------------------------------------------------------------- /DLWidgetMenu/Classes/DLWMSelectionMenuAnimator.h: -------------------------------------------------------------------------------- 1 | // 2 | // DLWMSelectionMenuAnimator.h 3 | // DLWidgetMenu 4 | // 5 | // Created by Vincent Esche on 05/11/13. 6 | // Copyright (c) 2013 Vincent Esche. All rights reserved. 7 | // 8 | 9 | #import "DLWMMenuAnimator.h" 10 | 11 | @interface DLWMSelectionMenuAnimator : DLWMMenuAnimator 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /DLWidgetMenu/Classes/DLWMSelectionMenuAnimator.m: -------------------------------------------------------------------------------- 1 | // 2 | // DLWMSelectionMenuAnimator.m 3 | // DLWidgetMenu 4 | // 5 | // Created by Vincent Esche on 05/11/13. 6 | // Copyright (c) 2013 Vincent Esche. All rights reserved. 7 | // 8 | 9 | #import "DLWMSelectionMenuAnimator.h" 10 | 11 | @interface DLWMMenuAnimator () 12 | 13 | - (void)willAnimateItem:(DLWMMenuItem *)item atIndex:(NSUInteger)index inMenu:(DLWMMenu *)menu; 14 | - (void)animateItem:(DLWMMenuItem *)item atIndex:(NSUInteger)index inMenu:(DLWMMenu *)menu; 15 | - (void)didAnimateItem:(DLWMMenuItem *)item atIndex:(NSUInteger)index inMenu:(DLWMMenu *)menu finished:(BOOL)finished; 16 | 17 | @end 18 | 19 | @implementation DLWMSelectionMenuAnimator 20 | 21 | - (void)didAnimateItem:(DLWMMenuItem *)item atIndex:(NSUInteger)index inMenu:(DLWMMenu *)menu finished:(BOOL)finished { 22 | [super didAnimateItem:item atIndex:index inMenu:menu finished:finished]; 23 | if (finished) { 24 | item.alpha = 1.0; 25 | item.transform = CGAffineTransformIdentity; 26 | } 27 | } 28 | 29 | - (void)animateItem:(DLWMMenuItem *)item atIndex:(NSUInteger)index inMenu:(DLWMMenu *)menu { 30 | item.alpha = 0.0; 31 | item.transform = CGAffineTransformConcat(item.transform, CGAffineTransformMakeScale(2.0, 2.0)); 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /DLWidgetMenu/Classes/DLWMSpiralLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // DLWMSpiralLayout.h 3 | // DLWidgetMenu 4 | // 5 | // Created by Vincent Esche on 05/11/13. 6 | // Copyright (c) 2013 Vincent Esche. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "DLWMMenu.h" 12 | 13 | typedef CGFloat(^DLWMLayoutRadiusLogic)(DLWMMenu *menu, NSArray *items, CGFloat arc); 14 | 15 | @interface DLWMSpiralLayout : NSObject 16 | 17 | @property (readwrite, assign, nonatomic) CGFloat angle; 18 | @property (readwrite, assign, nonatomic) CGFloat radius; 19 | @property (readwrite, assign, nonatomic) CGFloat itemDistance; 20 | @property (readwrite, assign, nonatomic, getter = isClockwise) BOOL clockwise; 21 | 22 | - (id)initWithAngle:(CGFloat)angle radius:(CGFloat)radius itemDistance:(CGFloat)itemDistance; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /DLWidgetMenu/Classes/DLWMSpiralLayout.m: -------------------------------------------------------------------------------- 1 | // 2 | // DLWMSpiralLayout.m 3 | // DLWidgetMenu 4 | // 5 | // Created by Vincent Esche on 05/11/13. 6 | // Copyright (c) 2013 Vincent Esche. All rights reserved. 7 | // 8 | 9 | #import "DLWMSpiralLayout.h" 10 | 11 | #import "DLWMMenuItem.h" 12 | 13 | @interface DLWMSpiralLayout () 14 | 15 | @property (readwrite, assign, nonatomic) CGFloat arc; 16 | 17 | @end 18 | 19 | @implementation DLWMSpiralLayout 20 | 21 | - (id)init { 22 | return [self initWithAngle:[[self class] defaultAngle] radius:[[self class] defaultRadius] itemDistance:[[self class] defaultItemDistance]]; 23 | } 24 | 25 | - (id)initWithAngle:(CGFloat)angle radius:(CGFloat)radius itemDistance:(CGFloat)itemDistance { 26 | self = [super init]; 27 | if (self) { 28 | self.radius = radius; 29 | self.angle = angle; 30 | self.clockwise = YES; 31 | self.itemDistance = itemDistance; 32 | } 33 | return self; 34 | } 35 | 36 | + (CGFloat)defaultAngle { 37 | return - M_PI_2; 38 | } 39 | 40 | + (CGFloat)defaultRadius { 41 | return 30.0; 42 | } 43 | 44 | + (CGFloat)defaultItemDistance { 45 | return 20.0; 46 | } 47 | 48 | - (void)layoutItems:(NSArray *)items forCenterPoint:(CGPoint)centerPoint inMenu:(DLWMMenu *)menu { 49 | BOOL clockwise = self.clockwise; 50 | CGFloat itemDistance = self.itemDistance; 51 | 52 | CGFloat scale = itemDistance; 53 | 54 | CGFloat angle = self.angle; 55 | CGFloat radius = self.radius / scale; 56 | 57 | CGFloat angleScale = 2.0; 58 | CGFloat radiusScale = 1.5; 59 | 60 | __block CGFloat angleSum = 0.0; 61 | __block CGFloat radiusSum = 0.0; 62 | 63 | NSUInteger indexOffset = 0; 64 | while (radiusSum < radius) { 65 | radiusSum += sqrt(indexOffset + 2) - sqrt(indexOffset + 1); 66 | indexOffset++; 67 | } 68 | 69 | __block CGFloat squareRootOfCurrentIndex = sqrt(indexOffset + 1); 70 | __block CGFloat squareRootOfNextIndex = sqrt(indexOffset + 2); 71 | 72 | [items enumerateObjectsUsingBlock:^(DLWMMenuItem *item, NSUInteger index, BOOL *stop) { 73 | if (index) { 74 | CGFloat angleDelta = atan(1.0 / squareRootOfCurrentIndex); 75 | angleSum += (clockwise) ? angleDelta : -angleDelta; 76 | 77 | CGFloat radiusDelta = squareRootOfNextIndex - squareRootOfCurrentIndex; 78 | radiusSum += radiusDelta; 79 | 80 | squareRootOfCurrentIndex = squareRootOfNextIndex; 81 | squareRootOfNextIndex = sqrt(indexOffset + index + 2.0); 82 | } 83 | CGFloat itemAngle = ((clockwise) ? angleSum : -angleSum); 84 | CGFloat itemRadius = radiusSum; 85 | itemAngle *= angleScale; 86 | itemRadius *= radiusScale; 87 | CGPoint unitItemCenter = CGPointMake(cos(angle + itemAngle) * itemRadius, 88 | sin(angle + itemAngle) * itemRadius); 89 | CGPoint itemCenter = CGPointApplyAffineTransform(unitItemCenter, CGAffineTransformMakeScale(scale, scale)); 90 | itemCenter = CGPointApplyAffineTransform(itemCenter, CGAffineTransformMakeTranslation(centerPoint.x, centerPoint.y)); 91 | item.layoutLocation = itemCenter; 92 | }]; 93 | } 94 | 95 | - (void)setRadius:(CGFloat)radius { 96 | _radius = radius; 97 | [self announceChange]; 98 | } 99 | 100 | - (void)setItemDistance:(CGFloat)itemDistance { 101 | _itemDistance = itemDistance; 102 | [self announceChange]; 103 | } 104 | 105 | - (void)setAngle:(CGFloat)angle { 106 | _angle = fmod(angle, DLWMFullCircle); 107 | [self announceChange]; 108 | } 109 | 110 | - (void)setClockwise:(BOOL)clockwise { 111 | _clockwise = clockwise; 112 | [self announceChange]; 113 | } 114 | 115 | - (void)announceChange { 116 | [[NSNotificationCenter defaultCenter] postNotificationName:DLWMMenuLayoutChangedNotification object:self]; 117 | } 118 | 119 | @end 120 | -------------------------------------------------------------------------------- /DLWidgetMenu/Classes/DLWMSpringMenuAnimator.h: -------------------------------------------------------------------------------- 1 | // 2 | // DLWMSpringMenuAnimator.h 3 | // DLWidgetMenu 4 | // 5 | // Created by Vincent Esche on 05/11/13. 6 | // Copyright (c) 2013 Vincent Esche. All rights reserved. 7 | // 8 | 9 | #import "DLWMMenuAnimator.h" 10 | 11 | @interface DLWMSpringMenuAnimator : DLWMMenuAnimator 12 | 13 | @property (readwrite, assign, nonatomic) CGFloat damping; 14 | @property (readwrite, assign, nonatomic) CGFloat velocity; 15 | 16 | - (id)initWithDamping:(CGFloat)damping velocity:(CGFloat)velocity; 17 | - (id)initWithDuration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options damping:(CGFloat)damping velocity:(CGFloat)velocity; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /DLWidgetMenu/Classes/DLWMSpringMenuAnimator.m: -------------------------------------------------------------------------------- 1 | // 2 | // DLWMSpringMenuAnimator.m 3 | // DLWidgetMenu 4 | // 5 | // Created by Vincent Esche on 05/11/13. 6 | // Copyright (c) 2013 Vincent Esche. All rights reserved. 7 | // 8 | 9 | #import "DLWMSpringMenuAnimator.h" 10 | 11 | @interface DLWMMenuAnimator () 12 | 13 | - (void)willAnimateItem:(DLWMMenuItem *)item atIndex:(NSUInteger)index inMenu:(DLWMMenu *)menu; 14 | - (void)animateItem:(DLWMMenuItem *)item atIndex:(NSUInteger)index inMenu:(DLWMMenu *)menu; 15 | - (void)didAnimateItem:(DLWMMenuItem *)item atIndex:(NSUInteger)index inMenu:(DLWMMenu *)menu finished:(BOOL)finished; 16 | 17 | @end 18 | 19 | @implementation DLWMSpringMenuAnimator 20 | 21 | - (id)init { 22 | self = [super init]; 23 | if (self) { 24 | self.duration = 0.5; 25 | self.damping = 0.45; 26 | self.velocity = 7.5; 27 | } 28 | return self; 29 | } 30 | 31 | - (id)initWithDamping:(CGFloat)damping velocity:(CGFloat)velocity { 32 | self = [self init]; 33 | if (self) { 34 | self.damping = damping; 35 | self.velocity = velocity; 36 | } 37 | return self; 38 | } 39 | 40 | - (id)initWithDuration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options damping:(CGFloat)damping velocity:(CGFloat)velocity { 41 | self = [super initWithDuration:duration options:options]; 42 | if (self) { 43 | self.damping = damping; 44 | self.velocity = velocity; 45 | } 46 | return self; 47 | } 48 | 49 | - (void)animateItem:(DLWMMenuItem *)item atIndex:(NSUInteger)index inMenu:(DLWMMenu *)menu animated:(BOOL)animated completion:(DLWMMenuAnimatorCompletionBlock)completion { 50 | [self willAnimateItem:item atIndex:index inMenu:menu]; 51 | [UIView animateWithDuration:self.duration 52 | delay:0.0 53 | usingSpringWithDamping:self.damping 54 | initialSpringVelocity:self.velocity 55 | options:self.options 56 | animations:^{ 57 | [self animateItem:item atIndex:index inMenu:menu]; 58 | } 59 | completion:^(BOOL finished){ 60 | [self didAnimateItem:item atIndex:index inMenu:menu finished:finished]; 61 | if (completion) { 62 | completion(item, index, menu, finished); 63 | } 64 | }]; 65 | } 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /DLWidgetMenu/DLWMAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // DLWMAppDelegate.h 3 | // DLWidgetMenu 4 | // 5 | // Created by Vincent Esche on 05/11/13. 6 | // Copyright (c) 2013 Vincent Esche. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DLWMAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /DLWidgetMenu/DLWMAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // DLWMAppDelegate.m 3 | // DLWidgetMenu 4 | // 5 | // Created by Vincent Esche on 05/11/13. 6 | // Copyright (c) 2013 Vincent Esche. All rights reserved. 7 | // 8 | 9 | #import "DLWMAppDelegate.h" 10 | 11 | @implementation DLWMAppDelegate 12 | 13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 14 | // Override point for customization after application launch. 15 | return YES; 16 | } 17 | 18 | - (void)applicationWillResignActive:(UIApplication *)application { 19 | // 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. 20 | // 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. 21 | } 22 | 23 | - (void)applicationDidEnterBackground:(UIApplication *)application { 24 | // 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. 25 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 26 | } 27 | 28 | - (void)applicationWillEnterForeground:(UIApplication *)application { 29 | // 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. 30 | } 31 | 32 | - (void)applicationDidBecomeActive:(UIApplication *)application { 33 | // 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. 34 | } 35 | 36 | - (void)applicationWillTerminate:(UIApplication *)application { 37 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /DLWidgetMenu/DLWMGenericMenuViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DLWMGenericMenuViewController.h 3 | // DLWidgetMenu 4 | // 5 | // Created by Vincent Esche on 05/11/13. 6 | // Copyright (c) 2013 Vincent Esche. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class DLWMMenu; 12 | 13 | @interface DLWMGenericMenuViewController : UIViewController 14 | 15 | @property (readwrite, weak, nonatomic) IBOutlet UIView *configurationView; 16 | @property (readwrite, strong, nonatomic) DLWMMenu *menu; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /DLWidgetMenu/DLWMGenericMenuViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DLWMGenericMenuViewController.m 3 | // DLWidgetMenu 4 | // 5 | // Created by Vincent Esche on 05/11/13. 6 | // Copyright (c) 2013 Vincent Esche. All rights reserved. 7 | // 8 | 9 | #import "DLWMGenericMenuViewController.h" 10 | 11 | #import "DLWMMenu.h" 12 | #import "DLWMMenuAnimator.h" 13 | #import "DLWMSpringMenuAnimator.h" 14 | #import "DLWMSelectionMenuAnimator.h" 15 | 16 | @interface DLWMGenericMenuViewController () 17 | 18 | @end 19 | 20 | @implementation DLWMGenericMenuViewController 21 | 22 | - (id)initWithCoder:(NSCoder *)decoder { 23 | self = [super initWithCoder:decoder]; 24 | if (self) { 25 | 26 | } 27 | return self; 28 | } 29 | 30 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { 31 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 32 | if (self) { 33 | 34 | } 35 | return self; 36 | } 37 | 38 | - (void)viewDidLoad { 39 | [super viewDidLoad]; 40 | 41 | if ([self respondsToSelector:@selector(edgesForExtendedLayout)]) { 42 | self.edgesForExtendedLayout = UIRectEdgeNone; 43 | } 44 | 45 | self.view.backgroundColor = [UIColor darkGrayColor]; 46 | 47 | id layout = [[self class] layout]; 48 | 49 | CGRect frame = self.view.bounds; 50 | 51 | DLWMMenu *menu = [[DLWMMenu alloc] initWithMainItemView:[self viewForMainItemInMenu:nil] 52 | dataSource:self 53 | itemSource:self 54 | delegate:self 55 | itemDelegate:self 56 | layout:layout 57 | representedObject:self]; 58 | menu.openAnimationDelayBetweenItems = 0.01; 59 | menu.closeAnimationDelayBetweenItems = 0.01; 60 | 61 | menu.center = CGPointMake(CGRectGetMidX(frame), CGRectGetMaxY(frame) * 0.66); 62 | self.menu = menu; 63 | 64 | [self.view insertSubview:self.menu atIndex:0]; 65 | 66 | self.configurationView.backgroundColor = [UIColor clearColor]; 67 | 68 | for (UIView *subview in self.configurationView.subviews) { 69 | if ([subview isKindOfClass:[UILabel class]]) { 70 | ((UILabel *)subview).textColor = [UIColor whiteColor]; 71 | } 72 | } 73 | } 74 | 75 | - (void)didReceiveMemoryWarning { 76 | [super didReceiveMemoryWarning]; 77 | // Dispose of any resources that can be recreated. 78 | } 79 | 80 | - (void)viewDidAppear:(BOOL)animated { 81 | [super viewDidAppear:animated]; 82 | 83 | [self.menu openAnimated:YES]; 84 | } 85 | 86 | - (void)viewDidDisappear:(BOOL)animated { 87 | [super viewDidDisappear:animated]; 88 | 89 | [self.menu closeAnimated:NO]; 90 | } 91 | 92 | - (BOOL)prefersStatusBarHidden { 93 | return YES; 94 | } 95 | 96 | #pragma mark - DLWMMenuLayout 97 | 98 | + (id)layout { 99 | return nil; 100 | } 101 | 102 | #pragma mark - DLWMMenuDataSource Protocol 103 | 104 | - (NSUInteger)numberOfObjectsInMenu:(DLWMMenu *)menu { 105 | return 50; 106 | } 107 | 108 | - (id)objectAtIndex:(NSUInteger)index inMenu:(DLWMMenu *)menu { 109 | return @(index); 110 | } 111 | 112 | #pragma mark - DLWMMenuItemSource Protocol 113 | 114 | + (UIView *)menuItemView { 115 | UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 40.0, 40.0)]; 116 | view.layer.borderColor = [UIColor colorWithWhite:1.0 alpha:0.75].CGColor; 117 | view.layer.borderWidth = 1.5; 118 | view.layer.shadowColor = [UIColor blackColor].CGColor; 119 | view.layer.shadowOffset = CGSizeMake(0.0, 1.5); 120 | view.layer.shadowOpacity = 0.5; 121 | 122 | CAGradientLayer *gradientLayer = [CAGradientLayer layer]; 123 | gradientLayer.frame = view.layer.bounds; 124 | gradientLayer.colors = @[(__bridge id)[UIColor colorWithWhite:1.0 alpha:0.5].CGColor, 125 | (__bridge id)[UIColor colorWithWhite:1.0 alpha:0.1].CGColor, 126 | (__bridge id)[UIColor colorWithWhite:1.0 alpha:0.0].CGColor, 127 | (__bridge id)[UIColor colorWithWhite:1.0 alpha:0.2].CGColor]; 128 | gradientLayer.locations = @[@0.0, @0.49, @0.5, @1.0]; 129 | [view.layer addSublayer:gradientLayer]; 130 | return view; 131 | } 132 | 133 | - (UIView *)viewForMainItemInMenu:(DLWMMenu *)menu { 134 | UIView *view = [[self class] menuItemView]; 135 | view.bounds = CGRectMake(0.0, 0.0, 50.0, 50.0); 136 | view.backgroundColor = [UIColor lightGrayColor]; 137 | view.layer.cornerRadius = 25.0; 138 | CAGradientLayer *gradientLayer = (CAGradientLayer *)view.layer.sublayers[0]; 139 | gradientLayer.cornerRadius = view.layer.cornerRadius; 140 | gradientLayer.frame = view.layer.bounds; 141 | return view; 142 | } 143 | 144 | - (UIView *)viewForObject:(id)object atIndex:(NSUInteger)index inMenu:(DLWMMenu *)menu { 145 | UIView *view = [[self class] menuItemView]; 146 | view.bounds = CGRectMake(0.0, 0.0, 40.0, 40.0); 147 | CGFloat hue = ((1.0 / [self numberOfObjectsInMenu:menu]) * index); 148 | view.backgroundColor = [UIColor colorWithHue:hue saturation:1.0 brightness:1.0 alpha:1.0]; 149 | view.layer.cornerRadius = 20.0; 150 | CAGradientLayer *gradientLayer = (CAGradientLayer *)view.layer.sublayers[0]; 151 | gradientLayer.cornerRadius = view.layer.cornerRadius; 152 | gradientLayer.frame = view.layer.bounds; 153 | return view; 154 | } 155 | 156 | #pragma mark - DLWMMenuItemDelegate Protocol 157 | 158 | - (void)willOpenItem:(DLWMMenuItem *)item inMenu:(DLWMMenu *)menu withDuration:(NSTimeInterval)duration { 159 | NSLog(@"%s", __FUNCTION__); 160 | } 161 | 162 | - (void)willCloseItem:(DLWMMenuItem *)item inMenu:(DLWMMenu *)menu withDuration:(NSTimeInterval)duration { 163 | NSLog(@"%s", __FUNCTION__); 164 | } 165 | 166 | - (void)didOpenItem:(DLWMMenuItem *)item inMenu:(DLWMMenu *)menu withDuration:(NSTimeInterval)duration { 167 | NSLog(@"%s", __FUNCTION__); 168 | } 169 | 170 | - (void)didCloseItem:(DLWMMenuItem *)item inMenu:(DLWMMenu *)menu withDuration:(NSTimeInterval)duration { 171 | NSLog(@"%s", __FUNCTION__); 172 | } 173 | 174 | #pragma mark - DLWMMenuDelegate Protocol 175 | 176 | - (void)receivedSingleTap:(UITapGestureRecognizer *)recognizer onItem:(DLWMMenuItem *)item inMenu:(DLWMMenu *)menu { 177 | NSLog(@"%s", __FUNCTION__); 178 | NSLog(@"recognizer: %@", recognizer); 179 | if ([menu isClosedOrClosing]) { 180 | [menu open]; 181 | } else if ([menu isOpenedOrOpening]) { 182 | if (item == menu.mainItem) { 183 | [menu close]; 184 | } else { 185 | [menu closeWithSpecialAnimator:[[DLWMSelectionMenuAnimator alloc] init] forItem:item]; 186 | } 187 | } 188 | } 189 | 190 | - (void)receivedDoubleTap:(UITapGestureRecognizer *)recognizer onItem:(DLWMMenuItem *)item inMenu:(DLWMMenu *)menu { 191 | NSLog(@"%s", __FUNCTION__); 192 | NSLog(@"recognizer: %@", recognizer); 193 | } 194 | 195 | - (void)receivedLongPress:(UILongPressGestureRecognizer *)recognizer onItem:(DLWMMenuItem *)item inMenu:(DLWMMenu *)menu { 196 | NSLog(@"%s", __FUNCTION__); 197 | NSLog(@"recognizer: %@", recognizer); 198 | } 199 | 200 | - (void)receivedPinch:(UIPinchGestureRecognizer *)recognizer onItem:(DLWMMenuItem *)item inMenu:(DLWMMenu *)menu { 201 | NSLog(@"%s", __FUNCTION__); 202 | NSLog(@"recognizer: %@", recognizer); 203 | } 204 | 205 | - (void)receivedPan:(UIPanGestureRecognizer *)recognizer onItem:(DLWMMenuItem *)item inMenu:(DLWMMenu *)menu { 206 | NSLog(@"%s", __FUNCTION__); 207 | NSLog(@"recognizer: %@", recognizer); 208 | if (item == menu.mainItem) { 209 | [menu moveTo:[recognizer locationInView:menu.superview] animated:NO]; 210 | } 211 | } 212 | 213 | - (void)receivedSingleTap:(UITapGestureRecognizer *)recognizer outsideOfMenu:(DLWMMenu *)menu { 214 | NSLog(@"recognizer: %@", recognizer); 215 | [menu close]; 216 | } 217 | 218 | - (void)willOpenMenu:(DLWMMenu *)menu withDuration:(NSTimeInterval)duration { 219 | NSLog(@"%s", __FUNCTION__); 220 | } 221 | 222 | - (void)didOpenMenu:(DLWMMenu *)menu { 223 | NSLog(@"%s", __FUNCTION__); 224 | } 225 | 226 | - (void)willCloseMenu:(DLWMMenu *)menu withDuration:(NSTimeInterval)duration { 227 | NSLog(@"%s", __FUNCTION__); 228 | } 229 | 230 | - (void)didCloseMenu:(DLWMMenu *)menu { 231 | NSLog(@"%s", __FUNCTION__); 232 | } 233 | 234 | @end 235 | -------------------------------------------------------------------------------- /DLWidgetMenu/DLWMLinearMenuViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DLWMLinearMenuViewController.h 3 | // DLWidgetMenu 4 | // 5 | // Created by Vincent Esche on 05/11/13. 6 | // Copyright (c) 2013 Vincent Esche. All rights reserved. 7 | // 8 | 9 | #import "DLWMGenericMenuViewController.h" 10 | 11 | @interface DLWMLinearMenuViewController : DLWMGenericMenuViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /DLWidgetMenu/DLWMLinearMenuViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DLWMLinearMenuViewController.m 3 | // DLWidgetMenu 4 | // 5 | // Created by Vincent Esche on 05/11/13. 6 | // Copyright (c) 2013 Vincent Esche. All rights reserved. 7 | // 8 | 9 | #import "DLWMLinearMenuViewController.h" 10 | 11 | #import "DLWMLinearLayout.h" 12 | 13 | @interface DLWMLinearMenuViewController () 14 | 15 | @property (readwrite, assign, nonatomic) BOOL layered; 16 | 17 | @property (readwrite, assign, nonatomic) NSUInteger items; 18 | @property (readwrite, assign, nonatomic) CGFloat itemSpacing; 19 | @property (readwrite, assign, nonatomic) CGFloat centerSpacing; 20 | @property (readwrite, assign, nonatomic) CGFloat angle; 21 | 22 | @property (weak, nonatomic) IBOutlet UISlider *itemsSlider; 23 | @property (weak, nonatomic) IBOutlet UILabel *itemsLabel; 24 | 25 | @property (weak, nonatomic) IBOutlet UISlider *itemSpacingSlider; 26 | @property (weak, nonatomic) IBOutlet UILabel *itemSpacingLabel; 27 | 28 | @property (weak, nonatomic) IBOutlet UISlider *centerSpacingSlider; 29 | @property (weak, nonatomic) IBOutlet UILabel *centerSpacingLabel; 30 | 31 | @property (weak, nonatomic) IBOutlet UISlider *angleSlider; 32 | @property (weak, nonatomic) IBOutlet UILabel *angleLabel; 33 | 34 | @end 35 | 36 | @implementation DLWMLinearMenuViewController 37 | 38 | #pragma mark - DLWMMenuLayout 39 | 40 | - (id)initWithCoder:(NSCoder *)decoder { 41 | self = [super initWithCoder:decoder]; 42 | if (self) { 43 | 44 | } 45 | return self; 46 | } 47 | 48 | - (void)viewDidLoad { 49 | [super viewDidLoad]; 50 | 51 | self.items = 3; 52 | self.itemSpacing = 45.0; // in pixels 53 | self.centerSpacing = 50.0; // in pixels 54 | self.angle = M_PI_2 * 3; // in radians 55 | } 56 | 57 | + (id)layout { 58 | return [[DLWMLinearLayout alloc] initWithAngle:0.0 itemSpacing:45.0 centerSpacing:50.0]; 59 | } 60 | 61 | #pragma mark - DLWMMenuDataSource Protocol 62 | 63 | - (NSUInteger)numberOfObjectsInMenu:(DLWMMenu *)menu { 64 | return self.items; 65 | } 66 | 67 | #pragma mark - IBActions 68 | 69 | - (IBAction)changeItems:(UISlider *)sender { 70 | self.items = sender.value; 71 | } 72 | 73 | - (IBAction)changeItemSpacing:(UISlider *)sender { 74 | self.itemSpacing = sender.value; 75 | } 76 | 77 | - (IBAction)changeCenterSpacing:(UISlider *)sender { 78 | self.centerSpacing = sender.value; 79 | } 80 | 81 | - (IBAction)changeAngle:(UISlider *)sender { 82 | self.angle = sender.value; 83 | } 84 | 85 | #pragma mark - Accessors 86 | 87 | - (void)setItems:(NSUInteger)items { 88 | _items = items; 89 | [self.menu reloadData]; 90 | self.itemsSlider.value = items; 91 | self.itemsLabel.text = [NSString stringWithFormat:@"%lu", (unsigned long)items]; 92 | } 93 | 94 | - (void)setItemSpacing:(CGFloat)itemSpacing { 95 | _itemSpacing = itemSpacing; 96 | ((DLWMLinearLayout *)self.menu.layout).itemSpacing = itemSpacing; 97 | self.itemSpacingSlider.value = itemSpacing; 98 | self.itemSpacingLabel.text = [NSString stringWithFormat:@"%.0fpx", itemSpacing]; 99 | } 100 | 101 | - (void)setCenterSpacing:(CGFloat)centerSpacing { 102 | _centerSpacing = centerSpacing; 103 | ((DLWMLinearLayout *)self.menu.layout).centerSpacing = centerSpacing; 104 | self.centerSpacingSlider.value = centerSpacing; 105 | self.centerSpacingLabel.text = [NSString stringWithFormat:@"%.0fpx", centerSpacing]; 106 | } 107 | 108 | - (void)setAngle:(CGFloat)angle { 109 | _angle = angle; 110 | ((DLWMLinearLayout *)self.menu.layout).angle = angle; 111 | self.angleSlider.value = angle; 112 | self.angleLabel.text = [NSString stringWithFormat:@"%.0fº", (angle / M_PI) * 180]; 113 | } 114 | 115 | @end 116 | -------------------------------------------------------------------------------- /DLWidgetMenu/DLWMRadialMenuViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DLWMRadialMenuViewController.h 3 | // DLWidgetMenu 4 | // 5 | // Created by Vincent Esche on 05/11/13. 6 | // Copyright (c) 2013 Vincent Esche. All rights reserved. 7 | // 8 | 9 | #import "DLWMGenericMenuViewController.h" 10 | 11 | @interface DLWMRadialMenuViewController : DLWMGenericMenuViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /DLWidgetMenu/DLWMRadialMenuViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DLWMRadialMenuViewController.m 3 | // DLWidgetMenu 4 | // 5 | // Created by Vincent Esche on 05/11/13. 6 | // Copyright (c) 2013 Vincent Esche. All rights reserved. 7 | // 8 | 9 | #import "DLWMRadialMenuViewController.h" 10 | 11 | #import "DLWMRadialLayout.h" 12 | 13 | @interface DLWMRadialMenuViewController () 14 | 15 | @property (readwrite, assign, nonatomic) BOOL layered; 16 | 17 | @property (readwrite, assign, nonatomic) NSUInteger items; 18 | @property (readwrite, assign, nonatomic) CGFloat radius; 19 | @property (readwrite, assign, nonatomic) CGFloat arc; 20 | @property (readwrite, assign, nonatomic) CGFloat angle; 21 | @property (readwrite, assign, nonatomic) CGFloat minDistance; 22 | @property (readwrite, assign, nonatomic) BOOL uniformOuterLayer; 23 | 24 | @property (weak, nonatomic) IBOutlet UISlider *itemsSlider; 25 | @property (weak, nonatomic) IBOutlet UILabel *itemsLabel; 26 | 27 | @property (weak, nonatomic) IBOutlet UISlider *radiusSlider; 28 | @property (weak, nonatomic) IBOutlet UILabel *radiusLabel; 29 | 30 | @property (weak, nonatomic) IBOutlet UISlider *arcSlider; 31 | @property (weak, nonatomic) IBOutlet UILabel *arcLabel; 32 | 33 | @property (weak, nonatomic) IBOutlet UISlider *angleSlider; 34 | @property (weak, nonatomic) IBOutlet UILabel *angleLabel; 35 | 36 | @property (weak, nonatomic) IBOutlet UISlider *minDistanceSlider; 37 | @property (weak, nonatomic) IBOutlet UILabel *minDistanceLabel; 38 | 39 | @property (weak, nonatomic) IBOutlet UISwitch *uniformOuterLayerSwitch; 40 | 41 | @end 42 | 43 | @implementation DLWMRadialMenuViewController 44 | 45 | #pragma mark - DLWMMenuLayout 46 | 47 | - (id)initWithCoder:(NSCoder *)decoder { 48 | self = [super initWithCoder:decoder]; 49 | if (self) { 50 | 51 | } 52 | return self; 53 | } 54 | 55 | - (void)viewDidLoad { 56 | [super viewDidLoad]; 57 | 58 | self.items = 8; 59 | self.radius = 60.0; // in pixels 60 | self.arc = DLWMFullCircle; // in radians 61 | self.angle = M_PI_2 * 3; // in radians 62 | self.minDistance = 40.0; // in pixels 63 | self.uniformOuterLayer = NO; 64 | } 65 | 66 | + (id)layout { 67 | return [[DLWMRadialLayout alloc] initWithRadius:60.0 arc:DLWMFullCircle angle:0.0 minDistance:0.0]; 68 | } 69 | 70 | #pragma mark - DLWMMenuDataSource Protocol 71 | 72 | - (NSUInteger)numberOfObjectsInMenu:(DLWMMenu *)menu { 73 | return self.items; 74 | } 75 | 76 | #pragma mark - IBActions 77 | 78 | - (IBAction)changeItems:(UISlider *)sender { 79 | self.items = sender.value; 80 | } 81 | 82 | - (IBAction)changeRadius:(UISlider *)sender { 83 | self.radius = sender.value; 84 | } 85 | 86 | - (IBAction)changeArc:(UISlider *)sender { 87 | self.arc = sender.value; 88 | } 89 | 90 | - (IBAction)changeAngle:(UISlider *)sender { 91 | self.angle = sender.value; 92 | } 93 | 94 | - (IBAction)changeMinDistance:(UISlider *)sender { 95 | self.minDistance = sender.value; 96 | } 97 | 98 | - (IBAction)changeUniformOuterLayer:(UISwitch *)sender { 99 | self.uniformOuterLayer = sender.on; 100 | 101 | } 102 | 103 | #pragma mark - Accessors 104 | 105 | - (void)setItems:(NSUInteger)items { 106 | _items = items; 107 | [self.menu reloadData]; 108 | self.itemsSlider.value = items; 109 | self.itemsLabel.text = [NSString stringWithFormat:@"%lu", (unsigned long)items]; 110 | } 111 | 112 | - (void)setRadius:(CGFloat)radius { 113 | _radius = radius; 114 | ((DLWMRadialLayout *)self.menu.layout).radius = radius; 115 | self.radiusSlider.value = radius; 116 | self.radiusLabel.text = [NSString stringWithFormat:@"%.0fpx", radius]; 117 | } 118 | 119 | - (void)setArc:(CGFloat)arc { 120 | _arc = arc; 121 | ((DLWMRadialLayout *)self.menu.layout).arc = arc; 122 | self.arcSlider.value = arc; 123 | self.arcLabel.text = [NSString stringWithFormat:@"%.0fº", (arc / M_PI) * 180]; 124 | } 125 | 126 | - (void)setAngle:(CGFloat)angle { 127 | _angle = angle; 128 | ((DLWMRadialLayout *)self.menu.layout).angle = angle; 129 | self.angleSlider.value = angle; 130 | self.angleLabel.text = [NSString stringWithFormat:@"%.0fº", (angle / M_PI) * 180]; 131 | } 132 | 133 | - (void)setMinDistance:(CGFloat)minDistance { 134 | _minDistance = minDistance; 135 | ((DLWMRadialLayout *)self.menu.layout).minDistance = minDistance; 136 | self.minDistanceSlider.value = minDistance; 137 | self.minDistanceLabel.text = [NSString stringWithFormat:@"%.0fpx", minDistance]; 138 | } 139 | 140 | - (void)setUniformOuterLayer:(BOOL)uniformOuterLayer { 141 | _uniformOuterLayer = uniformOuterLayer; 142 | ((DLWMRadialLayout *)self.menu.layout).uniformOuterLayer = uniformOuterLayer; 143 | self.uniformOuterLayerSwitch.on = uniformOuterLayer; 144 | } 145 | 146 | @end 147 | -------------------------------------------------------------------------------- /DLWidgetMenu/DLWMSpiralMenuViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DLWMSpiralMenuViewController.h 3 | // DLWidgetMenu 4 | // 5 | // Created by Vincent Esche on 05/11/13. 6 | // Copyright (c) 2013 Vincent Esche. All rights reserved. 7 | // 8 | 9 | #import "DLWMGenericMenuViewController.h" 10 | 11 | @interface DLWMSpiralMenuViewController : DLWMGenericMenuViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /DLWidgetMenu/DLWMSpiralMenuViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DLWMSpiralMenuViewController.m 3 | // DLWidgetMenu 4 | // 5 | // Created by Vincent Esche on 05/11/13. 6 | // Copyright (c) 2013 Vincent Esche. All rights reserved. 7 | // 8 | 9 | #import "DLWMSpiralMenuViewController.h" 10 | 11 | #import "DLWMSpiralLayout.h" 12 | 13 | @interface DLWMSpiralMenuViewController () 14 | 15 | @property (readwrite, assign, nonatomic) BOOL layered; 16 | 17 | @property (readwrite, assign, nonatomic) NSUInteger items; 18 | @property (readwrite, assign, nonatomic) CGFloat radius; 19 | @property (readwrite, assign, nonatomic) CGFloat angle; 20 | @property (readwrite, assign, nonatomic) CGFloat itemDistance; 21 | 22 | @property (weak, nonatomic) IBOutlet UISlider *itemsSlider; 23 | @property (weak, nonatomic) IBOutlet UILabel *itemsLabel; 24 | 25 | @property (weak, nonatomic) IBOutlet UISlider *radiusSlider; 26 | @property (weak, nonatomic) IBOutlet UILabel *radiusLabel; 27 | 28 | @property (weak, nonatomic) IBOutlet UISlider *angleSlider; 29 | @property (weak, nonatomic) IBOutlet UILabel *angleLabel; 30 | 31 | @property (weak, nonatomic) IBOutlet UISlider *itemDistanceSlider; 32 | @property (weak, nonatomic) IBOutlet UILabel *itemDistanceLabel; 33 | 34 | @end 35 | 36 | @implementation DLWMSpiralMenuViewController 37 | 38 | #pragma mark - DLWMMenuLayout 39 | 40 | - (id)initWithCoder:(NSCoder *)decoder { 41 | self = [super initWithCoder:decoder]; 42 | if (self) { 43 | 44 | } 45 | return self; 46 | } 47 | 48 | - (void)viewDidLoad { 49 | [super viewDidLoad]; 50 | 51 | self.items = 8; 52 | self.radius = 30.0; // in pixels 53 | self.angle = M_PI_2 * 3; // in radians 54 | self.itemDistance = 20.0; // in pixels 55 | } 56 | 57 | + (id)layout { 58 | return [[DLWMSpiralLayout alloc] initWithAngle:0.0 radius:50.0 itemDistance:30.0]; 59 | } 60 | 61 | #pragma mark - DLWMMenuDataSource Protocol 62 | 63 | - (NSUInteger)numberOfObjectsInMenu:(DLWMMenu *)menu { 64 | return self.items; 65 | } 66 | 67 | #pragma mark - IBActions 68 | 69 | - (IBAction)changeItems:(UISlider *)sender { 70 | self.items = sender.value; 71 | } 72 | 73 | - (IBAction)changeRadius:(UISlider *)sender { 74 | self.radius = sender.value; 75 | } 76 | 77 | - (IBAction)changeAngle:(UISlider *)sender { 78 | self.angle = sender.value; 79 | } 80 | 81 | - (IBAction)changeItemDistance:(UISlider *)sender { 82 | self.itemDistance = sender.value; 83 | } 84 | 85 | #pragma mark - Accessors 86 | 87 | - (void)setItems:(NSUInteger)items { 88 | _items = items; 89 | [self.menu reloadData]; 90 | self.itemsSlider.value = items; 91 | self.itemsLabel.text = [NSString stringWithFormat:@"%lu", (unsigned long)items]; 92 | } 93 | 94 | - (void)setRadius:(CGFloat)radius { 95 | _radius = radius; 96 | ((DLWMSpiralLayout *)self.menu.layout).radius = radius; 97 | self.radiusSlider.value = radius; 98 | self.radiusLabel.text = [NSString stringWithFormat:@"%.0fpx", radius]; 99 | } 100 | 101 | - (void)setAngle:(CGFloat)angle { 102 | _angle = angle; 103 | ((DLWMSpiralLayout *)self.menu.layout).angle = angle; 104 | self.angleSlider.value = angle; 105 | self.angleLabel.text = [NSString stringWithFormat:@"%.0fº", (angle / M_PI) * 180]; 106 | } 107 | 108 | - (void)setItemDistance:(CGFloat)itemDistance { 109 | _itemDistance = itemDistance; 110 | ((DLWMSpiralLayout *)self.menu.layout).itemDistance = itemDistance; 111 | self.itemDistanceSlider.value = itemDistance; 112 | self.itemDistanceLabel.text = [NSString stringWithFormat:@"%.0fpx", itemDistance]; 113 | } 114 | 115 | @end 116 | -------------------------------------------------------------------------------- /DLWidgetMenu/DLWidgetMenu-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | definiteloop.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIMainStoryboardFile 28 | Main_iPhone 29 | UIMainStoryboardFile~ipad 30 | Main_iPhone 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UIStatusBarTintParameters 36 | 37 | UINavigationBar 38 | 39 | Style 40 | UIBarStyleDefault 41 | Translucent 42 | 43 | 44 | 45 | UISupportedInterfaceOrientations 46 | 47 | UIInterfaceOrientationPortrait 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | UISupportedInterfaceOrientations~ipad 52 | 53 | UIInterfaceOrientationPortrait 54 | UIInterfaceOrientationPortraitUpsideDown 55 | UIInterfaceOrientationLandscapeLeft 56 | UIInterfaceOrientationLandscapeRight 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /DLWidgetMenu/DLWidgetMenu-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #endif 17 | -------------------------------------------------------------------------------- /DLWidgetMenu/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "60x60", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "ipad", 20 | "size" : "29x29", 21 | "scale" : "1x" 22 | }, 23 | { 24 | "idiom" : "ipad", 25 | "size" : "29x29", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "ipad", 30 | "size" : "40x40", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "40x40", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "76x76", 41 | "scale" : "1x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "76x76", 46 | "scale" : "2x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /DLWidgetMenu/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "orientation" : "portrait", 20 | "idiom" : "ipad", 21 | "extent" : "full-screen", 22 | "minimum-system-version" : "7.0", 23 | "scale" : "1x" 24 | }, 25 | { 26 | "orientation" : "landscape", 27 | "idiom" : "ipad", 28 | "extent" : "full-screen", 29 | "minimum-system-version" : "7.0", 30 | "scale" : "1x" 31 | }, 32 | { 33 | "orientation" : "portrait", 34 | "idiom" : "ipad", 35 | "extent" : "full-screen", 36 | "minimum-system-version" : "7.0", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "orientation" : "landscape", 41 | "idiom" : "ipad", 42 | "extent" : "full-screen", 43 | "minimum-system-version" : "7.0", 44 | "scale" : "2x" 45 | } 46 | ], 47 | "info" : { 48 | "version" : 1, 49 | "author" : "xcode" 50 | } 51 | } -------------------------------------------------------------------------------- /DLWidgetMenu/Images.xcassets/linear.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "linear.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "linear@2x.png" 12 | } 13 | ], 14 | "info" : { 15 | "version" : 1, 16 | "author" : "xcode" 17 | } 18 | } -------------------------------------------------------------------------------- /DLWidgetMenu/Images.xcassets/linear.imageset/linear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regexident/DLWidgetMenu/9e937801cd583fa08d331128babcefe012a7daae/DLWidgetMenu/Images.xcassets/linear.imageset/linear.png -------------------------------------------------------------------------------- /DLWidgetMenu/Images.xcassets/linear.imageset/linear@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regexident/DLWidgetMenu/9e937801cd583fa08d331128babcefe012a7daae/DLWidgetMenu/Images.xcassets/linear.imageset/linear@2x.png -------------------------------------------------------------------------------- /DLWidgetMenu/Images.xcassets/radial.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "radial.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "radial@2x.png" 12 | } 13 | ], 14 | "info" : { 15 | "version" : 1, 16 | "author" : "xcode" 17 | } 18 | } -------------------------------------------------------------------------------- /DLWidgetMenu/Images.xcassets/radial.imageset/radial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regexident/DLWidgetMenu/9e937801cd583fa08d331128babcefe012a7daae/DLWidgetMenu/Images.xcassets/radial.imageset/radial.png -------------------------------------------------------------------------------- /DLWidgetMenu/Images.xcassets/radial.imageset/radial@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regexident/DLWidgetMenu/9e937801cd583fa08d331128babcefe012a7daae/DLWidgetMenu/Images.xcassets/radial.imageset/radial@2x.png -------------------------------------------------------------------------------- /DLWidgetMenu/Images.xcassets/spiral.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "spiral.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "spiral@2x.png" 12 | } 13 | ], 14 | "info" : { 15 | "version" : 1, 16 | "author" : "xcode" 17 | } 18 | } -------------------------------------------------------------------------------- /DLWidgetMenu/Images.xcassets/spiral.imageset/spiral.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regexident/DLWidgetMenu/9e937801cd583fa08d331128babcefe012a7daae/DLWidgetMenu/Images.xcassets/spiral.imageset/spiral.png -------------------------------------------------------------------------------- /DLWidgetMenu/Images.xcassets/spiral.imageset/spiral@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regexident/DLWidgetMenu/9e937801cd583fa08d331128babcefe012a7daae/DLWidgetMenu/Images.xcassets/spiral.imageset/spiral@2x.png -------------------------------------------------------------------------------- /DLWidgetMenu/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /DLWidgetMenu/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // DLWidgetMenu 4 | // 5 | // Created by Vincent Esche on 05/11/13. 6 | // Copyright (c) 2013 Vincent Esche. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "DLWMAppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([DLWMAppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DLWidgetMenuTests/DLWidgetMenuTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | definiteloop.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /DLWidgetMenuTests/DLWidgetMenuTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // DLWidgetMenuTests.m 3 | // DLWidgetMenuTests 4 | // 5 | // Created by Vincent Esche on 05/11/13. 6 | // Copyright (c) 2013 Vincent Esche. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DLWidgetMenuTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation DLWidgetMenuTests 16 | 17 | - (void)setUp 18 | { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown 24 | { 25 | // Put teardown code here. This method is called after the invocation of each test method in the class. 26 | [super tearDown]; 27 | } 28 | 29 | - (void)testExample 30 | { 31 | XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /DLWidgetMenuTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Vincent Esche 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | 3. Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | 4. Redistributions of any form whatsoever must retain the following acknowledgment: "This product includes code by Vincent Esche." where would be replaced by the name of the specific source-code package being made use of. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DLWidgetMenu [![No Maintenance Intended](http://unmaintained.tech/badge.svg)](http://unmaintained.tech/) 2 | 3 | **DLWidgetMenu** is a versatile solution for displaying widget menus and can easily be extended with custom layouts and/or animations. 4 | 5 | ## Preview 6 | ![screenshot](screencast.gif) 7 | 8 | ## Features 9 | 10 | **DLWidgetMenu** comes with three built-in layouts: 11 | 12 | * `DLWMLinearLayout` 13 | * `DLWMRadialLayout` 14 | * `DLWMSpiralLayout` 15 | 16 | **DLWidgetMenu** uses iOS7's **spring animations** (`DLWMSpringMenuAnimator`) by default, but can easily be provided with your own **custom animations** as well (by subclassing `DLWMMenuAnimator`). 17 | 18 | **DLWidgetMenu** observes its menu items for these **gesture recognizer events**: 19 | 20 | * single tap 21 | * double tap 22 | * long press 23 | * pan 24 | * pinch 25 | 26 | and **forwards them** directly to its **delegate**. 27 | 28 | None of these events make **DLWidgetMenu** do anything by default. 29 | The menu (by default) **doesn't even open**, when you **tap it**! 30 | 31 | And **it shouldn't**. 32 | 33 | **DLWidgetMenu** was specifically **designed** to keep you in full control over **its behaviour**. All it does is animating and layouting its menu items. Keeping **you in total charge** of **what to do** and **when**. 34 | 35 | Assuming you do want **DLWidgetMenu** to open when single-tapped, all you need to do is implement this in your `DLWMMenuDelegate`: 36 | 37 | ```objc 38 | - (void)receivedSingleTap:(UITapGestureRecognizer *)recognizer onItem:(DLWMMenuItem *)item inMenu:(DLWMMenu *)menu { 39 | if ([menu isClosedOrClosing]) { 40 | [menu open]; 41 | } else if ([menu isOpenedOrOpening]) { 42 | if (item == menu.mainItem) { 43 | [menu close]; 44 | } else { 45 | [menu closeWithSpecialAnimator:[[DLWMSelectionMenuAnimator alloc] init] forItem:item]; 46 | } 47 | } 48 | } 49 | ``` 50 | 51 | As you can see the delegate makes **DLWidgetMenu** use a custom animator for the tapped menu item by calling: 52 | 53 | ```objc 54 | [menu closeWithSpecialAnimator:[[DLWMSelectionMenuAnimator alloc] init] forItem:item]; 55 | ``` 56 | 57 | (The built-in `DLWMSelectionMenuAnimator` applies a **scale-up** in combination with a **fade-out**.) 58 | 59 | Don't want that? Fine, just call `[menu close];` then. 60 | 61 | Okay, tapping is pretty standard stuff, right? Nothing too fancy. 62 | So how about **moving** a **menu around** using a **pan gesture**? Piece of cake: 63 | 64 | ```objc 65 | - (void)receivedPan:(UIPanGestureRecognizer *)recognizer onItem:(DLWMMenuItem *)item inMenu:(DLWMMenu *)menu { 66 | // NSLog(@"%s", __FUNCTION__); 67 | if (item == menu.mainItem) { 68 | [menu moveTo:[recognizer locationInView:menu.superview] animated:NO]; 69 | } 70 | } 71 | ``` 72 | **** 73 | It's worth mentioning here that one should usually prefer `[menu moveTo:… animated:…];` over `menu.center = …;` (while the menu is open, that is). 74 | 75 | The reason for this is that **DLWidgetMenu** shrinks to just wrap its main item when closed and stretches to fit its superview when opened (in order to catch tap events outside its menu items). Just enable the debugging-mode (`menu.debuggingEnabled = YES;`) and you should get an idea of what this means. 76 | 77 | ## Installation 78 | 79 | Just copy the files in `"DLWidgetMenu/Classes/..."` into your project. 80 | 81 | Alternatively you can install DLWidgetMenu into your project with [CocoaPods](http://cocoapods.org/). 82 | Just add it to your Podfile: `pod 'DLWidgetMenu'` 83 | 84 | ## Demos 85 | 86 | **DLWidgetMenu** contains a demo app giving you a quick overview of all three included layouts. 87 | 88 | ## ARC 89 | 90 | **DLWidgetMenu** uses **automatic reference counting (ARC)**. 91 | 92 | ## Dependencies 93 | 94 | None. 95 | 96 | ## Creator 97 | 98 | Vincent Esche ([@regexident](http://twitter.com/regexident)) 99 | 100 | ## License 101 | 102 | **DLWidgetMenu** is available under a **modified BSD-3 clause license** with the **additional requirement of attribution**. See the `LICENSE` file for more info. 103 | -------------------------------------------------------------------------------- /icons.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regexident/DLWidgetMenu/9e937801cd583fa08d331128babcefe012a7daae/icons.psd -------------------------------------------------------------------------------- /screencast.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regexident/DLWidgetMenu/9e937801cd583fa08d331128babcefe012a7daae/screencast.gif --------------------------------------------------------------------------------