├── .DS_Store ├── .gitignore ├── LICENSE ├── README.md ├── YDMenu.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata └── YDMenu ├── AppDelegate.swift ├── Assets.xcassets ├── 0.imageset │ ├── 0@2x.png │ ├── 0@3x.png │ └── Contents.json ├── 1.imageset │ ├── 1@2x.png │ ├── 1@3x.png │ └── Contents.json ├── 2.imageset │ ├── 2@2x.png │ ├── 2@3x.png │ └── Contents.json ├── 3.imageset │ ├── 3@2x.png │ ├── 3@3x.png │ └── Contents.json ├── 4.imageset │ ├── 4@2x.png │ ├── 4@3x.png │ └── Contents.json ├── 5.imageset │ ├── 5@2x.png │ ├── 5@3x.png │ └── Contents.json ├── 6.imageset │ ├── 6@2x.png │ ├── 6@3x.png │ └── Contents.json ├── 7.imageset │ ├── 7@2x.png │ ├── 7@3x.png │ └── Contents.json ├── 8.imageset │ ├── 8@2x.png │ ├── 8@3x.png │ └── Contents.json ├── 9.imageset │ ├── 9@2x.png │ ├── 9@3x.png │ └── Contents.json ├── AppIcon.appiconset │ └── Contents.json ├── Contents.json └── home_4.imageset │ ├── Contents.json │ ├── home_4.png │ ├── home_4@2x.png │ └── home_4@3x.png ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── Info.plist ├── MenuData.plist ├── ViewController.swift └── YDMenu ├── YDMenu.swift ├── YDMenuDataSource.swift └── YDMenuDelegate.swift /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YandaZhao/YDMenu/cba7bb47ac6e5f7a972d0e55c27b83db8e0dc7bb/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | .build/ 41 | 42 | # CocoaPods 43 | # 44 | # We recommend against adding the Pods directory to your .gitignore. However 45 | # you should judge for yourself, the pros and cons are mentioned at: 46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 47 | # 48 | # Pods/ 49 | 50 | # Carthage 51 | # 52 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 53 | # Carthage/Checkouts 54 | 55 | Carthage/Build 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 63 | 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots 67 | fastlane/test_output 68 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Yanda Zhao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YDMenu 2 | 3 | 话不多说上效果 4 | 5 | ![](http://zhaoyanda-git.oss-cn-beijing.aliyuncs.com/YDMenu.gif) 6 | 7 | 网上类似的功能很多, 但大部分都是OC的, 最近新立的项目都是Swift写的, OC和Swift混编大家懂得, 所以干脆自己用Swift写了一个. 8 | 9 | 使用方式也是很简单, Demo中注释也比较全, 用法很类似于`UITableView`. 10 | 具体使用方式详见Demo 11 | 12 | 13 | -------------------------------------------------------------------------------- /YDMenu.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | E29173E720562CCF00210FAE /* MenuData.plist in Resources */ = {isa = PBXBuildFile; fileRef = E29173E620562CCF00210FAE /* MenuData.plist */; }; 11 | E2D458642050C9CC00E55CA2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2D458632050C9CC00E55CA2 /* AppDelegate.swift */; }; 12 | E2D458662050C9CC00E55CA2 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2D458652050C9CC00E55CA2 /* ViewController.swift */; }; 13 | E2D458692050C9CC00E55CA2 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E2D458672050C9CC00E55CA2 /* Main.storyboard */; }; 14 | E2D4586B2050C9CC00E55CA2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E2D4586A2050C9CC00E55CA2 /* Assets.xcassets */; }; 15 | E2D4586E2050C9CC00E55CA2 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E2D4586C2050C9CC00E55CA2 /* LaunchScreen.storyboard */; }; 16 | E2D458772050CB5A00E55CA2 /* YDMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2D458762050CB5A00E55CA2 /* YDMenu.swift */; }; 17 | E2D4587B2050CBBF00E55CA2 /* YDMenuDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2D4587A2050CBBE00E55CA2 /* YDMenuDataSource.swift */; }; 18 | E2D4587D2050CBD700E55CA2 /* YDMenuDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2D4587C2050CBD700E55CA2 /* YDMenuDelegate.swift */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | E29173E620562CCF00210FAE /* MenuData.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = MenuData.plist; sourceTree = ""; }; 23 | E2D458602050C9CC00E55CA2 /* YDMenu.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = YDMenu.app; sourceTree = BUILT_PRODUCTS_DIR; }; 24 | E2D458632050C9CC00E55CA2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 25 | E2D458652050C9CC00E55CA2 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 26 | E2D458682050C9CC00E55CA2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 27 | E2D4586A2050C9CC00E55CA2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 28 | E2D4586D2050C9CC00E55CA2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 29 | E2D4586F2050C9CC00E55CA2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 30 | E2D458762050CB5A00E55CA2 /* YDMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YDMenu.swift; sourceTree = ""; }; 31 | E2D4587A2050CBBE00E55CA2 /* YDMenuDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YDMenuDataSource.swift; sourceTree = ""; }; 32 | E2D4587C2050CBD700E55CA2 /* YDMenuDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YDMenuDelegate.swift; sourceTree = ""; }; 33 | /* End PBXFileReference section */ 34 | 35 | /* Begin PBXFrameworksBuildPhase section */ 36 | E2D4585D2050C9CC00E55CA2 /* Frameworks */ = { 37 | isa = PBXFrameworksBuildPhase; 38 | buildActionMask = 2147483647; 39 | files = ( 40 | ); 41 | runOnlyForDeploymentPostprocessing = 0; 42 | }; 43 | /* End PBXFrameworksBuildPhase section */ 44 | 45 | /* Begin PBXGroup section */ 46 | E2D458572050C9CC00E55CA2 = { 47 | isa = PBXGroup; 48 | children = ( 49 | E2D458622050C9CC00E55CA2 /* YDMenu */, 50 | E2D458612050C9CC00E55CA2 /* Products */, 51 | ); 52 | sourceTree = ""; 53 | }; 54 | E2D458612050C9CC00E55CA2 /* Products */ = { 55 | isa = PBXGroup; 56 | children = ( 57 | E2D458602050C9CC00E55CA2 /* YDMenu.app */, 58 | ); 59 | name = Products; 60 | sourceTree = ""; 61 | }; 62 | E2D458622050C9CC00E55CA2 /* YDMenu */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | E2D458752050CA5200E55CA2 /* YDMenu */, 66 | E2D458632050C9CC00E55CA2 /* AppDelegate.swift */, 67 | E2D458652050C9CC00E55CA2 /* ViewController.swift */, 68 | E29173E620562CCF00210FAE /* MenuData.plist */, 69 | E2D458672050C9CC00E55CA2 /* Main.storyboard */, 70 | E2D4586A2050C9CC00E55CA2 /* Assets.xcassets */, 71 | E2D4586C2050C9CC00E55CA2 /* LaunchScreen.storyboard */, 72 | E2D4586F2050C9CC00E55CA2 /* Info.plist */, 73 | ); 74 | path = YDMenu; 75 | sourceTree = ""; 76 | }; 77 | E2D458752050CA5200E55CA2 /* YDMenu */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | E2D458762050CB5A00E55CA2 /* YDMenu.swift */, 81 | E2D4587A2050CBBE00E55CA2 /* YDMenuDataSource.swift */, 82 | E2D4587C2050CBD700E55CA2 /* YDMenuDelegate.swift */, 83 | ); 84 | path = YDMenu; 85 | sourceTree = ""; 86 | }; 87 | /* End PBXGroup section */ 88 | 89 | /* Begin PBXNativeTarget section */ 90 | E2D4585F2050C9CC00E55CA2 /* YDMenu */ = { 91 | isa = PBXNativeTarget; 92 | buildConfigurationList = E2D458722050C9CC00E55CA2 /* Build configuration list for PBXNativeTarget "YDMenu" */; 93 | buildPhases = ( 94 | E2D4585C2050C9CC00E55CA2 /* Sources */, 95 | E2D4585D2050C9CC00E55CA2 /* Frameworks */, 96 | E2D4585E2050C9CC00E55CA2 /* Resources */, 97 | ); 98 | buildRules = ( 99 | ); 100 | dependencies = ( 101 | ); 102 | name = YDMenu; 103 | productName = YDMenu; 104 | productReference = E2D458602050C9CC00E55CA2 /* YDMenu.app */; 105 | productType = "com.apple.product-type.application"; 106 | }; 107 | /* End PBXNativeTarget section */ 108 | 109 | /* Begin PBXProject section */ 110 | E2D458582050C9CC00E55CA2 /* Project object */ = { 111 | isa = PBXProject; 112 | attributes = { 113 | LastSwiftUpdateCheck = 0910; 114 | LastUpgradeCheck = 0910; 115 | ORGANIZATIONNAME = YDZhao; 116 | TargetAttributes = { 117 | E2D4585F2050C9CC00E55CA2 = { 118 | CreatedOnToolsVersion = 9.1; 119 | ProvisioningStyle = Automatic; 120 | }; 121 | }; 122 | }; 123 | buildConfigurationList = E2D4585B2050C9CC00E55CA2 /* Build configuration list for PBXProject "YDMenu" */; 124 | compatibilityVersion = "Xcode 8.0"; 125 | developmentRegion = en; 126 | hasScannedForEncodings = 0; 127 | knownRegions = ( 128 | en, 129 | Base, 130 | ); 131 | mainGroup = E2D458572050C9CC00E55CA2; 132 | productRefGroup = E2D458612050C9CC00E55CA2 /* Products */; 133 | projectDirPath = ""; 134 | projectRoot = ""; 135 | targets = ( 136 | E2D4585F2050C9CC00E55CA2 /* YDMenu */, 137 | ); 138 | }; 139 | /* End PBXProject section */ 140 | 141 | /* Begin PBXResourcesBuildPhase section */ 142 | E2D4585E2050C9CC00E55CA2 /* Resources */ = { 143 | isa = PBXResourcesBuildPhase; 144 | buildActionMask = 2147483647; 145 | files = ( 146 | E2D4586E2050C9CC00E55CA2 /* LaunchScreen.storyboard in Resources */, 147 | E2D4586B2050C9CC00E55CA2 /* Assets.xcassets in Resources */, 148 | E2D458692050C9CC00E55CA2 /* Main.storyboard in Resources */, 149 | E29173E720562CCF00210FAE /* MenuData.plist in Resources */, 150 | ); 151 | runOnlyForDeploymentPostprocessing = 0; 152 | }; 153 | /* End PBXResourcesBuildPhase section */ 154 | 155 | /* Begin PBXSourcesBuildPhase section */ 156 | E2D4585C2050C9CC00E55CA2 /* Sources */ = { 157 | isa = PBXSourcesBuildPhase; 158 | buildActionMask = 2147483647; 159 | files = ( 160 | E2D458662050C9CC00E55CA2 /* ViewController.swift in Sources */, 161 | E2D458772050CB5A00E55CA2 /* YDMenu.swift in Sources */, 162 | E2D4587B2050CBBF00E55CA2 /* YDMenuDataSource.swift in Sources */, 163 | E2D4587D2050CBD700E55CA2 /* YDMenuDelegate.swift in Sources */, 164 | E2D458642050C9CC00E55CA2 /* AppDelegate.swift in Sources */, 165 | ); 166 | runOnlyForDeploymentPostprocessing = 0; 167 | }; 168 | /* End PBXSourcesBuildPhase section */ 169 | 170 | /* Begin PBXVariantGroup section */ 171 | E2D458672050C9CC00E55CA2 /* Main.storyboard */ = { 172 | isa = PBXVariantGroup; 173 | children = ( 174 | E2D458682050C9CC00E55CA2 /* Base */, 175 | ); 176 | name = Main.storyboard; 177 | sourceTree = ""; 178 | }; 179 | E2D4586C2050C9CC00E55CA2 /* LaunchScreen.storyboard */ = { 180 | isa = PBXVariantGroup; 181 | children = ( 182 | E2D4586D2050C9CC00E55CA2 /* Base */, 183 | ); 184 | name = LaunchScreen.storyboard; 185 | sourceTree = ""; 186 | }; 187 | /* End PBXVariantGroup section */ 188 | 189 | /* Begin XCBuildConfiguration section */ 190 | E2D458702050C9CC00E55CA2 /* Debug */ = { 191 | isa = XCBuildConfiguration; 192 | buildSettings = { 193 | ALWAYS_SEARCH_USER_PATHS = NO; 194 | CLANG_ANALYZER_NONNULL = YES; 195 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 196 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 197 | CLANG_CXX_LIBRARY = "libc++"; 198 | CLANG_ENABLE_MODULES = YES; 199 | CLANG_ENABLE_OBJC_ARC = YES; 200 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 201 | CLANG_WARN_BOOL_CONVERSION = YES; 202 | CLANG_WARN_COMMA = YES; 203 | CLANG_WARN_CONSTANT_CONVERSION = YES; 204 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 205 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 206 | CLANG_WARN_EMPTY_BODY = YES; 207 | CLANG_WARN_ENUM_CONVERSION = YES; 208 | CLANG_WARN_INFINITE_RECURSION = YES; 209 | CLANG_WARN_INT_CONVERSION = YES; 210 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 211 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 212 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 213 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 214 | CLANG_WARN_STRICT_PROTOTYPES = YES; 215 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 216 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 217 | CLANG_WARN_UNREACHABLE_CODE = YES; 218 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 219 | CODE_SIGN_IDENTITY = "iPhone Developer"; 220 | COPY_PHASE_STRIP = NO; 221 | DEBUG_INFORMATION_FORMAT = dwarf; 222 | ENABLE_STRICT_OBJC_MSGSEND = YES; 223 | ENABLE_TESTABILITY = YES; 224 | GCC_C_LANGUAGE_STANDARD = gnu11; 225 | GCC_DYNAMIC_NO_PIC = NO; 226 | GCC_NO_COMMON_BLOCKS = YES; 227 | GCC_OPTIMIZATION_LEVEL = 0; 228 | GCC_PREPROCESSOR_DEFINITIONS = ( 229 | "DEBUG=1", 230 | "$(inherited)", 231 | ); 232 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 233 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 234 | GCC_WARN_UNDECLARED_SELECTOR = YES; 235 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 236 | GCC_WARN_UNUSED_FUNCTION = YES; 237 | GCC_WARN_UNUSED_VARIABLE = YES; 238 | IPHONEOS_DEPLOYMENT_TARGET = 11.1; 239 | MTL_ENABLE_DEBUG_INFO = YES; 240 | ONLY_ACTIVE_ARCH = YES; 241 | SDKROOT = iphoneos; 242 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 243 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 244 | }; 245 | name = Debug; 246 | }; 247 | E2D458712050C9CC00E55CA2 /* Release */ = { 248 | isa = XCBuildConfiguration; 249 | buildSettings = { 250 | ALWAYS_SEARCH_USER_PATHS = NO; 251 | CLANG_ANALYZER_NONNULL = YES; 252 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 253 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 254 | CLANG_CXX_LIBRARY = "libc++"; 255 | CLANG_ENABLE_MODULES = YES; 256 | CLANG_ENABLE_OBJC_ARC = YES; 257 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 258 | CLANG_WARN_BOOL_CONVERSION = YES; 259 | CLANG_WARN_COMMA = YES; 260 | CLANG_WARN_CONSTANT_CONVERSION = YES; 261 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 262 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 263 | CLANG_WARN_EMPTY_BODY = YES; 264 | CLANG_WARN_ENUM_CONVERSION = YES; 265 | CLANG_WARN_INFINITE_RECURSION = YES; 266 | CLANG_WARN_INT_CONVERSION = YES; 267 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 268 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 269 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 270 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 271 | CLANG_WARN_STRICT_PROTOTYPES = YES; 272 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 273 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 274 | CLANG_WARN_UNREACHABLE_CODE = YES; 275 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 276 | CODE_SIGN_IDENTITY = "iPhone Developer"; 277 | COPY_PHASE_STRIP = NO; 278 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 279 | ENABLE_NS_ASSERTIONS = NO; 280 | ENABLE_STRICT_OBJC_MSGSEND = YES; 281 | GCC_C_LANGUAGE_STANDARD = gnu11; 282 | GCC_NO_COMMON_BLOCKS = YES; 283 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 284 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 285 | GCC_WARN_UNDECLARED_SELECTOR = YES; 286 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 287 | GCC_WARN_UNUSED_FUNCTION = YES; 288 | GCC_WARN_UNUSED_VARIABLE = YES; 289 | IPHONEOS_DEPLOYMENT_TARGET = 11.1; 290 | MTL_ENABLE_DEBUG_INFO = NO; 291 | SDKROOT = iphoneos; 292 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 293 | VALIDATE_PRODUCT = YES; 294 | }; 295 | name = Release; 296 | }; 297 | E2D458732050C9CC00E55CA2 /* Debug */ = { 298 | isa = XCBuildConfiguration; 299 | buildSettings = { 300 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 301 | CODE_SIGN_STYLE = Automatic; 302 | INFOPLIST_FILE = YDMenu/Info.plist; 303 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 304 | PRODUCT_BUNDLE_IDENTIFIER = com.zhaoyanda.YDMenu; 305 | PRODUCT_NAME = "$(TARGET_NAME)"; 306 | SWIFT_VERSION = 4.0; 307 | TARGETED_DEVICE_FAMILY = "1,2"; 308 | }; 309 | name = Debug; 310 | }; 311 | E2D458742050C9CC00E55CA2 /* Release */ = { 312 | isa = XCBuildConfiguration; 313 | buildSettings = { 314 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 315 | CODE_SIGN_STYLE = Automatic; 316 | INFOPLIST_FILE = YDMenu/Info.plist; 317 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 318 | PRODUCT_BUNDLE_IDENTIFIER = com.zhaoyanda.YDMenu; 319 | PRODUCT_NAME = "$(TARGET_NAME)"; 320 | SWIFT_VERSION = 4.0; 321 | TARGETED_DEVICE_FAMILY = "1,2"; 322 | }; 323 | name = Release; 324 | }; 325 | /* End XCBuildConfiguration section */ 326 | 327 | /* Begin XCConfigurationList section */ 328 | E2D4585B2050C9CC00E55CA2 /* Build configuration list for PBXProject "YDMenu" */ = { 329 | isa = XCConfigurationList; 330 | buildConfigurations = ( 331 | E2D458702050C9CC00E55CA2 /* Debug */, 332 | E2D458712050C9CC00E55CA2 /* Release */, 333 | ); 334 | defaultConfigurationIsVisible = 0; 335 | defaultConfigurationName = Release; 336 | }; 337 | E2D458722050C9CC00E55CA2 /* Build configuration list for PBXNativeTarget "YDMenu" */ = { 338 | isa = XCConfigurationList; 339 | buildConfigurations = ( 340 | E2D458732050C9CC00E55CA2 /* Debug */, 341 | E2D458742050C9CC00E55CA2 /* Release */, 342 | ); 343 | defaultConfigurationIsVisible = 0; 344 | defaultConfigurationName = Release; 345 | }; 346 | /* End XCConfigurationList section */ 347 | }; 348 | rootObject = E2D458582050C9CC00E55CA2 /* Project object */; 349 | } 350 | -------------------------------------------------------------------------------- /YDMenu.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /YDMenu/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // YDMenu 4 | // 5 | // Created by ZJXN on 2018/3/8. 6 | // Copyright © 2018年 YDZhao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // 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. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // 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. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // 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. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/0.imageset/0@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YandaZhao/YDMenu/cba7bb47ac6e5f7a972d0e55c27b83db8e0dc7bb/YDMenu/Assets.xcassets/0.imageset/0@2x.png -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/0.imageset/0@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YandaZhao/YDMenu/cba7bb47ac6e5f7a972d0e55c27b83db8e0dc7bb/YDMenu/Assets.xcassets/0.imageset/0@3x.png -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/0.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "0@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "0@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/1.imageset/1@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YandaZhao/YDMenu/cba7bb47ac6e5f7a972d0e55c27b83db8e0dc7bb/YDMenu/Assets.xcassets/1.imageset/1@2x.png -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/1.imageset/1@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YandaZhao/YDMenu/cba7bb47ac6e5f7a972d0e55c27b83db8e0dc7bb/YDMenu/Assets.xcassets/1.imageset/1@3x.png -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/2.imageset/2@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YandaZhao/YDMenu/cba7bb47ac6e5f7a972d0e55c27b83db8e0dc7bb/YDMenu/Assets.xcassets/2.imageset/2@2x.png -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/2.imageset/2@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YandaZhao/YDMenu/cba7bb47ac6e5f7a972d0e55c27b83db8e0dc7bb/YDMenu/Assets.xcassets/2.imageset/2@3x.png -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "2@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "2@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/3.imageset/3@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YandaZhao/YDMenu/cba7bb47ac6e5f7a972d0e55c27b83db8e0dc7bb/YDMenu/Assets.xcassets/3.imageset/3@2x.png -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/3.imageset/3@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YandaZhao/YDMenu/cba7bb47ac6e5f7a972d0e55c27b83db8e0dc7bb/YDMenu/Assets.xcassets/3.imageset/3@3x.png -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "3@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "3@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/4.imageset/4@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YandaZhao/YDMenu/cba7bb47ac6e5f7a972d0e55c27b83db8e0dc7bb/YDMenu/Assets.xcassets/4.imageset/4@2x.png -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/4.imageset/4@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YandaZhao/YDMenu/cba7bb47ac6e5f7a972d0e55c27b83db8e0dc7bb/YDMenu/Assets.xcassets/4.imageset/4@3x.png -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "4@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "4@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/5.imageset/5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YandaZhao/YDMenu/cba7bb47ac6e5f7a972d0e55c27b83db8e0dc7bb/YDMenu/Assets.xcassets/5.imageset/5@2x.png -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/5.imageset/5@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YandaZhao/YDMenu/cba7bb47ac6e5f7a972d0e55c27b83db8e0dc7bb/YDMenu/Assets.xcassets/5.imageset/5@3x.png -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/5.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "5@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "5@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/6.imageset/6@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YandaZhao/YDMenu/cba7bb47ac6e5f7a972d0e55c27b83db8e0dc7bb/YDMenu/Assets.xcassets/6.imageset/6@2x.png -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/6.imageset/6@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YandaZhao/YDMenu/cba7bb47ac6e5f7a972d0e55c27b83db8e0dc7bb/YDMenu/Assets.xcassets/6.imageset/6@3x.png -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/6.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "6@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "6@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/7.imageset/7@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YandaZhao/YDMenu/cba7bb47ac6e5f7a972d0e55c27b83db8e0dc7bb/YDMenu/Assets.xcassets/7.imageset/7@2x.png -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/7.imageset/7@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YandaZhao/YDMenu/cba7bb47ac6e5f7a972d0e55c27b83db8e0dc7bb/YDMenu/Assets.xcassets/7.imageset/7@3x.png -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/7.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "7@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "7@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/8.imageset/8@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YandaZhao/YDMenu/cba7bb47ac6e5f7a972d0e55c27b83db8e0dc7bb/YDMenu/Assets.xcassets/8.imageset/8@2x.png -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/8.imageset/8@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YandaZhao/YDMenu/cba7bb47ac6e5f7a972d0e55c27b83db8e0dc7bb/YDMenu/Assets.xcassets/8.imageset/8@3x.png -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/8.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "8@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "8@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/9.imageset/9@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YandaZhao/YDMenu/cba7bb47ac6e5f7a972d0e55c27b83db8e0dc7bb/YDMenu/Assets.xcassets/9.imageset/9@2x.png -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/9.imageset/9@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YandaZhao/YDMenu/cba7bb47ac6e5f7a972d0e55c27b83db8e0dc7bb/YDMenu/Assets.xcassets/9.imageset/9@3x.png -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/9.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "9@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "9@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/home_4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "home_4.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "home_4@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "home_4@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/home_4.imageset/home_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YandaZhao/YDMenu/cba7bb47ac6e5f7a972d0e55c27b83db8e0dc7bb/YDMenu/Assets.xcassets/home_4.imageset/home_4.png -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/home_4.imageset/home_4@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YandaZhao/YDMenu/cba7bb47ac6e5f7a972d0e55c27b83db8e0dc7bb/YDMenu/Assets.xcassets/home_4.imageset/home_4@2x.png -------------------------------------------------------------------------------- /YDMenu/Assets.xcassets/home_4.imageset/home_4@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YandaZhao/YDMenu/cba7bb47ac6e5f7a972d0e55c27b83db8e0dc7bb/YDMenu/Assets.xcassets/home_4.imageset/home_4@3x.png -------------------------------------------------------------------------------- /YDMenu/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /YDMenu/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 28 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /YDMenu/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /YDMenu/MenuData.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | sift 6 | 7 | 折扣商品 8 | 今年爆款 9 | 下单有礼 10 | 11 | Order 12 | 13 | 人气优先 14 | 价格优先 15 | 品质优先 16 | 评价优先 17 | 18 | Area 19 | 20 | 21 | distance 22 | 23 | 最新潮 24 | 最热闹 25 | 喜欢安静 26 | 不醉不归 27 | 我是麦霸 28 | 29 | name 30 | 推荐地区 31 | 32 | 33 | distance 34 | 35 | 附近 36 | 1 Km 37 | 2 Km 38 | 3 Km 39 | 5 Km 40 | 10 Km 41 | 全部 42 | 43 | name 44 | 昌平区 45 | 46 | 47 | distance 48 | 49 | 附近 50 | 1 Km 51 | 2 Km 52 | 3 Km 53 | 5 Km 54 | 10 Km 55 | 全部 56 | 57 | name 58 | 大兴区 59 | 60 | 61 | distance 62 | 63 | 附近 64 | 1 Km 65 | 2 Km 66 | 3 Km 67 | 5 Km 68 | 10 Km 69 | 全部 70 | 71 | name 72 | 丰台区 73 | 74 | 75 | distance 76 | 77 | 附近 78 | 1 Km 79 | 2 Km 80 | 3 Km 81 | 5 Km 82 | 10 Km 83 | 全部 84 | 85 | name 86 | 海淀区 87 | 88 | 89 | distance 90 | 91 | 附近 92 | 1 Km 93 | 2 Km 94 | 3 Km 95 | 5 Km 96 | 10 Km 97 | 全部 98 | 99 | name 100 | 朝阳区 101 | 102 | 103 | distance 104 | 105 | 附近 106 | 1 Km 107 | 2 Km 108 | 3 Km 109 | 5 Km 110 | 10 Km 111 | 全部 112 | 113 | name 114 | 西城区 115 | 116 | 117 | distance 118 | 119 | 附近 120 | 1 Km 121 | 2 Km 122 | 3 Km 123 | 5 Km 124 | 10 Km 125 | 全部 126 | 127 | name 128 | 东城区 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /YDMenu/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // YDMenu 4 | // 5 | // Created by ZJXN on 2018/3/8. 6 | // Copyright © 2018年 YDZhao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController, YDMenuDataSource, YDMenuDelegate { 12 | 13 | var data = [String: AnyObject]() 14 | 15 | let menu = YDMenu(origin: CGPoint(x: 0, y: 50), menuheight: 40) 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | 21 | let dataUrl = Bundle.main.url(forResource: "MenuData", withExtension: "plist") 22 | 23 | if dataUrl != nil { 24 | data = NSDictionary(contentsOf: dataUrl!)! as! [String : AnyObject] 25 | } 26 | view.backgroundColor = UIColor(white: 0.95, alpha: 1) 27 | 28 | view.addSubview(menu) 29 | menu.delegate = self 30 | menu.dataSource = self 31 | 32 | } 33 | 34 | @IBAction func selectedBtnClick(_ sender: Any) { 35 | 36 | menu.selectedAtIndex(YDMenu.Index(column: 1, row: 2)) 37 | } 38 | 39 | @IBAction func selectedDefaultBtnClick(_ sender: Any) { 40 | 41 | menu.selectDeafult() 42 | } 43 | 44 | override func didReceiveMemoryWarning() { 45 | super.didReceiveMemoryWarning() 46 | // Dispose of any resources that can be recreated. 47 | } 48 | 49 | // MARK: - YDMenuDataSource / Delegate 50 | 51 | func numberOfColumnsInMenu(_ menu: YDMenu) -> Int { 52 | return data.count 53 | } 54 | 55 | func menu(_ menu: YDMenu, numberOfRowsInColumn column: Int) -> Int { 56 | 57 | switch column { 58 | case 0: 59 | return (data["Area"] as! [[String: AnyObject]]).count 60 | case 1: 61 | return (data["Order"] as! [String]).count 62 | case 2: 63 | return (data["sift"] as! [String]).count 64 | default: 65 | return 0 66 | } 67 | } 68 | 69 | func menu(_ menu: YDMenu, numberOfItemsInRow row: Int, inColumn column: Int) -> Int { 70 | if column == 0 { 71 | return ((data["Area"] as! [[String: AnyObject]])[row]["distance"] as! [String]).count 72 | } 73 | return 0 74 | } 75 | 76 | func menu(_ menu: YDMenu, titleForItemsInRowAtIndexPath indexPath: YDMenu.Index) -> String { 77 | 78 | 79 | switch indexPath.column { 80 | 81 | case 0: 82 | return ((data["Area"] as! [[String: AnyObject]])[indexPath.row]["distance"] as! [String])[indexPath.item] 83 | 84 | default: 85 | return "" 86 | } 87 | 88 | 89 | } 90 | 91 | func menu(_ menu: YDMenu, titleForRowAtIndexPath indexPath: YDMenu.Index) -> String { 92 | 93 | switch indexPath.column { 94 | case 0: 95 | return (data["Area"] as! [[String: AnyObject]])[indexPath.row]["name"] as! String 96 | case 1: 97 | return (data["Order"] as! [String])[indexPath.row] 98 | case 2: 99 | return (data["sift"] as! [String])[indexPath.row] 100 | default: 101 | return "" 102 | } 103 | } 104 | 105 | func menu(_ menu: YDMenu, imageNameForRowAtIndexPath indexPath: YDMenu.Index) -> String? { 106 | if indexPath.column == 0 || indexPath.column == 2 { 107 | return (arc4random() % 10).description 108 | } 109 | return nil 110 | } 111 | 112 | func menu(_ menu: YDMenu, detailTextForRowAtIndexPath indexPath: YDMenu.Index) -> String? { 113 | 114 | let random = arc4random() % 100 115 | return random.description 116 | } 117 | 118 | func menu(_ menu: YDMenu, detailTextForItemsInRowAtIndexPath indexPath: YDMenu.Index) -> String? { 119 | let random = arc4random() % 1000 120 | return random.description 121 | } 122 | 123 | 124 | func menu(_ menu: YDMenu, didSelectRowAtIndexPath indexPath: YDMenu.Index) { 125 | 126 | print("选中了第\(indexPath.column)列, 一级列表的第\(indexPath.row)行\(indexPath.haveItem ? ", 二级列表的第\(indexPath.item)行" : ", 没有选择二级列表")") 127 | } 128 | 129 | } 130 | 131 | 132 | -------------------------------------------------------------------------------- /YDMenu/YDMenu/YDMenu.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YDMenu.swift 3 | // YDMenu 4 | // 5 | // Created by ZJXN on 2018/3/8. 6 | // Copyright © 2018年 YDZhao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | private let kScreenWidth = UIScreen.main.bounds.width 12 | private let kScreenHeight = UIScreen.main.bounds.height 13 | private let kScreenScale = UIScreen.main.scale 14 | private let kBottomHeight = CGFloat(UIApplication.shared.statusBarFrame.size.height > 20 ? 34 : 0) 15 | 16 | private let kAnimationDuration = 0.2 17 | 18 | class YDMenu: UIView { 19 | 20 | /// 用于描述菜单中的下标 21 | public struct Index { 22 | /// 列 23 | var column: Int 24 | /// 行 25 | var row: Int 26 | /// 行的子行 27 | var item: Int 28 | /// 是否有item 29 | var haveItem:Bool { 30 | return item != -1 31 | } 32 | 33 | init(column: Int, row: Int, item: Int = -1) { 34 | self.column = column 35 | self.row = row 36 | self.item = item 37 | } 38 | } 39 | 40 | // MARK: - 属性 41 | // Public 42 | weak var delegate: YDMenuDelegate? 43 | weak var dataSource: YDMenuDataSource? { 44 | didSet{ 45 | if oldValue === dataSource { 46 | 47 | return 48 | } 49 | didSetDataSource(ds: dataSource!) 50 | } 51 | } 52 | var textColor: UIColor = UIColor(red: 51/255.0, green: 51/255.0, blue: 51/255.0, alpha: 1) 53 | var selectedTextColor: UIColor = UIColor(red: 246/255.0, green: 79/255.0, blue: 0/255.0, alpha: 1) 54 | var detailTextColor: UIColor = UIColor(red: 51/255.0, green: 51/255.0, blue: 51/255.0, alpha: 1) 55 | var indicatorColor = UIColor(red: 51/255.0, green: 51/255.0, blue: 51/255.0, alpha: 1) 56 | var detailTextFontSize: CGFloat = 11 57 | var separatorColor = UIColor(red: 219/255.0, green: 219/255.0, blue: 219/255.0, alpha: 1) 58 | var fontSize:CGFloat = 14 59 | var tableViewHeight: CGFloat = 300 60 | var cellHeight: CGFloat = 44 61 | // Private 62 | private var menuOrigin: CGPoint 63 | private var menuHeight: CGFloat 64 | private var numberOfColumn = 0 //列数 65 | private var isShow: Bool = false 66 | private var currentSelectedColumn = -1 67 | private var currentSelectedRows = [Int]() 68 | 69 | private var currentTitleLayers = [CATextLayer]() 70 | private var currentIndicatorLayers = [CAShapeLayer]() 71 | private var currentBgLayers = [CALayer]() 72 | 73 | private lazy var backGroundView: UIView = { 74 | let view = UIView(frame: CGRect(x: menuOrigin.x, y: menuOrigin.y, width: kScreenWidth, height: kScreenHeight)) 75 | view.backgroundColor = UIColor(white: 0, alpha: 0) 76 | view.isOpaque = false 77 | 78 | view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(backTapped(sender:)))) 79 | return view 80 | }() 81 | 82 | private lazy var leftTableView: UITableView = { 83 | let view = UITableView(frame: CGRect(x: menuOrigin.x, y: menuOrigin.y + menuHeight, width: kScreenWidth / 2, height: 0)) 84 | view.dataSource = self; 85 | view.delegate = self; 86 | view.rowHeight = cellHeight 87 | view.backgroundColor = UIColor(white: 0.99, alpha: 1) 88 | // view.tableFooterView = UIView() 89 | view.separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) 90 | view.separatorColor = separatorColor 91 | return view 92 | }() 93 | 94 | private lazy var rightTableView: UITableView = { 95 | let view = UITableView(frame: CGRect(x: menuOrigin.x + kScreenWidth / 2, y: menuOrigin.y + menuHeight, width: kScreenWidth / 2, height: 0)) 96 | view.dataSource = self; 97 | view.delegate = self; 98 | view.rowHeight = cellHeight 99 | // view.tableFooterView = UIView() 100 | view.backgroundColor = UIColor(white: 0.985, alpha: 1) 101 | view.separatorColor = separatorColor 102 | view.separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) 103 | return view 104 | }() 105 | 106 | private lazy var bottomLine: UIView = { 107 | let view = UIView(frame: CGRect(x: 0, y: menuHeight - (1 / kScreenScale), width: kScreenWidth, height: 1 / kScreenScale)) 108 | view.backgroundColor = separatorColor 109 | view.isHidden = true 110 | return view 111 | }() 112 | 113 | // MARK: - 初始化方法 114 | init(origin: CGPoint, menuheight height: CGFloat) { 115 | 116 | menuOrigin = origin 117 | menuHeight = height 118 | 119 | super.init(frame: CGRect(x: origin.x, y: origin.y, width: kScreenWidth, height: height)) 120 | 121 | backgroundColor = UIColor.white 122 | addSubview(bottomLine) 123 | 124 | let menuTap = UITapGestureRecognizer(target: self, action: #selector(menuTapped)) 125 | self.addGestureRecognizer(menuTap) 126 | 127 | leftTableView.tableFooterView = UIView() 128 | rightTableView.tableFooterView = UIView() 129 | } 130 | 131 | required public init?(coder aDecoder: NSCoder) { 132 | fatalError("init(coder:) has not been implemented") 133 | } 134 | 135 | private func didSetDataSource(ds: YDMenuDataSource) { 136 | 137 | /// 背景layer 138 | func creatBackgroundLayer(position: CGPoint, backgroundColor: UIColor) -> CALayer { 139 | let layer = CALayer() 140 | layer.position = position 141 | layer.backgroundColor = backgroundColor.cgColor 142 | layer.bounds = CGRect(x: 0, y: 0, width: kScreenWidth / CGFloat(numberOfColumn), height: menuHeight - 1) 143 | return layer 144 | } 145 | /// 标题Layer 146 | func creatTitleLayer(text: String, position: CGPoint, textColor: UIColor) -> CATextLayer { 147 | // size 148 | let textSize = calculateStringSize(text) 149 | let maxWidth = kScreenWidth / CGFloat(numberOfColumn) - 25 150 | let textLayerWidth = (textSize.width < maxWidth) ? textSize.width : maxWidth 151 | // textLayer 152 | let textLayer = CATextLayer() 153 | textLayer.bounds = CGRect(x: 0, y: 0, width: textLayerWidth, height: textSize.height) 154 | textLayer.fontSize = fontSize 155 | textLayer.string = text 156 | textLayer.alignmentMode = kCAAlignmentCenter 157 | textLayer.truncationMode = kCATruncationEnd 158 | textLayer.foregroundColor = textColor.cgColor 159 | textLayer.contentsScale = kScreenScale 160 | textLayer.position = position 161 | return textLayer 162 | } 163 | /// indicatorLayer 164 | func creatIndicatorLayer(position: CGPoint, color: UIColor) -> CAShapeLayer { 165 | // path 166 | let bezierPath = UIBezierPath() 167 | bezierPath.move(to: CGPoint(x: 0, y: 0)) 168 | bezierPath.addLine(to: CGPoint(x: 5, y: 5)) 169 | bezierPath.move(to: CGPoint(x: 5, y: 5)) 170 | bezierPath.addLine(to: CGPoint(x: 10, y: 0)) 171 | bezierPath.close() 172 | // shapeLayer 173 | let shapeLayer = CAShapeLayer() 174 | shapeLayer.path = bezierPath.cgPath 175 | shapeLayer.lineWidth = 0.8 176 | shapeLayer.strokeColor = color.cgColor 177 | shapeLayer.bounds = shapeLayer.path!.boundingBox 178 | shapeLayer.position = position 179 | return shapeLayer 180 | } 181 | /// separatorLayer 182 | func creatSeparatorLayer(position: CGPoint, color: UIColor) -> CAShapeLayer { 183 | // path 184 | let bezierPath = UIBezierPath() 185 | bezierPath.move(to: CGPoint(x: 0, y: 0)) 186 | bezierPath.addLine(to: CGPoint(x: 0, y: menuHeight - 16)) 187 | bezierPath.close() 188 | // separatorLayer 189 | let separatorLayer = CAShapeLayer() 190 | separatorLayer.path = bezierPath.cgPath 191 | separatorLayer.strokeColor = color.cgColor 192 | separatorLayer.lineWidth = 1 193 | separatorLayer.bounds = separatorLayer.path!.boundingBox 194 | separatorLayer.position = position 195 | return separatorLayer 196 | } 197 | 198 | // 列数 199 | numberOfColumn = ds.numberOfColumnsInMenu(self) 200 | 201 | // 当前的每列的选择情况 202 | currentSelectedRows = Array(repeating: 0, count: numberOfColumn) 203 | 204 | let backgroundLayerWidth = kScreenWidth / CGFloat(numberOfColumn) 205 | 206 | currentBgLayers.removeAll() 207 | currentTitleLayers.removeAll() 208 | currentBgLayers.removeAll() 209 | 210 | // 画出菜单 211 | for i in 0 ..< numberOfColumn { 212 | let index = CGFloat(i) 213 | 214 | // backgroundLayer 215 | let backgroundLayerPosition = CGPoint(x: (index + 0.5) * backgroundLayerWidth, y: menuHeight * 0.5) 216 | let backgroundLayer = creatBackgroundLayer(position: backgroundLayerPosition, backgroundColor: UIColor.white) 217 | layer.addSublayer(backgroundLayer) 218 | currentBgLayers.append(backgroundLayer) 219 | 220 | // titleLayer 221 | var titleStr: String! 222 | if let itemsCount = dataSource?.menu(self, numberOfItemsInRow: 0, inColumn: i), itemsCount > 0 { 223 | titleStr = dataSource?.menu(self, titleForItemsInRowAtIndexPath: Index(column: i, row: 0, item: 0)) 224 | }else{ 225 | titleStr = dataSource?.menu(self, titleForRowAtIndexPath: Index(column: i, row: 0)) 226 | } 227 | let titleLayerPosition = CGPoint(x: (index + 0.5) * backgroundLayerWidth, y: menuHeight * 0.5) 228 | let titleLayer = creatTitleLayer(text: titleStr, position: titleLayerPosition, textColor: textColor) 229 | layer.addSublayer(titleLayer) 230 | currentTitleLayers.append(titleLayer) 231 | 232 | // indicatorLayer 233 | let textSize = calculateStringSize(titleStr) 234 | let indicatorLayerPosition = CGPoint(x: titleLayerPosition.x + (textSize.width / 2) + 10, y: menuHeight / 2 + 2) 235 | let indicatorLayer = creatIndicatorLayer(position: indicatorLayerPosition, color: textColor) 236 | layer.addSublayer(indicatorLayer) 237 | currentIndicatorLayers.append(indicatorLayer) 238 | 239 | // separatorLayer 240 | if i != numberOfColumn - 1 { 241 | let separatorLayerPosition = CGPoint(x: ceil((index + 1) * backgroundLayerWidth) - 1, y: menuHeight / 2) 242 | let separatorLayer = creatSeparatorLayer(position: separatorLayerPosition, color: separatorColor) 243 | layer.addSublayer(separatorLayer) 244 | } 245 | } 246 | bottomLine.isHidden = false 247 | } 248 | 249 | private func calculateStringSize(_ string: String) -> CGSize { 250 | let attributes = [NSAttributedStringKey.font: UIFont.systemFont(ofSize: fontSize)] 251 | let option = NSStringDrawingOptions.usesLineFragmentOrigin 252 | let size = string.boundingRect(with: CGSize(width: 280, height: 0), options: option, attributes: attributes, context: nil).size 253 | return CGSize(width: ceil(size.width) + 2, height: size.height) 254 | } 255 | 256 | /// 使用代码选中列表中的row 257 | func selectedAtIndex(_ indexPath: Index) { 258 | 259 | guard let ds = dataSource else { 260 | return 261 | } 262 | // 判断传入Index是否合法 263 | guard indexPath.column >= 0 && indexPath.row >= 0 && indexPath.column < ds.numberOfColumnsInMenu(self) && indexPath.row < ds.menu(self, numberOfRowsInColumn: indexPath.column) else { 264 | fatalError("传入的indexPath不合法") 265 | } 266 | 267 | if indexPath.haveItem { 268 | guard indexPath.item < ds.menu(self, numberOfItemsInRow: indexPath.row, inColumn: indexPath.column) && indexPath.item >= 0 else { 269 | /* 270 | 如果不需要选中二级列表或者无二级列表 271 | 创建Index时请不要传入item参数, 例如: menu.selectedAtIndex(YDMenu.Index(column: 1, row: 2)) 272 | 如果一定要传入, 请传入-1, 例如: menu.selectedAtIndex(YDMenu.Index(column: 1, row: 2, item: -1)) 273 | 上面两种写法等效 274 | 不要给item 传 0, 例如: menu.selectedAtIndex(YDMenu.Index(column: 1, row: 2, item: 0)) ❌❌❌ 因为这种写法代表选择第1列中的一级列表中的第二行中的二级列表中的第0行 275 | */ 276 | fatalError("传入的indexPath不合法") 277 | } 278 | } 279 | 280 | 281 | // 选择 282 | let titleLayer = currentTitleLayers[indexPath.column] 283 | currentSelectedColumn = indexPath.column 284 | currentSelectedRows[indexPath.column] = indexPath.row 285 | if indexPath.haveItem { 286 | titleLayer.string = ds.menu(self, titleForItemsInRowAtIndexPath: indexPath) 287 | animateFor(titleLayer: titleLayer, indicator: currentIndicatorLayers[currentSelectedColumn], show: isShow, complete: { 288 | 289 | }) 290 | }else { 291 | titleLayer.string = ds.menu(self, titleForRowAtIndexPath: indexPath) 292 | animateFor(titleLayer: titleLayer, indicator: currentIndicatorLayers[currentSelectedColumn], show: isShow, complete: { 293 | 294 | }) 295 | } 296 | 297 | delegate?.menu(self, didSelectRowAtIndexPath: indexPath) 298 | } 299 | /// 默认选中 300 | func selectDeafult() { 301 | 302 | guard let ds = dataSource else { 303 | return 304 | } 305 | 306 | for i in 0 ..< ds.numberOfColumnsInMenu(self) { 307 | 308 | if ds.menu(self, numberOfItemsInRow: 0, inColumn: i) > 0 { 309 | // 有二级列表 310 | selectedAtIndex(Index(column: i, row: 0, item: 0)) 311 | }else { 312 | // 无二级列表 313 | selectedAtIndex(Index(column: i, row: 0)) 314 | } 315 | } 316 | 317 | } 318 | 319 | } 320 | 321 | // MARK: - UITableViewDataSource / Delegate 322 | extension YDMenu: UITableViewDataSource, UITableViewDelegate { 323 | 324 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 325 | 326 | if tableView == leftTableView { 327 | 328 | if let ds = dataSource { 329 | return ds.menu(self, numberOfRowsInColumn: currentSelectedColumn) 330 | } 331 | }else { 332 | 333 | if let ds = dataSource { 334 | return ds.menu(self, numberOfItemsInRow: currentSelectedRows[currentSelectedColumn], inColumn: currentSelectedColumn) 335 | } 336 | } 337 | return 0 338 | } 339 | 340 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 341 | 342 | let cellID = "cellID" 343 | var cell: UITableViewCell! = tableView.dequeueReusableCell(withIdentifier: cellID) 344 | 345 | if cell == nil { 346 | cell = UITableViewCell(style: .value1, reuseIdentifier: cellID) 347 | cell.textLabel?.textColor = textColor 348 | cell.textLabel?.highlightedTextColor = selectedTextColor 349 | cell.textLabel?.font = UIFont.systemFont(ofSize: fontSize) 350 | cell.detailTextLabel?.textColor = detailTextColor 351 | cell.detailTextLabel?.highlightedTextColor = selectedTextColor 352 | cell.detailTextLabel?.font = UIFont.systemFont(ofSize: detailTextFontSize) 353 | cell.imageView?.contentMode = .scaleAspectFit 354 | } 355 | 356 | if tableView == leftTableView { 357 | // 一级列表 358 | if let ds = dataSource { 359 | 360 | cell.textLabel?.text = ds.menu(self, titleForRowAtIndexPath: Index(column: currentSelectedColumn, row: indexPath.row)) 361 | cell.detailTextLabel?.text = ds.menu(self, detailTextForRowAtIndexPath: Index(column: currentSelectedColumn, row: indexPath.row)) 362 | // image 363 | switch ds.menu(self, imageNameForRowAtIndexPath: Index(column: currentSelectedColumn, row: indexPath.row)) { 364 | case .some(let imageName): 365 | 366 | cell.imageView?.image = UIImage(named: imageName) 367 | break 368 | case .none: 369 | cell.imageView?.image = nil 370 | 371 | } 372 | 373 | // 选中上次选择的行 374 | if currentSelectedRows[currentSelectedColumn] == indexPath.row { 375 | 376 | tableView.selectRow(at: indexPath, animated: true, scrollPosition: .none) 377 | 378 | } 379 | 380 | if ds.menu(self, numberOfItemsInRow: indexPath.row, inColumn: currentSelectedColumn) > 0 { 381 | cell.accessoryType = .disclosureIndicator 382 | }else { 383 | cell.accessoryType = .none 384 | } 385 | cell.backgroundColor = UIColor(white: 0.99, alpha: 1) 386 | 387 | } 388 | }else { 389 | // 二级列表 390 | if let ds = dataSource { 391 | 392 | let currentSelectedRow = currentSelectedRows[currentSelectedColumn] 393 | 394 | cell.textLabel?.text = ds.menu(self, titleForItemsInRowAtIndexPath: Index(column: currentSelectedColumn, row: currentSelectedRow, item: indexPath.row)) 395 | cell.detailTextLabel?.text = ds.menu(self, detailTextForItemsInRowAtIndexPath: Index(column: currentSelectedColumn, row: currentSelectedRow, item: indexPath.row)) 396 | // image 397 | switch ds.menu(self, imageNameForItemsInRowAtIndexPath: Index(column: currentSelectedColumn, row: currentSelectedRow, item: indexPath.row)) { 398 | case .some(let imageName): 399 | cell.imageView?.image = UIImage(named: imageName) 400 | break 401 | case .none: 402 | cell.imageView?.image = nil 403 | } 404 | 405 | // 选中上次选择的行 406 | if cell.textLabel?.text == currentTitleLayers[currentSelectedColumn].string as! String? { 407 | leftTableView.selectRow(at: IndexPath(row: currentSelectedRows[currentSelectedColumn], section: 0), animated: true, scrollPosition: .middle) 408 | rightTableView.selectRow(at: indexPath, animated: true, scrollPosition: .middle) 409 | } 410 | 411 | cell.accessoryType = .none 412 | cell.backgroundColor = UIColor(white: 0.985, alpha: 1) 413 | } 414 | 415 | } 416 | return cell 417 | } 418 | 419 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 420 | 421 | guard let ds = dataSource else { return } 422 | 423 | let titleLayer = currentTitleLayers[currentSelectedColumn] 424 | 425 | if tableView == leftTableView { 426 | // 一级列表 427 | 428 | currentSelectedRows[currentSelectedColumn] = indexPath.row 429 | 430 | let haveItems = ds.menu(self, numberOfItemsInRow: indexPath.row, inColumn: currentSelectedColumn) > 0 431 | titleLayer.string = ds.menu(self, titleForRowAtIndexPath: Index(column: currentSelectedColumn, row: indexPath.row)) 432 | animateFor(titleLayer: titleLayer, indicator: currentIndicatorLayers[currentSelectedColumn], show: true, complete: {}) 433 | 434 | if haveItems { 435 | rightTableView.reloadData() 436 | }else { 437 | // 收回列表 438 | animateFor(indicator: currentIndicatorLayers[currentSelectedColumn], title: titleLayer, show: false, complete: { 439 | isShow = false 440 | }) 441 | } 442 | 443 | delegate?.menu(self, didSelectRowAtIndexPath: Index(column: currentSelectedColumn, row: indexPath.row)) 444 | 445 | }else { 446 | // 二级列表 447 | 448 | titleLayer.string = ds.menu(self, titleForItemsInRowAtIndexPath: Index(column: currentSelectedColumn, row: currentSelectedRows[currentSelectedColumn], item: indexPath.row)) 449 | // 收回列表 450 | animateFor(indicator: currentIndicatorLayers[currentSelectedColumn], title: titleLayer, show: false, complete: { 451 | isShow = false 452 | }) 453 | 454 | delegate?.menu(self, didSelectRowAtIndexPath: Index(column: currentSelectedColumn, row: currentSelectedRows[currentSelectedColumn], item: indexPath.row)) 455 | } 456 | 457 | } 458 | } 459 | 460 | // MARK: - ActionEvent 461 | private extension YDMenu { 462 | 463 | @objc private func backTapped(sender: UITapGestureRecognizer) -> Void { 464 | 465 | animateFor(indicator: currentIndicatorLayers[currentSelectedColumn], title: currentTitleLayers[currentSelectedColumn], show: false) { 466 | isShow = false 467 | } 468 | } 469 | 470 | @objc private func menuTapped(sender: UITapGestureRecognizer) -> Void { 471 | 472 | guard let ds = dataSource else { return } 473 | 474 | // 确定点击的index 475 | let tapPoint = sender .location(in: self) 476 | let tapIndex: Int = Int(tapPoint.x / (kScreenWidth / CGFloat(numberOfColumn))) 477 | 478 | // 收回其他的column的menu 479 | for i in 0 ..< numberOfColumn { 480 | if i != tapIndex { 481 | animateFor(indicator: currentIndicatorLayers[i], reverse: false, complete: { 482 | animateFor(titleLayer: currentTitleLayers[i], indicator: nil, show: false, complete: { 483 | 484 | }) 485 | }) 486 | } 487 | 488 | } 489 | 490 | // 收回或弹出当前的menu 491 | if currentSelectedColumn == tapIndex && isShow { 492 | // 收回menu 493 | animateFor(indicator: currentIndicatorLayers[tapIndex], title: currentTitleLayers[tapIndex], show: false, complete: { 494 | currentSelectedColumn = tapIndex 495 | isShow = false 496 | }) 497 | 498 | }else { 499 | // 弹出menu 500 | currentSelectedColumn = tapIndex 501 | // 载入数据 502 | leftTableView.reloadData() 503 | 504 | if ds.menu(self, numberOfItemsInRow: currentSelectedRows[currentSelectedColumn], inColumn: currentSelectedColumn) > 0 { 505 | rightTableView.reloadData() 506 | } 507 | 508 | animateFor(indicator: currentIndicatorLayers[tapIndex], title: currentTitleLayers[tapIndex], show: true, complete: { 509 | isShow = true 510 | }) 511 | } 512 | 513 | } 514 | 515 | } 516 | 517 | // MARK: - Animation 518 | private extension YDMenu { 519 | 520 | 521 | func animateFor(indicator: CAShapeLayer, title: CATextLayer, show: Bool, complete: () -> Void) -> Void { 522 | 523 | animateFor(indicator: indicator, reverse: show) { 524 | animateFor(titleLayer: title, indicator: indicator, show: show, complete: { 525 | animateForBackgroundView(show: show, complete: { 526 | animateTableView(show: show, complete: { 527 | 528 | }) 529 | }) 530 | }) 531 | } 532 | complete() 533 | } 534 | 535 | /// 指示符 536 | func animateFor(indicator: CAShapeLayer, reverse: Bool, complete: () -> Void) -> Void { 537 | 538 | // 旋转动画 539 | 540 | // CATransaction.begin() 541 | // CATransaction.setAnimationDuration(kAnimationDuration) 542 | // CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(controlPoints: 0.4, 0.0, 0.2, 1.0)) 543 | // let animation = CAKeyframeAnimation(keyPath: "transform.rotation") 544 | // animation.values = reverse ? [CGFloat(0), CGFloat.pi] : [CGFloat.pi, CGFloat(0)] 545 | // animation.duration = kAnimationDuration 546 | // indicator.add(animation, forKey: animation.keyPath) 547 | 548 | // if animation.isRemovedOnCompletion { 549 | if reverse { 550 | indicator.transform = CATransform3DMakeRotation(CGFloat.pi, 0, 0, 1) 551 | }else { 552 | indicator.transform = CATransform3DIdentity 553 | } 554 | 555 | 556 | // indicator.setValue(animation.values?.last, forKey: animation.keyPath!) 557 | // } 558 | 559 | // CATransaction.commit() 560 | 561 | /// indicator 颜色 562 | if reverse { 563 | indicator.strokeColor = selectedTextColor.cgColor 564 | }else { 565 | indicator.strokeColor = textColor.cgColor 566 | } 567 | 568 | 569 | 570 | complete() 571 | } 572 | 573 | /// backgroundView动画 574 | func animateForBackgroundView(show: Bool, complete: () -> Void) -> Void { 575 | 576 | if show { 577 | superview?.addSubview(backGroundView) 578 | superview?.addSubview(self) 579 | UIView.animate(withDuration: kAnimationDuration, animations: { 580 | self.backGroundView.backgroundColor = UIColor(white: 0, alpha: 0.3) 581 | }) 582 | }else { 583 | UIView.animate(withDuration: kAnimationDuration, animations: { 584 | self.backGroundView.backgroundColor = UIColor(white: 0, alpha: 0) 585 | }, completion: { (finished) in 586 | if finished { 587 | self.backGroundView.removeFromSuperview() 588 | } 589 | }) 590 | } 591 | 592 | complete() 593 | } 594 | 595 | /// tableView动画 596 | func animateTableView(show: Bool, complete: () -> Void) -> Void { 597 | 598 | var haveItems = false 599 | let numberOfRow = leftTableView.numberOfRows(inSection: 0) 600 | if let ds = dataSource { 601 | for i in 0 ..< numberOfRow { 602 | if ds.menu(self, numberOfItemsInRow: i, inColumn: currentSelectedColumn) > 0 { 603 | haveItems = true 604 | break 605 | } 606 | } 607 | } 608 | 609 | let heightForTableView = CGFloat(numberOfRow) * cellHeight > tableViewHeight ? tableViewHeight : CGFloat(numberOfRow) * cellHeight; 610 | 611 | 612 | if show { 613 | 614 | if haveItems { 615 | 616 | leftTableView.frame = CGRect(x: 0, y: menuOrigin.y + menuHeight, width: kScreenWidth / 2, height: 0) 617 | rightTableView.frame = CGRect(x: kScreenWidth / 2, y: menuOrigin.y + menuHeight, width: kScreenWidth / 2, height: 0) 618 | 619 | superview?.addSubview(leftTableView) 620 | superview?.addSubview(rightTableView) 621 | }else { 622 | rightTableView.removeFromSuperview() 623 | leftTableView.frame = CGRect(x: 0, y: menuOrigin.y + menuHeight, width: kScreenWidth, height: 0) 624 | superview?.addSubview(leftTableView) 625 | } 626 | 627 | UIView.animate(withDuration: kAnimationDuration) { 628 | self.leftTableView.frame.size.height = heightForTableView 629 | if haveItems { 630 | self.rightTableView.frame.size.height = heightForTableView 631 | } 632 | } 633 | 634 | }else { 635 | 636 | UIView.animate(withDuration: kAnimationDuration, animations: { 637 | 638 | self.leftTableView.frame.size.height = 0 639 | if haveItems { 640 | self.rightTableView.frame.size.height = 0 641 | } 642 | }, completion: { (finished) in 643 | 644 | self.leftTableView.removeFromSuperview() 645 | if haveItems { 646 | self.rightTableView.removeFromSuperview() 647 | } 648 | }) 649 | } 650 | 651 | complete() 652 | } 653 | 654 | /// titleLayer动画 655 | func animateFor(titleLayer textLayer: CATextLayer, indicator: CAShapeLayer?, show: Bool, complete: () -> Void) -> Void { 656 | 657 | let textSize = calculateStringSize((textLayer.string as! String?) ?? "") 658 | let maxWidth = kScreenWidth / CGFloat(numberOfColumn) - 25 659 | let textLayerWidth = (textSize.width < maxWidth) ? textSize.width : maxWidth 660 | 661 | textLayer.bounds.size.width = textLayerWidth 662 | textLayer.bounds.size.height = textSize.height 663 | 664 | if let indicatorR = indicator { 665 | indicatorR.position.x = textLayer.position.x + textLayerWidth / 2 + 10 666 | } 667 | 668 | if show { 669 | textLayer.foregroundColor = selectedTextColor.cgColor 670 | }else { 671 | textLayer.foregroundColor = textColor.cgColor 672 | } 673 | 674 | complete() 675 | } 676 | 677 | 678 | } 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | -------------------------------------------------------------------------------- /YDMenu/YDMenu/YDMenuDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YDMenuDataSource.swift 3 | // YDMenu 4 | // 5 | // Created by ZJXN on 2018/3/8. 6 | // Copyright © 2018年 YDZhao. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol YDMenuDataSource: class { 12 | 13 | // required 14 | ///每个column有多少行 15 | func menu(_ menu: YDMenu, numberOfRowsInColumn column: Int) -> Int 16 | 17 | ///每个column中每行的title 18 | func menu(_ menu: YDMenu, titleForRowAtIndexPath indexPath: YDMenu.Index) -> String 19 | 20 | // optional 21 | /// 有多少个column,默认为1列 22 | func numberOfColumnsInMenu(_ menu: YDMenu) -> Int 23 | 24 | 25 | // MARK: - 一级菜单 26 | /// 第column列,每行的image 27 | func menu(_ menu: YDMenu, imageNameForRowAtIndexPath: YDMenu.Index) -> String? 28 | 29 | /// detail text 30 | func menu(_ menu: YDMenu, detailTextForRowAtIndexPath indexPath: YDMenu.Index) -> String? 31 | 32 | /// 某列的某行item的数量,如果有,则说明有二级菜单,反之亦然 33 | func menu(_ menu: YDMenu, numberOfItemsInRow row: Int, inColumn column: Int) -> Int 34 | 35 | 36 | // MARK: - 二级菜单 37 | /// 二级菜单的标题 38 | func menu(_ menu: YDMenu, titleForItemsInRowAtIndexPath indexPath: YDMenu.Index) -> String 39 | 40 | /// 二级菜单的image 41 | func menu(_ menu: YDMenu, imageNameForItemsInRowAtIndexPath: YDMenu.Index) -> String? 42 | 43 | /// 二级菜单的detail text 44 | func menu(_ menu: YDMenu, detailTextForItemsInRowAtIndexPath indexPath: YDMenu.Index) -> String? 45 | } 46 | 47 | extension YDMenuDataSource { 48 | 49 | /// 有多少个column,默认为1列 50 | func numberOfColumnsInMenu(_ menu: YDMenu) -> Int { 51 | return 1 52 | } 53 | 54 | 55 | // MARK: - 一级菜单 56 | /// 第column列,每行的image 57 | func menu(_ menu: YDMenu, imageNameForRowAtIndexPath indexPath: YDMenu.Index) -> String? { 58 | return nil 59 | } 60 | 61 | /// detail text 62 | func menu(_ menu: YDMenu, detailTextForRowAtIndexPath indexPath: YDMenu.Index) -> String? { 63 | return nil 64 | } 65 | 66 | /// 某列的某行item的数量,如果有,则说明有二级菜单,反之亦然 67 | func menu(_ menu: YDMenu, numberOfItemsInRow row: Int, inColumn column: Int) -> Int { 68 | return 0 69 | } 70 | 71 | 72 | // MARK: - 二级菜单 73 | /// 二级菜单的标题 74 | func menu(_ menu: YDMenu, titleForItemsInRowAtIndexPath indexPath: YDMenu.Index) -> String { 75 | return "" 76 | } 77 | 78 | /// 二级菜单的image 79 | func menu(_ menu: YDMenu, imageNameForItemsInRowAtIndexPath: YDMenu.Index) -> String? { 80 | return nil 81 | } 82 | 83 | /// 二级菜单的detail text 84 | func menu(_ menu: YDMenu, detailTextForItemsInRowAtIndexPath indexPath: YDMenu.Index) -> String? { 85 | return nil 86 | } 87 | } 88 | 89 | 90 | -------------------------------------------------------------------------------- /YDMenu/YDMenu/YDMenuDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YDMenuDelegate.swift 3 | // YDMenu 4 | // 5 | // Created by ZJXN on 2018/3/8. 6 | // Copyright © 2018年 YDZhao. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol YDMenuDelegate: class { 12 | 13 | /// 点击 14 | func menu(_ menu: YDMenu, didSelectRowAtIndexPath indexPath: YDMenu.Index) -> Void 15 | } 16 | 17 | --------------------------------------------------------------------------------