├── Dome.gif ├── README.md ├── ReadMeImage ├── XPQRotateMenuDependPositionLeft.png ├── XPQRotateMenuDependPositionRight.png ├── isUpToTop_NO.png └── isUpToTop_YES.png ├── XPQRotateMenu.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── XPQRotateMenu ├── AppDelegate.h ├── AppDelegate.m ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard ├── Images.xcassets │ ├── 1430121054613.imageset │ │ ├── 1430121054613.jpg │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── cancleIcon.imageset │ │ ├── Contents.json │ │ └── cancleIcon.png ├── Info.plist ├── ViewController.h ├── ViewController.m ├── XPQRotateItem.h ├── XPQRotateItem.m ├── XPQRotateMenu.h ├── XPQRotateMenu.m ├── XPQRotateMenuDelegate.h └── main.m └── XPQRotateMenuTests ├── Info.plist └── XPQRotateMenuTests.m /Dome.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepanqi/XPQRotateMenu/9255a0686c8afb9b7af6bb6bf19457b845b826c9/Dome.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XPQRotateMenu 2 | ## 由来 3 | 这个扇形菜单是从仿照ZQLRotateMenu(https://github.com/zangqilong198812/ZQLRotateMenu )开始的,然后慢慢的完善扩展。 4 | ## 效果图 5 | 6 | ![Flipboard playing multiple GIFs](https://github.com/xiepanqi/XPQRotateMenu/blob/master/Dome.gif) 7 | ## 使用 8 | 使用此菜单只需把(XPQRotateItem.h/XPQRotateItem.m/XPQRotateMenu.h/XPQRotateMenu.m/XPQRotateMenuDelegate.h)5个文件导入工程,再在需要的试图控制器类中添加 9 | 10 | #import "XPQRotateMenu.h" 11 | 然后再在viewDidLoad中添加以下3句代码就行了。 12 | 13 | NSArray *arr = @[@"菜单1",@"菜单2",@"菜单3",@"菜单4",@"菜单5",@"菜单6",@"菜单7",@"菜单8",@"菜单9"]; 14 | XPQRotateMenu *menu = [[XPQRotateMenu alloc] initWithTitleArray:arr]; 15 | [self.view addSubview:menu]; 16 | 17 | ##属性详解 18 | 当然,如果想使菜单具有更好的效果就需要设置相应的属性,下面就来详细介绍XPQRotateMenu中的属性。 19 | ###time 20 | 菜单展开或收缩动画的耗时,单位秒,默认值0.75s。 21 | ###backgroundColor 22 | 菜单展开时背景色。展开过程中背景色由透明慢慢变成backgroundColor,收缩过程背景色则由backgroundColor慢慢变成透明。 23 | 默认值是灰色(r:0.5 g:0.5 r:0.5 a:1.0)。 24 | ###expand(isExpand) 25 | 菜单是否展开,只读属性。展开时为YES,收缩时为NO。 26 | ###isUpToTop 27 | 是否上方菜单放上面。这个文字有点表达不清楚,下面贴两张效果图。默认值是YES。 28 | 29 | ![Flipboard playing multiple GIFs](https://github.com/xiepanqi/XPQRotateMenu/blob/master/ReadMeImage/isUpToTop_YES.png) 30 | ![Flipboard playing multiple GIFs](https://github.com/xiepanqi/XPQRotateMenu/blob/master/ReadMeImage/isUpToTop_NO.png) 31 | ###intersectionImage 32 | 交汇按钮图片,如果为nil则显示字符“➕”。默认为nil。 33 | ###backgroundImage 34 | 菜单展开时的背景图片,展开过程中背景图慢慢显示,收缩过程中慢慢消失。默认为nil。 35 | ###showClockwise 36 | 菜单展开动画是否沿顺时针。YES顺时针展开,NO逆时针展开。默认YES。 37 | ###hideClockwise 38 | 菜单收缩动画是否沿顺时针。YES顺时针展开,NO逆时针展开。默认NO。 39 | ###handleHideEnable 40 | 是否启用手势收缩菜单。启用后上下滑动手势可以收缩菜单,上滑逆时针收缩,下滑顺时针收缩。默认YES。 41 | ###dependPosition 42 | 菜单吸附的位置。枚举XPQRotateMenuDependPosition类型,暂只支持XPQRotateMenuDependPositionLeft和XPQRotateMenuDependPositionRight。效果如下: 43 | ![Flipboard playing multiple GIFs](https://github.com/xiepanqi/XPQRotateMenu/blob/master/ReadMeImage/XPQRotateMenuDependPositionLeft.png) 44 | ![Flipboard playing multiple GIFs](https://github.com/xiepanqi/XPQRotateMenu/blob/master/ReadMeImage/XPQRotateMenuDependPositionRight.png) 45 | ###delegate 46 | 代理 47 | -------------------------------------------------------------------------------- /ReadMeImage/XPQRotateMenuDependPositionLeft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepanqi/XPQRotateMenu/9255a0686c8afb9b7af6bb6bf19457b845b826c9/ReadMeImage/XPQRotateMenuDependPositionLeft.png -------------------------------------------------------------------------------- /ReadMeImage/XPQRotateMenuDependPositionRight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepanqi/XPQRotateMenu/9255a0686c8afb9b7af6bb6bf19457b845b826c9/ReadMeImage/XPQRotateMenuDependPositionRight.png -------------------------------------------------------------------------------- /ReadMeImage/isUpToTop_NO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepanqi/XPQRotateMenu/9255a0686c8afb9b7af6bb6bf19457b845b826c9/ReadMeImage/isUpToTop_NO.png -------------------------------------------------------------------------------- /ReadMeImage/isUpToTop_YES.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepanqi/XPQRotateMenu/9255a0686c8afb9b7af6bb6bf19457b845b826c9/ReadMeImage/isUpToTop_YES.png -------------------------------------------------------------------------------- /XPQRotateMenu.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | DFB4A64C1B28512500A7D4EF /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DFB4A64B1B28512500A7D4EF /* main.m */; }; 11 | DFB4A64F1B28512500A7D4EF /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DFB4A64E1B28512500A7D4EF /* AppDelegate.m */; }; 12 | DFB4A6521B28512500A7D4EF /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DFB4A6511B28512500A7D4EF /* ViewController.m */; }; 13 | DFB4A6551B28512500A7D4EF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DFB4A6531B28512500A7D4EF /* Main.storyboard */; }; 14 | DFB4A6571B28512500A7D4EF /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DFB4A6561B28512500A7D4EF /* Images.xcassets */; }; 15 | DFB4A65A1B28512500A7D4EF /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = DFB4A6581B28512500A7D4EF /* LaunchScreen.xib */; }; 16 | DFB4A6661B28512500A7D4EF /* XPQRotateMenuTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DFB4A6651B28512500A7D4EF /* XPQRotateMenuTests.m */; }; 17 | DFB4A6721B28524F00A7D4EF /* XPQRotateMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = DFB4A6711B28524F00A7D4EF /* XPQRotateMenu.m */; }; 18 | DFB4A6751B28534500A7D4EF /* XPQRotateItem.m in Sources */ = {isa = PBXBuildFile; fileRef = DFB4A6741B28534500A7D4EF /* XPQRotateItem.m */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXContainerItemProxy section */ 22 | DFB4A6601B28512500A7D4EF /* PBXContainerItemProxy */ = { 23 | isa = PBXContainerItemProxy; 24 | containerPortal = DFB4A63E1B28512500A7D4EF /* Project object */; 25 | proxyType = 1; 26 | remoteGlobalIDString = DFB4A6451B28512500A7D4EF; 27 | remoteInfo = XPQRotateMenu; 28 | }; 29 | /* End PBXContainerItemProxy section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | DFB4A6461B28512500A7D4EF /* XPQRotateMenu.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = XPQRotateMenu.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | DFB4A64A1B28512500A7D4EF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 34 | DFB4A64B1B28512500A7D4EF /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 35 | DFB4A64D1B28512500A7D4EF /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 36 | DFB4A64E1B28512500A7D4EF /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 37 | DFB4A6501B28512500A7D4EF /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 38 | DFB4A6511B28512500A7D4EF /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 39 | DFB4A6541B28512500A7D4EF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 40 | DFB4A6561B28512500A7D4EF /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 41 | DFB4A6591B28512500A7D4EF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 42 | DFB4A65F1B28512500A7D4EF /* XPQRotateMenuTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = XPQRotateMenuTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 43 | DFB4A6641B28512500A7D4EF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 44 | DFB4A6651B28512500A7D4EF /* XPQRotateMenuTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XPQRotateMenuTests.m; sourceTree = ""; }; 45 | DFB4A6701B28524F00A7D4EF /* XPQRotateMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XPQRotateMenu.h; sourceTree = ""; }; 46 | DFB4A6711B28524F00A7D4EF /* XPQRotateMenu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XPQRotateMenu.m; sourceTree = ""; }; 47 | DFB4A6731B28534500A7D4EF /* XPQRotateItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XPQRotateItem.h; sourceTree = ""; }; 48 | DFB4A6741B28534500A7D4EF /* XPQRotateItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XPQRotateItem.m; sourceTree = ""; }; 49 | E46E3A2D1B760265004C63A2 /* XPQRotateMenuDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XPQRotateMenuDelegate.h; sourceTree = ""; }; 50 | /* End PBXFileReference section */ 51 | 52 | /* Begin PBXFrameworksBuildPhase section */ 53 | DFB4A6431B28512500A7D4EF /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | ); 58 | runOnlyForDeploymentPostprocessing = 0; 59 | }; 60 | DFB4A65C1B28512500A7D4EF /* Frameworks */ = { 61 | isa = PBXFrameworksBuildPhase; 62 | buildActionMask = 2147483647; 63 | files = ( 64 | ); 65 | runOnlyForDeploymentPostprocessing = 0; 66 | }; 67 | /* End PBXFrameworksBuildPhase section */ 68 | 69 | /* Begin PBXGroup section */ 70 | DFB4A63D1B28512500A7D4EF = { 71 | isa = PBXGroup; 72 | children = ( 73 | DFB4A6481B28512500A7D4EF /* XPQRotateMenu */, 74 | DFB4A6621B28512500A7D4EF /* XPQRotateMenuTests */, 75 | DFB4A6471B28512500A7D4EF /* Products */, 76 | ); 77 | sourceTree = ""; 78 | }; 79 | DFB4A6471B28512500A7D4EF /* Products */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | DFB4A6461B28512500A7D4EF /* XPQRotateMenu.app */, 83 | DFB4A65F1B28512500A7D4EF /* XPQRotateMenuTests.xctest */, 84 | ); 85 | name = Products; 86 | sourceTree = ""; 87 | }; 88 | DFB4A6481B28512500A7D4EF /* XPQRotateMenu */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | DFB4A66F1B28517300A7D4EF /* XPQRotateMenu */, 92 | DFB4A64D1B28512500A7D4EF /* AppDelegate.h */, 93 | DFB4A64E1B28512500A7D4EF /* AppDelegate.m */, 94 | DFB4A6501B28512500A7D4EF /* ViewController.h */, 95 | DFB4A6511B28512500A7D4EF /* ViewController.m */, 96 | DFB4A6531B28512500A7D4EF /* Main.storyboard */, 97 | DFB4A6561B28512500A7D4EF /* Images.xcassets */, 98 | DFB4A6581B28512500A7D4EF /* LaunchScreen.xib */, 99 | DFB4A6491B28512500A7D4EF /* Supporting Files */, 100 | ); 101 | path = XPQRotateMenu; 102 | sourceTree = ""; 103 | }; 104 | DFB4A6491B28512500A7D4EF /* Supporting Files */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | DFB4A64A1B28512500A7D4EF /* Info.plist */, 108 | DFB4A64B1B28512500A7D4EF /* main.m */, 109 | ); 110 | name = "Supporting Files"; 111 | sourceTree = ""; 112 | }; 113 | DFB4A6621B28512500A7D4EF /* XPQRotateMenuTests */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | DFB4A6651B28512500A7D4EF /* XPQRotateMenuTests.m */, 117 | DFB4A6631B28512500A7D4EF /* Supporting Files */, 118 | ); 119 | path = XPQRotateMenuTests; 120 | sourceTree = ""; 121 | }; 122 | DFB4A6631B28512500A7D4EF /* Supporting Files */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | DFB4A6641B28512500A7D4EF /* Info.plist */, 126 | ); 127 | name = "Supporting Files"; 128 | sourceTree = ""; 129 | }; 130 | DFB4A66F1B28517300A7D4EF /* XPQRotateMenu */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | DFB4A6701B28524F00A7D4EF /* XPQRotateMenu.h */, 134 | DFB4A6711B28524F00A7D4EF /* XPQRotateMenu.m */, 135 | DFB4A6731B28534500A7D4EF /* XPQRotateItem.h */, 136 | DFB4A6741B28534500A7D4EF /* XPQRotateItem.m */, 137 | E46E3A2D1B760265004C63A2 /* XPQRotateMenuDelegate.h */, 138 | ); 139 | name = XPQRotateMenu; 140 | sourceTree = ""; 141 | }; 142 | /* End PBXGroup section */ 143 | 144 | /* Begin PBXNativeTarget section */ 145 | DFB4A6451B28512500A7D4EF /* XPQRotateMenu */ = { 146 | isa = PBXNativeTarget; 147 | buildConfigurationList = DFB4A6691B28512500A7D4EF /* Build configuration list for PBXNativeTarget "XPQRotateMenu" */; 148 | buildPhases = ( 149 | DFB4A6421B28512500A7D4EF /* Sources */, 150 | DFB4A6431B28512500A7D4EF /* Frameworks */, 151 | DFB4A6441B28512500A7D4EF /* Resources */, 152 | ); 153 | buildRules = ( 154 | ); 155 | dependencies = ( 156 | ); 157 | name = XPQRotateMenu; 158 | productName = XPQRotateMenu; 159 | productReference = DFB4A6461B28512500A7D4EF /* XPQRotateMenu.app */; 160 | productType = "com.apple.product-type.application"; 161 | }; 162 | DFB4A65E1B28512500A7D4EF /* XPQRotateMenuTests */ = { 163 | isa = PBXNativeTarget; 164 | buildConfigurationList = DFB4A66C1B28512500A7D4EF /* Build configuration list for PBXNativeTarget "XPQRotateMenuTests" */; 165 | buildPhases = ( 166 | DFB4A65B1B28512500A7D4EF /* Sources */, 167 | DFB4A65C1B28512500A7D4EF /* Frameworks */, 168 | DFB4A65D1B28512500A7D4EF /* Resources */, 169 | ); 170 | buildRules = ( 171 | ); 172 | dependencies = ( 173 | DFB4A6611B28512500A7D4EF /* PBXTargetDependency */, 174 | ); 175 | name = XPQRotateMenuTests; 176 | productName = XPQRotateMenuTests; 177 | productReference = DFB4A65F1B28512500A7D4EF /* XPQRotateMenuTests.xctest */; 178 | productType = "com.apple.product-type.bundle.unit-test"; 179 | }; 180 | /* End PBXNativeTarget section */ 181 | 182 | /* Begin PBXProject section */ 183 | DFB4A63E1B28512500A7D4EF /* Project object */ = { 184 | isa = PBXProject; 185 | attributes = { 186 | LastUpgradeCheck = 0630; 187 | ORGANIZATIONNAME = "谢攀琪"; 188 | TargetAttributes = { 189 | DFB4A6451B28512500A7D4EF = { 190 | CreatedOnToolsVersion = 6.3.2; 191 | }; 192 | DFB4A65E1B28512500A7D4EF = { 193 | CreatedOnToolsVersion = 6.3.2; 194 | TestTargetID = DFB4A6451B28512500A7D4EF; 195 | }; 196 | }; 197 | }; 198 | buildConfigurationList = DFB4A6411B28512500A7D4EF /* Build configuration list for PBXProject "XPQRotateMenu" */; 199 | compatibilityVersion = "Xcode 3.2"; 200 | developmentRegion = English; 201 | hasScannedForEncodings = 0; 202 | knownRegions = ( 203 | en, 204 | Base, 205 | ); 206 | mainGroup = DFB4A63D1B28512500A7D4EF; 207 | productRefGroup = DFB4A6471B28512500A7D4EF /* Products */; 208 | projectDirPath = ""; 209 | projectRoot = ""; 210 | targets = ( 211 | DFB4A6451B28512500A7D4EF /* XPQRotateMenu */, 212 | DFB4A65E1B28512500A7D4EF /* XPQRotateMenuTests */, 213 | ); 214 | }; 215 | /* End PBXProject section */ 216 | 217 | /* Begin PBXResourcesBuildPhase section */ 218 | DFB4A6441B28512500A7D4EF /* Resources */ = { 219 | isa = PBXResourcesBuildPhase; 220 | buildActionMask = 2147483647; 221 | files = ( 222 | DFB4A6551B28512500A7D4EF /* Main.storyboard in Resources */, 223 | DFB4A65A1B28512500A7D4EF /* LaunchScreen.xib in Resources */, 224 | DFB4A6571B28512500A7D4EF /* Images.xcassets in Resources */, 225 | ); 226 | runOnlyForDeploymentPostprocessing = 0; 227 | }; 228 | DFB4A65D1B28512500A7D4EF /* Resources */ = { 229 | isa = PBXResourcesBuildPhase; 230 | buildActionMask = 2147483647; 231 | files = ( 232 | ); 233 | runOnlyForDeploymentPostprocessing = 0; 234 | }; 235 | /* End PBXResourcesBuildPhase section */ 236 | 237 | /* Begin PBXSourcesBuildPhase section */ 238 | DFB4A6421B28512500A7D4EF /* Sources */ = { 239 | isa = PBXSourcesBuildPhase; 240 | buildActionMask = 2147483647; 241 | files = ( 242 | DFB4A6521B28512500A7D4EF /* ViewController.m in Sources */, 243 | DFB4A6751B28534500A7D4EF /* XPQRotateItem.m in Sources */, 244 | DFB4A64F1B28512500A7D4EF /* AppDelegate.m in Sources */, 245 | DFB4A64C1B28512500A7D4EF /* main.m in Sources */, 246 | DFB4A6721B28524F00A7D4EF /* XPQRotateMenu.m in Sources */, 247 | ); 248 | runOnlyForDeploymentPostprocessing = 0; 249 | }; 250 | DFB4A65B1B28512500A7D4EF /* Sources */ = { 251 | isa = PBXSourcesBuildPhase; 252 | buildActionMask = 2147483647; 253 | files = ( 254 | DFB4A6661B28512500A7D4EF /* XPQRotateMenuTests.m in Sources */, 255 | ); 256 | runOnlyForDeploymentPostprocessing = 0; 257 | }; 258 | /* End PBXSourcesBuildPhase section */ 259 | 260 | /* Begin PBXTargetDependency section */ 261 | DFB4A6611B28512500A7D4EF /* PBXTargetDependency */ = { 262 | isa = PBXTargetDependency; 263 | target = DFB4A6451B28512500A7D4EF /* XPQRotateMenu */; 264 | targetProxy = DFB4A6601B28512500A7D4EF /* PBXContainerItemProxy */; 265 | }; 266 | /* End PBXTargetDependency section */ 267 | 268 | /* Begin PBXVariantGroup section */ 269 | DFB4A6531B28512500A7D4EF /* Main.storyboard */ = { 270 | isa = PBXVariantGroup; 271 | children = ( 272 | DFB4A6541B28512500A7D4EF /* Base */, 273 | ); 274 | name = Main.storyboard; 275 | sourceTree = ""; 276 | }; 277 | DFB4A6581B28512500A7D4EF /* LaunchScreen.xib */ = { 278 | isa = PBXVariantGroup; 279 | children = ( 280 | DFB4A6591B28512500A7D4EF /* Base */, 281 | ); 282 | name = LaunchScreen.xib; 283 | sourceTree = ""; 284 | }; 285 | /* End PBXVariantGroup section */ 286 | 287 | /* Begin XCBuildConfiguration section */ 288 | DFB4A6671B28512500A7D4EF /* Debug */ = { 289 | isa = XCBuildConfiguration; 290 | buildSettings = { 291 | ALWAYS_SEARCH_USER_PATHS = NO; 292 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 293 | CLANG_CXX_LIBRARY = "libc++"; 294 | CLANG_ENABLE_MODULES = YES; 295 | CLANG_ENABLE_OBJC_ARC = YES; 296 | CLANG_WARN_BOOL_CONVERSION = YES; 297 | CLANG_WARN_CONSTANT_CONVERSION = YES; 298 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 299 | CLANG_WARN_EMPTY_BODY = YES; 300 | CLANG_WARN_ENUM_CONVERSION = YES; 301 | CLANG_WARN_INT_CONVERSION = YES; 302 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 303 | CLANG_WARN_UNREACHABLE_CODE = YES; 304 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 305 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 306 | COPY_PHASE_STRIP = NO; 307 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 308 | ENABLE_STRICT_OBJC_MSGSEND = YES; 309 | GCC_C_LANGUAGE_STANDARD = gnu99; 310 | GCC_DYNAMIC_NO_PIC = NO; 311 | GCC_NO_COMMON_BLOCKS = YES; 312 | GCC_OPTIMIZATION_LEVEL = 0; 313 | GCC_PREPROCESSOR_DEFINITIONS = ( 314 | "DEBUG=1", 315 | "$(inherited)", 316 | ); 317 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 318 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 319 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 320 | GCC_WARN_UNDECLARED_SELECTOR = YES; 321 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 322 | GCC_WARN_UNUSED_FUNCTION = YES; 323 | GCC_WARN_UNUSED_VARIABLE = YES; 324 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 325 | MTL_ENABLE_DEBUG_INFO = YES; 326 | ONLY_ACTIVE_ARCH = YES; 327 | SDKROOT = iphoneos; 328 | TARGETED_DEVICE_FAMILY = "1,2"; 329 | }; 330 | name = Debug; 331 | }; 332 | DFB4A6681B28512500A7D4EF /* Release */ = { 333 | isa = XCBuildConfiguration; 334 | buildSettings = { 335 | ALWAYS_SEARCH_USER_PATHS = NO; 336 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 337 | CLANG_CXX_LIBRARY = "libc++"; 338 | CLANG_ENABLE_MODULES = YES; 339 | CLANG_ENABLE_OBJC_ARC = YES; 340 | CLANG_WARN_BOOL_CONVERSION = YES; 341 | CLANG_WARN_CONSTANT_CONVERSION = YES; 342 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 343 | CLANG_WARN_EMPTY_BODY = YES; 344 | CLANG_WARN_ENUM_CONVERSION = YES; 345 | CLANG_WARN_INT_CONVERSION = YES; 346 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 347 | CLANG_WARN_UNREACHABLE_CODE = YES; 348 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 349 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 350 | COPY_PHASE_STRIP = NO; 351 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 352 | ENABLE_NS_ASSERTIONS = NO; 353 | ENABLE_STRICT_OBJC_MSGSEND = YES; 354 | GCC_C_LANGUAGE_STANDARD = gnu99; 355 | GCC_NO_COMMON_BLOCKS = YES; 356 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 357 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 358 | GCC_WARN_UNDECLARED_SELECTOR = YES; 359 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 360 | GCC_WARN_UNUSED_FUNCTION = YES; 361 | GCC_WARN_UNUSED_VARIABLE = YES; 362 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 363 | MTL_ENABLE_DEBUG_INFO = NO; 364 | SDKROOT = iphoneos; 365 | TARGETED_DEVICE_FAMILY = "1,2"; 366 | VALIDATE_PRODUCT = YES; 367 | }; 368 | name = Release; 369 | }; 370 | DFB4A66A1B28512500A7D4EF /* Debug */ = { 371 | isa = XCBuildConfiguration; 372 | buildSettings = { 373 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 374 | INFOPLIST_FILE = XPQRotateMenu/Info.plist; 375 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 376 | PRODUCT_NAME = "$(TARGET_NAME)"; 377 | }; 378 | name = Debug; 379 | }; 380 | DFB4A66B1B28512500A7D4EF /* Release */ = { 381 | isa = XCBuildConfiguration; 382 | buildSettings = { 383 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 384 | INFOPLIST_FILE = XPQRotateMenu/Info.plist; 385 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 386 | PRODUCT_NAME = "$(TARGET_NAME)"; 387 | }; 388 | name = Release; 389 | }; 390 | DFB4A66D1B28512500A7D4EF /* Debug */ = { 391 | isa = XCBuildConfiguration; 392 | buildSettings = { 393 | BUNDLE_LOADER = "$(TEST_HOST)"; 394 | FRAMEWORK_SEARCH_PATHS = ( 395 | "$(SDKROOT)/Developer/Library/Frameworks", 396 | "$(inherited)", 397 | ); 398 | GCC_PREPROCESSOR_DEFINITIONS = ( 399 | "DEBUG=1", 400 | "$(inherited)", 401 | ); 402 | INFOPLIST_FILE = XPQRotateMenuTests/Info.plist; 403 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 404 | PRODUCT_NAME = "$(TARGET_NAME)"; 405 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/XPQRotateMenu.app/XPQRotateMenu"; 406 | }; 407 | name = Debug; 408 | }; 409 | DFB4A66E1B28512500A7D4EF /* Release */ = { 410 | isa = XCBuildConfiguration; 411 | buildSettings = { 412 | BUNDLE_LOADER = "$(TEST_HOST)"; 413 | FRAMEWORK_SEARCH_PATHS = ( 414 | "$(SDKROOT)/Developer/Library/Frameworks", 415 | "$(inherited)", 416 | ); 417 | INFOPLIST_FILE = XPQRotateMenuTests/Info.plist; 418 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 419 | PRODUCT_NAME = "$(TARGET_NAME)"; 420 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/XPQRotateMenu.app/XPQRotateMenu"; 421 | }; 422 | name = Release; 423 | }; 424 | /* End XCBuildConfiguration section */ 425 | 426 | /* Begin XCConfigurationList section */ 427 | DFB4A6411B28512500A7D4EF /* Build configuration list for PBXProject "XPQRotateMenu" */ = { 428 | isa = XCConfigurationList; 429 | buildConfigurations = ( 430 | DFB4A6671B28512500A7D4EF /* Debug */, 431 | DFB4A6681B28512500A7D4EF /* Release */, 432 | ); 433 | defaultConfigurationIsVisible = 0; 434 | defaultConfigurationName = Release; 435 | }; 436 | DFB4A6691B28512500A7D4EF /* Build configuration list for PBXNativeTarget "XPQRotateMenu" */ = { 437 | isa = XCConfigurationList; 438 | buildConfigurations = ( 439 | DFB4A66A1B28512500A7D4EF /* Debug */, 440 | DFB4A66B1B28512500A7D4EF /* Release */, 441 | ); 442 | defaultConfigurationIsVisible = 0; 443 | defaultConfigurationName = Release; 444 | }; 445 | DFB4A66C1B28512500A7D4EF /* Build configuration list for PBXNativeTarget "XPQRotateMenuTests" */ = { 446 | isa = XCConfigurationList; 447 | buildConfigurations = ( 448 | DFB4A66D1B28512500A7D4EF /* Debug */, 449 | DFB4A66E1B28512500A7D4EF /* Release */, 450 | ); 451 | defaultConfigurationIsVisible = 0; 452 | defaultConfigurationName = Release; 453 | }; 454 | /* End XCConfigurationList section */ 455 | }; 456 | rootObject = DFB4A63E1B28512500A7D4EF /* Project object */; 457 | } 458 | -------------------------------------------------------------------------------- /XPQRotateMenu.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /XPQRotateMenu/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // XPQRotateMenu 4 | // 5 | // Created by 谢攀琪 on 15/6/10. 6 | // Copyright (c) 2015年 谢攀琪. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /XPQRotateMenu/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // XPQRotateMenu 4 | // 5 | // Created by 谢攀琪 on 15/6/10. 6 | // Copyright (c) 2015年 谢攀琪. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // 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. 25 | // 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. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // 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. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // 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. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 38 | // 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. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /XPQRotateMenu/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /XPQRotateMenu/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /XPQRotateMenu/Images.xcassets/1430121054613.imageset/1430121054613.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepanqi/XPQRotateMenu/9255a0686c8afb9b7af6bb6bf19457b845b826c9/XPQRotateMenu/Images.xcassets/1430121054613.imageset/1430121054613.jpg -------------------------------------------------------------------------------- /XPQRotateMenu/Images.xcassets/1430121054613.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "1430121054613.jpg" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /XPQRotateMenu/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" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /XPQRotateMenu/Images.xcassets/cancleIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "cancleIcon.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /XPQRotateMenu/Images.xcassets/cancleIcon.imageset/cancleIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepanqi/XPQRotateMenu/9255a0686c8afb9b7af6bb6bf19457b845b826c9/XPQRotateMenu/Images.xcassets/cancleIcon.imageset/cancleIcon.png -------------------------------------------------------------------------------- /XPQRotateMenu/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.xpq.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /XPQRotateMenu/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // XPQRotateMenu 4 | // 5 | // Created by 谢攀琪 on 15/6/10. 6 | // Copyright (c) 2015年 谢攀琪. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /XPQRotateMenu/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // XPQRotateMenu 4 | // 5 | // Created by 谢攀琪 on 15/6/10. 6 | // Copyright (c) 2015年 谢攀琪. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "XPQRotateMenu.h" 11 | 12 | @interface ViewController () 13 | 14 | @end 15 | 16 | @implementation ViewController 17 | 18 | - (void)viewDidLoad { 19 | [super viewDidLoad]; 20 | // Do any additional setup after loading the view, typically from a nib. 21 | NSMutableAttributedString *attriString = [[NSMutableAttributedString alloc] initWithString:@"我是一个特别的菜单"]; 22 | [attriString addAttribute:NSForegroundColorAttributeName value:[UIColor blueColor] range:NSMakeRange(0,4)]; 23 | [attriString addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(4,3)]; 24 | [attriString addAttribute:NSForegroundColorAttributeName value:[UIColor greenColor] range:NSMakeRange(7,2)]; 25 | [attriString addAttribute:NSFontAttributeName value:[UIFont fontWithName:@"Arial-BoldItalicMT" size:20.0] range:NSMakeRange(0, 4)]; 26 | [attriString addAttribute:NSFontAttributeName value:[UIFont fontWithName:@"HelveticaNeue-Bold" size:20.0] range:NSMakeRange(4, 3)]; 27 | [attriString addAttribute:NSFontAttributeName value:[UIFont fontWithName:@"Courier-BoldOblique" size:20.0] range:NSMakeRange(7, 2)]; 28 | 29 | 30 | NSArray *arr = @[@"菜单1",@"菜单2",@"菜单3",@"菜单4",attriString,@"菜单6",@"菜单7",@"菜单8",@"菜单9"]; 31 | XPQRotateMenu *menu = [[XPQRotateMenu alloc] initWithTitleArray:arr]; 32 | menu.intersectionImage = [UIImage imageNamed:@"cancleIcon"]; 33 | // menu.backgroundColor = [UIColor whiteColor]; 34 | // menu.backgroundImage = [UIImage imageNamed:@"1430121054613"]; 35 | // menu.isUpToTop = NO; 36 | menu.delegate = self; 37 | 38 | [self.view addSubview:menu]; 39 | } 40 | 41 | - (void)didReceiveMemoryWarning { 42 | [super didReceiveMemoryWarning]; 43 | // Dispose of any resources that can be recreated. 44 | } 45 | 46 | -(void)didClickMenuItem:(XPQRotateItem *)menuItem { 47 | NSLog(@"%@", menuItem); 48 | } 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /XPQRotateMenu/XPQRotateItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // XPQRotateItem.h 3 | // XPQRotateMenu 4 | // 5 | // Created by 谢攀琪 on 15/6/10. 6 | // Copyright (c) 2015年 谢攀琪. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #define MenuItemHight 40 12 | #define MenuItemAnchor 20 // MenuItemAnchor = MenuItemHight / 2 13 | 14 | #define MenuItemTag 0x400 15 | 16 | #define ScreenWidth [UIScreen mainScreen].bounds.size.width 17 | #define ScreenHeight [UIScreen mainScreen].bounds.size.height 18 | 19 | @interface XPQRotateItem : UIView 20 | /** 21 | * @brief 初始化菜单项 22 | * @param index 索引 23 | * @param target 目标 24 | * @param action 回调函数 25 | * @return 菜单项指针 26 | */ 27 | -(instancetype) initWithIndex:(NSInteger)index target:(id)target action:(SEL)action; 28 | 29 | /// 菜单项索引 30 | @property (nonatomic, readonly) NSInteger index; 31 | /// 标题 32 | @property (nonatomic) NSString *title; 33 | /// 带属性标题 34 | @property (nonatomic) NSAttributedString *attributedTitle; 35 | /// 阴影方向,YES向上,NO向下 36 | @property (nonatomic) BOOL upShadow; 37 | /// 菜单是否停靠在左侧 38 | @property (nonatomic) BOOL isLeft; 39 | @end 40 | -------------------------------------------------------------------------------- /XPQRotateMenu/XPQRotateItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // XPQRotateItem.m 3 | // XPQRotateMenu 4 | // 5 | // Created by 谢攀琪 on 15/6/10. 6 | // Copyright (c) 2015年 谢攀琪. All rights reserved. 7 | // 8 | 9 | #import "XPQRotateItem.h" 10 | 11 | @interface XPQRotateItem () 12 | @property (nonatomic, weak) UIButton *button; 13 | @end 14 | 15 | @implementation XPQRotateItem 16 | 17 | -(instancetype)initWithIndex:(NSInteger)index target:(id)target action:(SEL)action { 18 | CGFloat itemWidth = [UIScreen mainScreen].bounds.size.height / 2; 19 | self = [super initWithFrame:CGRectMake(0, 0, itemWidth, MenuItemHight)]; 20 | if (self) { 21 | self.backgroundColor = [UIColor whiteColor]; 22 | self.layer.cornerRadius = 5; 23 | self.layer.anchorPoint = CGPointMake(MenuItemAnchor / ScreenWidth, 0.5); 24 | 25 | // 阴影效果 26 | self.layer.shouldRasterize = YES; 27 | self.layer.shadowColor = [UIColor grayColor].CGColor; 28 | self.layer.shadowOffset = CGSizeMake(-2, 2); 29 | self.layer.shadowOpacity = 0.9; 30 | 31 | _isLeft = YES; 32 | UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(MenuItemHight + 10, 0, self.frame.size.width - 70, self.frame.size.height)]; 33 | button.tag = MenuItemTag + index; 34 | [button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; 35 | button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentRight; 36 | [button addTarget:target action:action forControlEvents:UIControlEventTouchUpInside]; 37 | self.button = button; 38 | [self addSubview:button]; 39 | } 40 | 41 | return self; 42 | } 43 | 44 | -(NSInteger)index { 45 | return self.button.tag - MenuItemTag; 46 | } 47 | 48 | -(NSString *)title { 49 | return [self.button titleForState:UIControlStateNormal]; 50 | } 51 | 52 | -(void)setTitle:(NSString *)title { 53 | [self.button setTitle:title forState:UIControlStateNormal]; 54 | } 55 | 56 | -(NSAttributedString *)attributedTitle { 57 | return [self.button attributedTitleForState:UIControlStateNormal]; 58 | } 59 | 60 | -(void)setAttributedTitle:(NSAttributedString *)attributedTitle { 61 | [self.button setAttributedTitle:attributedTitle forState:UIControlStateNormal]; 62 | } 63 | 64 | -(void)setUpShadow:(BOOL)upShadow { 65 | _upShadow = upShadow; 66 | self.layer.shadowOffset = CGSizeMake(-2, upShadow ? 2 : -2); 67 | } 68 | 69 | -(void)setIsLeft:(BOOL)isLeft { 70 | _isLeft = isLeft; 71 | if (isLeft) { 72 | self.button.frame = CGRectMake(MenuItemHight + 10, 0, self.frame.size.width - 70, self.frame.size.height); 73 | self.button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentRight; 74 | self.layer.anchorPoint = CGPointMake(MenuItemAnchor / ScreenWidth, 0.5); 75 | } 76 | else { 77 | self.button.frame = CGRectMake(20, 0, self.frame.size.width - 70, self.frame.size.height); 78 | self.button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft; 79 | self.layer.anchorPoint = CGPointMake(1 - MenuItemAnchor / ScreenWidth, 0.5); 80 | } 81 | } 82 | 83 | -(NSString *)description { 84 | NSString *title = self.title; 85 | NSString *attTitle = self.attributedTitle.string; 86 | NSString *returnStr; 87 | if (title == nil || [title isEqualToString:@""]) { 88 | returnStr = [NSString stringWithFormat:@"attTitle:%@ index:%ld", attTitle, self.index]; 89 | } 90 | else { 91 | returnStr = [NSString stringWithFormat:@"title:%@ index:%ld", title, self.index]; 92 | } 93 | return returnStr; 94 | } 95 | @end 96 | -------------------------------------------------------------------------------- /XPQRotateMenu/XPQRotateMenu.h: -------------------------------------------------------------------------------- 1 | // 2 | // XPQRotateMenu.h 3 | // XPQRotateMenu 4 | // 5 | // Created by 谢攀琪 on 15/6/10. 6 | // Copyright (c) 2015年 谢攀琪. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "XPQRotateItem.h" 11 | #import "XPQRotateMenuDelegate.h" 12 | 13 | /** 14 | 菜单吸附位置类型。 15 | */ 16 | typedef enum : NSUInteger { 17 | XPQRotateMenuDependPositionLeft, 18 | XPQRotateMenuDependPositionLeftDown, 19 | XPQRotateMenuDependPositionLeftUp, 20 | XPQRotateMenuDependPositionRight, 21 | XPQRotateMenuDependPositionRightDown, 22 | XPQRotateMenuDependPositionRightUp, 23 | } XPQRotateMenuDependPosition; 24 | 25 | @interface XPQRotateMenu : UIView 26 | /// 动画时间,默认0.75 27 | @property (nonatomic) NSTimeInterval time; 28 | /// 展开时背景色,默认r:0.5 g:0.5 r:0.5 a:1.0 29 | @property (nonatomic) UIColor *backgroundColor; 30 | /// 是否展开 31 | @property (nonatomic, readonly, getter=isExpand) BOOL expand; 32 | /// 是否上方菜单放上面 33 | @property (nonatomic) BOOL isUpToTop; 34 | /// 交汇处按钮图片 35 | @property (nonatomic) UIImage *intersectionImage; 36 | /// 背景图片,默认nil 37 | @property (nonatomic) UIImage *backgroundImage; 38 | /// 菜单显示时是向上方向,默认YES 39 | @property (nonatomic) BOOL showClockwise; 40 | /// 菜单隐藏时是向上方向,默认NO 41 | @property (nonatomic) BOOL hideClockwise; 42 | /// 启用上下滑动手势隐藏菜单,默认YES 43 | @property (nonatomic) BOOL handleHideEnable; 44 | /// 菜单吸附位置 45 | @property (nonatomic) XPQRotateMenuDependPosition dependPosition; 46 | /// 代理 47 | @property (nonatomic, weak) id delegate; 48 | 49 | /** 50 | * @brief 根据一个数组来创建菜单项 51 | * @param array 要创建的菜单项标题,可以是NSString或者NSAttributedString 52 | * @return 创建的对象 53 | */ 54 | -(instancetype)initWithTitleArray:(NSArray *)array; 55 | 56 | /** 57 | * @brief 显示菜单 58 | * @param isClockwise 旋转方向,YES-顺时针,NO-逆时钟 59 | * @return void 60 | */ 61 | -(void)showMenu:(BOOL)isClockwise; 62 | 63 | /** 64 | * @brief 隐藏菜单 65 | * @param isClockwise 旋转方向,YES-顺时针,NO-逆时钟 66 | * @return void 67 | */ 68 | -(void)hideMenu:(BOOL)isClockwise; 69 | 70 | /** 71 | * @brief 根据索引返回对应的菜单项,如果index大于菜单数则返回nil 72 | * @param index 要查找的索引 73 | * @return 索引对应的菜单项 74 | */ 75 | -(XPQRotateItem *)menuItemWithIndex:(NSInteger)index; 76 | @end 77 | -------------------------------------------------------------------------------- /XPQRotateMenu/XPQRotateMenu.m: -------------------------------------------------------------------------------- 1 | // 2 | // XPQRotateMenu.m 3 | // XPQRotateMenu 4 | // 5 | // Created by 谢攀琪 on 15/6/10. 6 | // Copyright (c) 2015年 谢攀琪. All rights reserved. 7 | // 8 | 9 | #import "XPQRotateMenu.h" 10 | 11 | #define hideWith 25 12 | 13 | 14 | @interface XPQRotateMenu () 15 | @property (nonatomic) NSMutableArray *menuItemArray; 16 | @property (nonatomic, weak) UIButton *intersection; 17 | @property (nonatomic, weak) UIImageView *backgroundImageView; 18 | @end 19 | 20 | @implementation XPQRotateMenu 21 | 22 | #pragma mark - 初始化 23 | - (instancetype)init 24 | { 25 | self = [super init]; 26 | if (self) { 27 | [self configSelf]; 28 | } 29 | return self; 30 | } 31 | 32 | - (instancetype)initWithCoder:(NSCoder *)coder 33 | { 34 | self = [super initWithCoder:coder]; 35 | if (self) { 36 | [self configSelf]; 37 | } 38 | return self; 39 | } 40 | 41 | - (instancetype)initWithFrame:(CGRect)frame 42 | { 43 | self = [super initWithFrame:frame]; 44 | if (self) { 45 | [self configSelf]; 46 | } 47 | return self; 48 | } 49 | 50 | -(instancetype)initWithTitleArray:(NSArray *)array { 51 | self = [super init]; 52 | if (self) { 53 | [self configSelf]; 54 | [self configSubview:array]; 55 | } 56 | return self; 57 | } 58 | 59 | -(void)configSelf { 60 | self.bounds = CGRectMake(0, 0, MenuItemHight, MenuItemHight); 61 | self.menuItemArray = [NSMutableArray array]; 62 | self.time = 0.75; 63 | self.isUpToTop = YES; 64 | self.backgroundColor = [UIColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:1.0]; 65 | super.backgroundColor = [self.backgroundColor colorWithAlphaComponent:0.0]; 66 | self.showClockwise = YES; 67 | self.hideClockwise = NO; 68 | self.handleHideEnable = YES; 69 | self.dependPosition = XPQRotateMenuDependPositionLeft; 70 | _expand = NO; 71 | 72 | // 上滑响应 73 | UISwipeGestureRecognizer *upRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipeFrom:)]; 74 | upRecognizer.direction = UISwipeGestureRecognizerDirectionUp; 75 | [self addGestureRecognizer:upRecognizer]; 76 | // 下滑响应 77 | UISwipeGestureRecognizer *downRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipeFrom:)]; 78 | downRecognizer.direction = UISwipeGestureRecognizerDirectionDown; 79 | [self addGestureRecognizer:downRecognizer]; 80 | } 81 | 82 | -(void)configSubview:(NSArray *)title { 83 | if (title.count > 1) { 84 | for (NSInteger i = 0; i < title.count; i++) { 85 | XPQRotateItem *menuItem = [[XPQRotateItem alloc] initWithIndex:i target:self action:@selector(actionMenuItem:)]; 86 | // 如果是字符 87 | if ([title[i] isKindOfClass:[NSString class]]) { 88 | menuItem.title = title[i]; 89 | } 90 | // 如果是富文本 91 | else if ([title[i] isKindOfClass:[NSAttributedString class]]) { 92 | menuItem.attributedTitle = title[i]; 93 | } 94 | // 如果是图片 95 | else if ([title[i] isKindOfClass:[UIImage class]]) { 96 | 97 | } 98 | 99 | menuItem.transform = CGAffineTransformMakeRotation(M_PI); 100 | menuItem.center = CGPointMake(20, 20); 101 | 102 | 103 | if (self.isUpToTop) { 104 | [self insertSubview:menuItem atIndex:0]; 105 | } 106 | else { 107 | [self addSubview:menuItem]; 108 | } 109 | [self.menuItemArray addObject:menuItem]; 110 | } 111 | 112 | 113 | UIButton *intersection = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 30, 30)]; 114 | intersection.center = CGPointMake(20, 20); 115 | [intersection setTitle:@"➕" forState:(UIControlStateNormal)]; 116 | [intersection addTarget:self action:@selector(actionIntersection:) forControlEvents:(UIControlEventTouchUpInside)]; 117 | self.intersection = intersection; 118 | [self addSubview:intersection]; 119 | } 120 | } 121 | 122 | -(NSString *)description { 123 | return [NSString stringWithFormat:@"isExpand:%@ menuItem:%@", _expand ? @"YES" : @"NO", self.menuItemArray]; 124 | } 125 | 126 | #pragma mark - 属性 127 | -(void)setIntersectionImage:(UIImage *)intersectionImage { 128 | if (intersectionImage == nil) { 129 | [self.intersection setTitle:@"➕" forState:UIControlStateNormal]; 130 | } 131 | else { 132 | [self.intersection setTitle:@"" forState:UIControlStateNormal]; 133 | } 134 | [self.intersection setImage:intersectionImage forState:UIControlStateNormal]; 135 | } 136 | 137 | -(UIImage *)intersectionImage { 138 | return [self.intersection imageForState:UIControlStateNormal]; 139 | } 140 | 141 | -(void)setBackgroundImage:(UIImage *)backgroundImage { 142 | _backgroundImage = backgroundImage; 143 | if (_backgroundImageView == nil) { 144 | UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectInset([UIScreen mainScreen].bounds, -20.0, 0.0)]; 145 | [self insertSubview:imageView atIndex:0]; 146 | _backgroundImageView = imageView; 147 | } 148 | _backgroundImageView.alpha = _expand ? 1.0 : 0.0; 149 | _backgroundImageView.image = _backgroundImage; 150 | } 151 | 152 | -(void)setIsUpToTop:(BOOL)isUpToTop { 153 | _isUpToTop = isUpToTop; 154 | if (isUpToTop) { 155 | for (XPQRotateItem *item in self.menuItemArray) { 156 | [self sendSubviewToBack:item]; 157 | item.upShadow = isUpToTop; 158 | } 159 | } 160 | else { 161 | for (NSUInteger i = self.menuItemArray.count - 1; i < self.menuItemArray.count; i--) { 162 | [self sendSubviewToBack:self.menuItemArray[i]]; 163 | ((XPQRotateItem*)self.menuItemArray[i]).upShadow = isUpToTop; 164 | } 165 | } 166 | if (self.backgroundImageView) { 167 | [self sendSubviewToBack:self.backgroundImageView]; 168 | } 169 | } 170 | 171 | -(void)setDependPosition:(XPQRotateMenuDependPosition)dependPosition { 172 | _dependPosition = dependPosition; 173 | if (dependPosition < XPQRotateMenuDependPositionRight) { 174 | self.center = CGPointMake(5, ScreenHeight / 2); 175 | for (XPQRotateItem *item in self.menuItemArray) { 176 | item.isLeft = YES; 177 | } 178 | } 179 | else { 180 | self.center = CGPointMake(ScreenWidth - 5, ScreenHeight / 2); 181 | for (XPQRotateItem *item in self.menuItemArray) { 182 | item.isLeft = NO; 183 | } 184 | } 185 | } 186 | 187 | #pragma mark - 上下滑动 188 | -(void)handleSwipeFrom:(UISwipeGestureRecognizer *)recognizer { 189 | if (self.handleHideEnable) { 190 | if (self.dependPosition < XPQRotateMenuDependPositionRight) { 191 | [self hideMenu:(recognizer.direction == UISwipeGestureRecognizerDirectionDown)]; 192 | } 193 | else { 194 | [self hideMenu:(recognizer.direction == UISwipeGestureRecognizerDirectionUp)]; 195 | } 196 | } 197 | } 198 | 199 | #pragma mark - 点击 200 | -(void)actionMenuItem:(XPQRotateItem *)sender { 201 | if ([self.delegate respondsToSelector:@selector(shouldClickMenuItem:)]) { 202 | if ([self.delegate shouldClickMenuItem:self.menuItemArray[sender.tag - MenuItemTag]] == NO) { 203 | return; 204 | } 205 | } 206 | 207 | [self hideMenu:self.hideClockwise]; 208 | 209 | if ([self.delegate respondsToSelector:@selector(didClickMenuItem:)]) { 210 | [self.delegate didClickMenuItem:self.menuItemArray[sender.tag - MenuItemTag]]; 211 | } 212 | } 213 | 214 | -(void)actionIntersection:(UIButton *)sender { 215 | if ([self.delegate respondsToSelector:@selector(shouldClickIntersection)]) { 216 | if ([self.delegate shouldClickIntersection] == NO) { 217 | return; 218 | } 219 | } 220 | 221 | if (self.expand) { 222 | [self hideMenu:self.hideClockwise]; 223 | } 224 | else { 225 | [self showMenu:self.showClockwise]; 226 | } 227 | 228 | if ([self.delegate respondsToSelector:@selector(didClickIntersection)]) { 229 | [self.delegate didClickIntersection]; 230 | } 231 | } 232 | 233 | #pragma mark - 菜单显示隐藏 234 | -(void)showMenu:(BOOL)isClockwise { 235 | if ([self.delegate respondsToSelector:@selector(shouldShowMenu)]) { 236 | if ([self.delegate shouldShowMenu] == NO) { 237 | return; 238 | } 239 | } 240 | 241 | // 先判断是否已经展开了 242 | if (!self.expand && self.menuItemArray.count > 1) { 243 | _expand = YES; 244 | // 交汇按钮转动 245 | [self rotateButtounAnimation:isClockwise]; 246 | // 显示背景 247 | [self showBackgroundAnimation]; 248 | // 转动菜单 249 | [self showMenuItemAnimation:isClockwise]; 250 | } 251 | 252 | if ([self.delegate respondsToSelector:@selector(didShowMenu)]) { 253 | [self.delegate didShowMenu]; 254 | } 255 | } 256 | 257 | -(void)hideMenu:(BOOL)isClockwise { 258 | if ([self.delegate respondsToSelector:@selector(shouldHideMenu)]) { 259 | if ([self.delegate shouldHideMenu] == NO) { 260 | return; 261 | } 262 | } 263 | 264 | // 先判断是否已经隐藏了 265 | if (self.expand && self.menuItemArray.count > 1) { 266 | _expand = NO; 267 | // 交汇按钮转动 268 | [self rotateButtounAnimation:isClockwise]; 269 | // 隐藏背景 270 | [self hideBackgroundAnimation]; 271 | // 转动菜单 272 | [self hideMenuItemAnimation:isClockwise]; 273 | } 274 | 275 | if ([self.delegate respondsToSelector:@selector(didHideMenu)]) { 276 | [self.delegate didHideMenu]; 277 | } 278 | } 279 | 280 | -(XPQRotateItem *)menuItemWithIndex:(NSInteger)index { 281 | return self.menuItemArray[index]; 282 | } 283 | 284 | #pragma mark -动画 285 | /** 286 | * @brief 菜单项显示时的转动动画 287 | * @param isClockwise 旋转方向,YES-顺时针,NO-逆时钟 288 | */ 289 | -(void)showMenuItemAnimation:(BOOL)isClockwise { 290 | CGFloat unitAngle = M_PI / (self.menuItemArray.count - 1) * 2 / 3; 291 | CGFloat angle = -M_PI / 3; 292 | // 右侧处理 293 | if (self.dependPosition >= XPQRotateMenuDependPositionRight) { 294 | unitAngle *= -1; 295 | angle += M_PI * 2 / 3; 296 | } 297 | // 逆时针处理 298 | if (!isClockwise) { 299 | angle -= (2 * M_PI); 300 | } 301 | for (XPQRotateItem *item in self.menuItemArray) { 302 | item.transform = CGAffineTransformMakeRotation(angle); 303 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; 304 | animation.fromValue = [NSNumber numberWithFloat:-angle - M_PI]; 305 | animation.toValue = [NSNumber numberWithFloat:0]; 306 | animation.duration = self.time; 307 | animation.cumulative = YES; 308 | animation.additive = YES; 309 | animation.delegate = self; 310 | [item.layer addAnimation:animation forKey:@"rotationMenuItem"]; 311 | angle += unitAngle; 312 | } 313 | } 314 | 315 | /** 316 | * @brief 菜单项隐藏时的转动动画 317 | * @param isUp 旋转方向,YES-顺时针,NO-逆时钟 318 | */ 319 | -(void)hideMenuItemAnimation:(BOOL)isClockwise { 320 | CGFloat unitAngle = M_PI / (self.menuItemArray.count - 1) * 2 / 3; 321 | CGFloat angle = (self.menuItemArray.count) * unitAngle - M_PI * 1 / 3 + M_PI_4; 322 | // 右侧处理 323 | if (self.dependPosition >= XPQRotateMenuDependPositionRight) { 324 | unitAngle *= -1; 325 | angle += M_PI * 2 / 3; 326 | } 327 | // 逆时针处理 328 | if (isClockwise) { 329 | angle -= (2 * M_PI); 330 | } 331 | for (XPQRotateItem *item in self.menuItemArray) { 332 | item.transform = CGAffineTransformMakeRotation(-M_PI); 333 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; 334 | animation.fromValue = [NSNumber numberWithFloat: angle]; 335 | animation.toValue = [NSNumber numberWithFloat:0]; 336 | animation.duration = self.time; 337 | animation.cumulative = YES; 338 | animation.additive = YES; 339 | animation.delegate = self; 340 | animation.removedOnCompletion = NO; 341 | animation.fillMode = kCAFillModeForwards; 342 | [item.layer addAnimation:animation forKey:@"rotationMenuItem"]; 343 | angle += unitAngle; 344 | } 345 | } 346 | 347 | /** 348 | * @brief 背景显示动画 349 | */ 350 | -(void)showBackgroundAnimation { 351 | CGFloat backgroudMoveSize = 0; 352 | CGFloat buttonPositionX = 0; 353 | if (self.dependPosition < XPQRotateMenuDependPositionRight) { 354 | backgroudMoveSize = 20; 355 | buttonPositionX = 25; 356 | } 357 | else { 358 | backgroudMoveSize = -20; 359 | buttonPositionX = ScreenWidth + 15; 360 | } 361 | // 先把背景铺满全屏, 362 | self.frame = CGRectMake(-20, 0, ScreenWidth + 40, ScreenHeight); 363 | // 和调整好按钮的位置 364 | self.intersection.center = CGPointMake(buttonPositionX, self.frame.size.height / 2); 365 | for (UIView *item in self.menuItemArray) { 366 | item.center = CGPointMake(buttonPositionX, self.frame.size.height / 2); 367 | } 368 | // 慢慢显示背景,并向右移动使菜单完全显示 369 | [UIView beginAnimations:@"showBackgroundAnimation" context:nil]; 370 | [UIView setAnimationDuration:self.time]; 371 | // 整体像右移20像素 372 | self.center = CGPointMake(self.center.x + backgroudMoveSize, self.center.y); 373 | if (self.backgroundImageView != nil) { 374 | // 为了让背景看起来没有变化,所以反方向移动20像素 375 | self.backgroundImageView.center = CGPointMake(self.backgroundImageView.center.x - backgroudMoveSize, self.backgroundImageView.center.y); 376 | self.backgroundImageView.alpha = 1.0; 377 | } 378 | // 设置背景色 379 | super.backgroundColor = self.backgroundColor; 380 | [UIView commitAnimations]; 381 | } 382 | 383 | /** 384 | * @brief 背景隐藏动画 385 | */ 386 | -(void)hideBackgroundAnimation { 387 | CGFloat moveSize = 0; 388 | if (self.dependPosition < XPQRotateMenuDependPositionRight) { 389 | moveSize = 20; 390 | } 391 | else { 392 | moveSize = -20; 393 | } 394 | // 慢慢隐藏背景,并向左移动使部分菜单被遮掩 395 | [UIView beginAnimations:@"hideBackgroundAnimation" context:nil]; 396 | [UIView setAnimationDuration:self.time]; 397 | [UIView setAnimationDelegate:self]; 398 | self.center = CGPointMake(self.center.x - moveSize, self.center.y); 399 | if (self.backgroundImageView != nil) { 400 | self.backgroundImageView.center = CGPointMake(self.backgroundImageView.center.x + moveSize, self.backgroundImageView.center.y); 401 | self.backgroundImageView.alpha = 0.0; 402 | } 403 | super.backgroundColor = [self.backgroundColor colorWithAlphaComponent:0.0]; 404 | [UIView commitAnimations]; 405 | } 406 | 407 | -(void)rotateButtounAnimation:(BOOL)isClockwise { 408 | [UIView beginAnimations:@"test" context:nil]; 409 | [UIView setAnimationDuration:self.time]; 410 | // 交汇按钮转动 411 | if (isClockwise) { 412 | self.intersection.transform = CGAffineTransformRotate(self.intersection.transform, M_PI_4); 413 | } 414 | else { 415 | self.intersection.transform = CGAffineTransformRotate(self.intersection.transform, -M_PI_4); 416 | } 417 | [UIView commitAnimations]; 418 | } 419 | 420 | -(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { 421 | // 隐藏背景动画结束后把背景视图缩小 422 | if ([anim isKindOfClass:[NSString class]]) { 423 | if ([(NSString*)anim isEqualToString:@"hideBackgroundAnimation"]) { 424 | self.bounds = CGRectMake(0, 0, MenuItemHight, MenuItemHight); 425 | if (self.dependPosition < XPQRotateMenuDependPositionRight) { 426 | self.center = CGPointMake(5, ScreenHeight / 2); 427 | } 428 | else { 429 | self.center = CGPointMake(ScreenWidth - 5, ScreenHeight / 2); 430 | } 431 | // 让菜单项和按钮看上去位置不变化 432 | self.intersection.center = CGPointMake(20, self.frame.size.height / 2); 433 | for (UIView *item in self.menuItemArray) { 434 | item.center = CGPointMake(20, self.frame.size.height / 2); 435 | } 436 | } 437 | } 438 | else { 439 | // CGFloat angle = M_PI / (self.menuItemArray.count - 1) * 2 / 3; 440 | // for (NSInteger i = self.menuItemArray.count - 1; i >= 0; i--) { 441 | // XPQRotateItem *menuItem = self.menuItemArray[i]; 442 | // menuItem.transform = CGAffineTransformMakeRotation(angle * i - M_PI / 3); 443 | // } 444 | // for (XPQRotateItem *item in self.menuItemArray) { 445 | // NSLog(@"%@", item); 446 | // } 447 | } 448 | } 449 | @end 450 | -------------------------------------------------------------------------------- /XPQRotateMenu/XPQRotateMenuDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // XPQRotateMenuDelegate.h 3 | // XPQRotateMenu 4 | // 5 | // Created by RHC on 15/8/8. 6 | // Copyright (c) 2015年 谢攀琪. All rights reserved. 7 | // 8 | 9 | @protocol XPQRotateMenuDelegate 10 | @optional 11 | 12 | /** 13 | * @brief 菜单即将显示,可以通过返回值来控制是否显示 14 | * @return YES-菜单继续显示,NO-菜单取消显示 15 | */ 16 | -(BOOL)shouldShowMenu; 17 | 18 | /** 19 | * @brief 菜单显示完成 20 | */ 21 | -(void)didShowMenu; 22 | 23 | /** 24 | * @brief 菜单即将隐藏,可以通过返回值来控制是否隐藏 25 | * @return YES-菜单继续隐藏,NO-菜单取消隐藏 26 | */ 27 | -(BOOL)shouldHideMenu; 28 | 29 | /** 30 | * @brief 菜单隐藏完成 31 | */ 32 | -(void)didHideMenu; 33 | 34 | /** 35 | * @brief 点击交汇处按钮,即将执行后续操作,可以通过返回值取消操作 36 | * @return YES-继续执行,NO-取消执行 37 | */ 38 | -(BOOL)shouldClickIntersection; 39 | 40 | /** 41 | * @brief 点击交汇处按钮后续操作完成 42 | */ 43 | -(void)didClickIntersection; 44 | 45 | /** 46 | * @brief 点击菜单项,即将执行后续操作,可以通过返回值取消操作 47 | * @param menuItem 点击的菜单项 48 | * @return YES-继续执行,NO-取消执行 49 | */ 50 | -(BOOL)shouldClickMenuItem:(XPQRotateItem *)menuItem; 51 | 52 | /** 53 | * @brief 点击菜单项后续操作完成 54 | * @param menuItem 点击的菜单项 55 | */ 56 | -(void)didClickMenuItem:(XPQRotateItem *)menuItem; 57 | 58 | /** 59 | * @brief 点击背景 60 | */ 61 | -(void)didClickBackground; 62 | @end 63 | 64 | -------------------------------------------------------------------------------- /XPQRotateMenu/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // XPQRotateMenu 4 | // 5 | // Created by 谢攀琪 on 15/6/10. 6 | // Copyright (c) 2015年 谢攀琪. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /XPQRotateMenuTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.xpq.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /XPQRotateMenuTests/XPQRotateMenuTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // XPQRotateMenuTests.m 3 | // XPQRotateMenuTests 4 | // 5 | // Created by 谢攀琪 on 15/6/10. 6 | // Copyright (c) 2015年 谢攀琪. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface XPQRotateMenuTests : XCTestCase 13 | 14 | @end 15 | 16 | @implementation XPQRotateMenuTests 17 | 18 | - (void)setUp { 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 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | [super tearDown]; 26 | } 27 | 28 | - (void)testExample { 29 | // This is an example of a functional test case. 30 | XCTAssert(YES, @"Pass"); 31 | } 32 | 33 | - (void)testPerformanceExample { 34 | // This is an example of a performance test case. 35 | [self measureBlock:^{ 36 | // Put the code you want to measure the time of here. 37 | }]; 38 | } 39 | 40 | @end 41 | --------------------------------------------------------------------------------