├── Example-1.PNG ├── Example-2.PNG ├── Example-3.PNG ├── Example └── JonContextMenu-Example │ ├── JonContextMenu-Example.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ ├── jonathanmartins.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ │ │ └── jussi.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ ├── jonathanmartins.xcuserdatad │ │ ├── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ │ └── jussi.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist │ └── JonContextMenu-Example │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── facebook.imageset │ │ ├── Contents.json │ │ └── facebook.png │ ├── google.imageset │ │ ├── Contents.json │ │ └── google.png │ ├── instagram.imageset │ │ ├── Contents.json │ │ └── instagram.png │ └── twitter.imageset │ │ ├── Contents.json │ │ └── twitter.png │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── Extension.swift │ ├── Info.plist │ └── ViewController.swift ├── JonContextMenu.podspec ├── JonContextMenu ├── JonContextMenu.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── jonathanmartins.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ ├── jonathanmartins.xcuserdatad │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ │ └── jussi.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── JonContextMenu │ ├── Extension │ ├── UIColor+Utils.swift │ ├── UIImage+Utils.swift │ └── UIView+Utils.swift │ ├── Info.plist │ ├── JonContextMenu.h │ ├── JonContextMenu.swift │ ├── JonContextMenuView.swift │ └── JonItem.swift ├── LICENSE └── README.md /Example-1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonSurrey/JonContextMenu/b81f5647637a36246545cc8e57c5586827b09fab/Example-1.PNG -------------------------------------------------------------------------------- /Example-2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonSurrey/JonContextMenu/b81f5647637a36246545cc8e57c5586827b09fab/Example-2.PNG -------------------------------------------------------------------------------- /Example-3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonSurrey/JonContextMenu/b81f5647637a36246545cc8e57c5586827b09fab/Example-3.PNG -------------------------------------------------------------------------------- /Example/JonContextMenu-Example/JonContextMenu-Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 742C56FA2147515F0076E25F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 742C56F92147515F0076E25F /* AppDelegate.swift */; }; 11 | 742C56FC2147515F0076E25F /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 742C56FB2147515F0076E25F /* ViewController.swift */; }; 12 | 742C57012147515F0076E25F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 742C57002147515F0076E25F /* Assets.xcassets */; }; 13 | 742C57042147515F0076E25F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 742C57022147515F0076E25F /* LaunchScreen.storyboard */; }; 14 | 746171E1214763380084F8D6 /* JonContextMenu.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 742C571121475D110076E25F /* JonContextMenu.framework */; }; 15 | 746171E2214763380084F8D6 /* JonContextMenu.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 742C571121475D110076E25F /* JonContextMenu.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 16 | 746171E621476B4F0084F8D6 /* Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 746171E521476B4E0084F8D6 /* Extension.swift */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXContainerItemProxy section */ 20 | 742C571021475D110076E25F /* PBXContainerItemProxy */ = { 21 | isa = PBXContainerItemProxy; 22 | containerPortal = 742C570C21475D110076E25F /* JonContextMenu.xcodeproj */; 23 | proxyType = 2; 24 | remoteGlobalIDString = 7438C0292146277500F313E2; 25 | remoteInfo = JonContextMenu; 26 | }; 27 | 742C571421475D690076E25F /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = 742C570C21475D110076E25F /* JonContextMenu.xcodeproj */; 30 | proxyType = 1; 31 | remoteGlobalIDString = 7438C0282146277500F313E2; 32 | remoteInfo = JonContextMenu; 33 | }; 34 | 746171E3214763380084F8D6 /* PBXContainerItemProxy */ = { 35 | isa = PBXContainerItemProxy; 36 | containerPortal = 742C570C21475D110076E25F /* JonContextMenu.xcodeproj */; 37 | proxyType = 1; 38 | remoteGlobalIDString = 7438C0282146277500F313E2; 39 | remoteInfo = JonContextMenu; 40 | }; 41 | /* End PBXContainerItemProxy section */ 42 | 43 | /* Begin PBXCopyFilesBuildPhase section */ 44 | 742C571621475D690076E25F /* Embed Frameworks */ = { 45 | isa = PBXCopyFilesBuildPhase; 46 | buildActionMask = 2147483647; 47 | dstPath = ""; 48 | dstSubfolderSpec = 10; 49 | files = ( 50 | 746171E2214763380084F8D6 /* JonContextMenu.framework in Embed Frameworks */, 51 | ); 52 | name = "Embed Frameworks"; 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXCopyFilesBuildPhase section */ 56 | 57 | /* Begin PBXFileReference section */ 58 | 742C56F62147515F0076E25F /* JonContextMenu-Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "JonContextMenu-Example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 59 | 742C56F92147515F0076E25F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 60 | 742C56FB2147515F0076E25F /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 61 | 742C57002147515F0076E25F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 62 | 742C57032147515F0076E25F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 63 | 742C57052147515F0076E25F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 64 | 742C570C21475D110076E25F /* JonContextMenu.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = JonContextMenu.xcodeproj; path = ../../JonContextMenu/JonContextMenu.xcodeproj; sourceTree = ""; }; 65 | 746171E521476B4E0084F8D6 /* Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extension.swift; sourceTree = ""; }; 66 | /* End PBXFileReference section */ 67 | 68 | /* Begin PBXFrameworksBuildPhase section */ 69 | 742C56F32147515F0076E25F /* Frameworks */ = { 70 | isa = PBXFrameworksBuildPhase; 71 | buildActionMask = 2147483647; 72 | files = ( 73 | 746171E1214763380084F8D6 /* JonContextMenu.framework in Frameworks */, 74 | ); 75 | runOnlyForDeploymentPostprocessing = 0; 76 | }; 77 | /* End PBXFrameworksBuildPhase section */ 78 | 79 | /* Begin PBXGroup section */ 80 | 742C56ED2147515E0076E25F = { 81 | isa = PBXGroup; 82 | children = ( 83 | 742C570C21475D110076E25F /* JonContextMenu.xcodeproj */, 84 | 742C56F82147515F0076E25F /* JonContextMenu-Example */, 85 | 742C56F72147515F0076E25F /* Products */, 86 | ); 87 | sourceTree = ""; 88 | }; 89 | 742C56F72147515F0076E25F /* Products */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | 742C56F62147515F0076E25F /* JonContextMenu-Example.app */, 93 | ); 94 | name = Products; 95 | sourceTree = ""; 96 | }; 97 | 742C56F82147515F0076E25F /* JonContextMenu-Example */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 746171E521476B4E0084F8D6 /* Extension.swift */, 101 | 742C56F92147515F0076E25F /* AppDelegate.swift */, 102 | 742C56FB2147515F0076E25F /* ViewController.swift */, 103 | 742C57002147515F0076E25F /* Assets.xcassets */, 104 | 742C57022147515F0076E25F /* LaunchScreen.storyboard */, 105 | 742C57052147515F0076E25F /* Info.plist */, 106 | ); 107 | path = "JonContextMenu-Example"; 108 | sourceTree = ""; 109 | }; 110 | 742C570D21475D110076E25F /* Products */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | 742C571121475D110076E25F /* JonContextMenu.framework */, 114 | ); 115 | name = Products; 116 | sourceTree = ""; 117 | }; 118 | /* End PBXGroup section */ 119 | 120 | /* Begin PBXNativeTarget section */ 121 | 742C56F52147515F0076E25F /* JonContextMenu-Example */ = { 122 | isa = PBXNativeTarget; 123 | buildConfigurationList = 742C57082147515F0076E25F /* Build configuration list for PBXNativeTarget "JonContextMenu-Example" */; 124 | buildPhases = ( 125 | 742C56F22147515F0076E25F /* Sources */, 126 | 742C56F32147515F0076E25F /* Frameworks */, 127 | 742C56F42147515F0076E25F /* Resources */, 128 | 742C571621475D690076E25F /* Embed Frameworks */, 129 | ); 130 | buildRules = ( 131 | ); 132 | dependencies = ( 133 | 742C571521475D690076E25F /* PBXTargetDependency */, 134 | 746171E4214763380084F8D6 /* PBXTargetDependency */, 135 | ); 136 | name = "JonContextMenu-Example"; 137 | productName = "JonContextMenu-Example"; 138 | productReference = 742C56F62147515F0076E25F /* JonContextMenu-Example.app */; 139 | productType = "com.apple.product-type.application"; 140 | }; 141 | /* End PBXNativeTarget section */ 142 | 143 | /* Begin PBXProject section */ 144 | 742C56EE2147515E0076E25F /* Project object */ = { 145 | isa = PBXProject; 146 | attributes = { 147 | LastSwiftUpdateCheck = 0900; 148 | LastUpgradeCheck = 1000; 149 | ORGANIZATIONNAME = Surrey; 150 | TargetAttributes = { 151 | 742C56F52147515F0076E25F = { 152 | CreatedOnToolsVersion = 9.0; 153 | LastSwiftMigration = 1000; 154 | ProvisioningStyle = Automatic; 155 | }; 156 | }; 157 | }; 158 | buildConfigurationList = 742C56F12147515E0076E25F /* Build configuration list for PBXProject "JonContextMenu-Example" */; 159 | compatibilityVersion = "Xcode 8.0"; 160 | developmentRegion = en; 161 | hasScannedForEncodings = 0; 162 | knownRegions = ( 163 | en, 164 | Base, 165 | ); 166 | mainGroup = 742C56ED2147515E0076E25F; 167 | productRefGroup = 742C56F72147515F0076E25F /* Products */; 168 | projectDirPath = ""; 169 | projectReferences = ( 170 | { 171 | ProductGroup = 742C570D21475D110076E25F /* Products */; 172 | ProjectRef = 742C570C21475D110076E25F /* JonContextMenu.xcodeproj */; 173 | }, 174 | ); 175 | projectRoot = ""; 176 | targets = ( 177 | 742C56F52147515F0076E25F /* JonContextMenu-Example */, 178 | ); 179 | }; 180 | /* End PBXProject section */ 181 | 182 | /* Begin PBXReferenceProxy section */ 183 | 742C571121475D110076E25F /* JonContextMenu.framework */ = { 184 | isa = PBXReferenceProxy; 185 | fileType = wrapper.framework; 186 | path = JonContextMenu.framework; 187 | remoteRef = 742C571021475D110076E25F /* PBXContainerItemProxy */; 188 | sourceTree = BUILT_PRODUCTS_DIR; 189 | }; 190 | /* End PBXReferenceProxy section */ 191 | 192 | /* Begin PBXResourcesBuildPhase section */ 193 | 742C56F42147515F0076E25F /* Resources */ = { 194 | isa = PBXResourcesBuildPhase; 195 | buildActionMask = 2147483647; 196 | files = ( 197 | 742C57042147515F0076E25F /* LaunchScreen.storyboard in Resources */, 198 | 742C57012147515F0076E25F /* Assets.xcassets in Resources */, 199 | ); 200 | runOnlyForDeploymentPostprocessing = 0; 201 | }; 202 | /* End PBXResourcesBuildPhase section */ 203 | 204 | /* Begin PBXSourcesBuildPhase section */ 205 | 742C56F22147515F0076E25F /* Sources */ = { 206 | isa = PBXSourcesBuildPhase; 207 | buildActionMask = 2147483647; 208 | files = ( 209 | 746171E621476B4F0084F8D6 /* Extension.swift in Sources */, 210 | 742C56FC2147515F0076E25F /* ViewController.swift in Sources */, 211 | 742C56FA2147515F0076E25F /* AppDelegate.swift in Sources */, 212 | ); 213 | runOnlyForDeploymentPostprocessing = 0; 214 | }; 215 | /* End PBXSourcesBuildPhase section */ 216 | 217 | /* Begin PBXTargetDependency section */ 218 | 742C571521475D690076E25F /* PBXTargetDependency */ = { 219 | isa = PBXTargetDependency; 220 | name = JonContextMenu; 221 | targetProxy = 742C571421475D690076E25F /* PBXContainerItemProxy */; 222 | }; 223 | 746171E4214763380084F8D6 /* PBXTargetDependency */ = { 224 | isa = PBXTargetDependency; 225 | name = JonContextMenu; 226 | targetProxy = 746171E3214763380084F8D6 /* PBXContainerItemProxy */; 227 | }; 228 | /* End PBXTargetDependency section */ 229 | 230 | /* Begin PBXVariantGroup section */ 231 | 742C57022147515F0076E25F /* LaunchScreen.storyboard */ = { 232 | isa = PBXVariantGroup; 233 | children = ( 234 | 742C57032147515F0076E25F /* Base */, 235 | ); 236 | name = LaunchScreen.storyboard; 237 | sourceTree = ""; 238 | }; 239 | /* End PBXVariantGroup section */ 240 | 241 | /* Begin XCBuildConfiguration section */ 242 | 742C57062147515F0076E25F /* Debug */ = { 243 | isa = XCBuildConfiguration; 244 | buildSettings = { 245 | ALWAYS_SEARCH_USER_PATHS = NO; 246 | CLANG_ANALYZER_NONNULL = YES; 247 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 248 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 249 | CLANG_CXX_LIBRARY = "libc++"; 250 | CLANG_ENABLE_MODULES = YES; 251 | CLANG_ENABLE_OBJC_ARC = YES; 252 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 253 | CLANG_WARN_BOOL_CONVERSION = YES; 254 | CLANG_WARN_COMMA = YES; 255 | CLANG_WARN_CONSTANT_CONVERSION = YES; 256 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 257 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 258 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 259 | CLANG_WARN_EMPTY_BODY = YES; 260 | CLANG_WARN_ENUM_CONVERSION = YES; 261 | CLANG_WARN_INFINITE_RECURSION = YES; 262 | CLANG_WARN_INT_CONVERSION = YES; 263 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 264 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 265 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 266 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 267 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 268 | CLANG_WARN_STRICT_PROTOTYPES = YES; 269 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 270 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 271 | CLANG_WARN_UNREACHABLE_CODE = YES; 272 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 273 | CODE_SIGN_IDENTITY = "iPhone Developer"; 274 | COPY_PHASE_STRIP = NO; 275 | DEBUG_INFORMATION_FORMAT = dwarf; 276 | ENABLE_STRICT_OBJC_MSGSEND = YES; 277 | ENABLE_TESTABILITY = YES; 278 | GCC_C_LANGUAGE_STANDARD = gnu11; 279 | GCC_DYNAMIC_NO_PIC = NO; 280 | GCC_NO_COMMON_BLOCKS = YES; 281 | GCC_OPTIMIZATION_LEVEL = 0; 282 | GCC_PREPROCESSOR_DEFINITIONS = ( 283 | "DEBUG=1", 284 | "$(inherited)", 285 | ); 286 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 287 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 288 | GCC_WARN_UNDECLARED_SELECTOR = YES; 289 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 290 | GCC_WARN_UNUSED_FUNCTION = YES; 291 | GCC_WARN_UNUSED_VARIABLE = YES; 292 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 293 | MTL_ENABLE_DEBUG_INFO = YES; 294 | ONLY_ACTIVE_ARCH = YES; 295 | SDKROOT = iphoneos; 296 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 297 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 298 | }; 299 | name = Debug; 300 | }; 301 | 742C57072147515F0076E25F /* Release */ = { 302 | isa = XCBuildConfiguration; 303 | buildSettings = { 304 | ALWAYS_SEARCH_USER_PATHS = NO; 305 | CLANG_ANALYZER_NONNULL = YES; 306 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 307 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 308 | CLANG_CXX_LIBRARY = "libc++"; 309 | CLANG_ENABLE_MODULES = YES; 310 | CLANG_ENABLE_OBJC_ARC = YES; 311 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 312 | CLANG_WARN_BOOL_CONVERSION = YES; 313 | CLANG_WARN_COMMA = YES; 314 | CLANG_WARN_CONSTANT_CONVERSION = YES; 315 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 316 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 317 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 318 | CLANG_WARN_EMPTY_BODY = YES; 319 | CLANG_WARN_ENUM_CONVERSION = YES; 320 | CLANG_WARN_INFINITE_RECURSION = YES; 321 | CLANG_WARN_INT_CONVERSION = YES; 322 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 323 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 324 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 325 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 326 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 327 | CLANG_WARN_STRICT_PROTOTYPES = YES; 328 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 329 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 330 | CLANG_WARN_UNREACHABLE_CODE = YES; 331 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 332 | CODE_SIGN_IDENTITY = "iPhone Developer"; 333 | COPY_PHASE_STRIP = NO; 334 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 335 | ENABLE_NS_ASSERTIONS = NO; 336 | ENABLE_STRICT_OBJC_MSGSEND = YES; 337 | GCC_C_LANGUAGE_STANDARD = gnu11; 338 | GCC_NO_COMMON_BLOCKS = YES; 339 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 340 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 341 | GCC_WARN_UNDECLARED_SELECTOR = YES; 342 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 343 | GCC_WARN_UNUSED_FUNCTION = YES; 344 | GCC_WARN_UNUSED_VARIABLE = YES; 345 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 346 | MTL_ENABLE_DEBUG_INFO = NO; 347 | SDKROOT = iphoneos; 348 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 349 | VALIDATE_PRODUCT = YES; 350 | }; 351 | name = Release; 352 | }; 353 | 742C57092147515F0076E25F /* Debug */ = { 354 | isa = XCBuildConfiguration; 355 | buildSettings = { 356 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 357 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 358 | CODE_SIGN_STYLE = Automatic; 359 | DEVELOPMENT_TEAM = NA36627892; 360 | INFOPLIST_FILE = "JonContextMenu-Example/Info.plist"; 361 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 362 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 363 | PRODUCT_BUNDLE_IDENTIFIER = "com.surrey.JonContextMenu-Example"; 364 | PRODUCT_NAME = "$(TARGET_NAME)"; 365 | SWIFT_VERSION = 4.2; 366 | TARGETED_DEVICE_FAMILY = "1,2"; 367 | }; 368 | name = Debug; 369 | }; 370 | 742C570A2147515F0076E25F /* Release */ = { 371 | isa = XCBuildConfiguration; 372 | buildSettings = { 373 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 374 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 375 | CODE_SIGN_STYLE = Automatic; 376 | DEVELOPMENT_TEAM = NA36627892; 377 | INFOPLIST_FILE = "JonContextMenu-Example/Info.plist"; 378 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 379 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 380 | PRODUCT_BUNDLE_IDENTIFIER = "com.surrey.JonContextMenu-Example"; 381 | PRODUCT_NAME = "$(TARGET_NAME)"; 382 | SWIFT_VERSION = 4.2; 383 | TARGETED_DEVICE_FAMILY = "1,2"; 384 | }; 385 | name = Release; 386 | }; 387 | /* End XCBuildConfiguration section */ 388 | 389 | /* Begin XCConfigurationList section */ 390 | 742C56F12147515E0076E25F /* Build configuration list for PBXProject "JonContextMenu-Example" */ = { 391 | isa = XCConfigurationList; 392 | buildConfigurations = ( 393 | 742C57062147515F0076E25F /* Debug */, 394 | 742C57072147515F0076E25F /* Release */, 395 | ); 396 | defaultConfigurationIsVisible = 0; 397 | defaultConfigurationName = Release; 398 | }; 399 | 742C57082147515F0076E25F /* Build configuration list for PBXNativeTarget "JonContextMenu-Example" */ = { 400 | isa = XCConfigurationList; 401 | buildConfigurations = ( 402 | 742C57092147515F0076E25F /* Debug */, 403 | 742C570A2147515F0076E25F /* Release */, 404 | ); 405 | defaultConfigurationIsVisible = 0; 406 | defaultConfigurationName = Release; 407 | }; 408 | /* End XCConfigurationList section */ 409 | }; 410 | rootObject = 742C56EE2147515E0076E25F /* Project object */; 411 | } 412 | -------------------------------------------------------------------------------- /Example/JonContextMenu-Example/JonContextMenu-Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/JonContextMenu-Example/JonContextMenu-Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/JonContextMenu-Example/JonContextMenu-Example.xcodeproj/project.xcworkspace/xcuserdata/jonathanmartins.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonSurrey/JonContextMenu/b81f5647637a36246545cc8e57c5586827b09fab/Example/JonContextMenu-Example/JonContextMenu-Example.xcodeproj/project.xcworkspace/xcuserdata/jonathanmartins.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Example/JonContextMenu-Example/JonContextMenu-Example.xcodeproj/project.xcworkspace/xcuserdata/jussi.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonSurrey/JonContextMenu/b81f5647637a36246545cc8e57c5586827b09fab/Example/JonContextMenu-Example/JonContextMenu-Example.xcodeproj/project.xcworkspace/xcuserdata/jussi.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Example/JonContextMenu-Example/JonContextMenu-Example.xcodeproj/xcuserdata/jonathanmartins.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /Example/JonContextMenu-Example/JonContextMenu-Example.xcodeproj/xcuserdata/jonathanmartins.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | JonContextMenu-Example.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | JonContextMenu-Example.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Example/JonContextMenu-Example/JonContextMenu-Example.xcodeproj/xcuserdata/jussi.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /Example/JonContextMenu-Example/JonContextMenu-Example.xcodeproj/xcuserdata/jussi.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | JonContextMenu-Example.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Example/JonContextMenu-Example/JonContextMenu-Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // JonContextMenu-Example 4 | // 5 | // Created by Jonathan Martins on 10/09/2018. 6 | // Copyright © 2018 Surrey. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | 18 | window = UIWindow() 19 | window?.makeKeyAndVisible() 20 | window?.rootViewController = UINavigationController(rootViewController: ViewController()) 21 | 22 | return true 23 | } 24 | 25 | func applicationWillResignActive(_ application: UIApplication) { 26 | // 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. 27 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 28 | } 29 | 30 | func applicationDidEnterBackground(_ application: UIApplication) { 31 | // 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. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | func applicationWillEnterForeground(_ application: UIApplication) { 36 | // 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. 37 | } 38 | 39 | func applicationDidBecomeActive(_ application: UIApplication) { 40 | // 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. 41 | } 42 | 43 | func applicationWillTerminate(_ application: UIApplication) { 44 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 45 | } 46 | 47 | 48 | } 49 | 50 | -------------------------------------------------------------------------------- /Example/JonContextMenu-Example/JonContextMenu-Example/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 | } -------------------------------------------------------------------------------- /Example/JonContextMenu-Example/JonContextMenu-Example/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/JonContextMenu-Example/JonContextMenu-Example/Assets.xcassets/facebook.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "facebook.png", 6 | "scale" : "1x" 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 | } -------------------------------------------------------------------------------- /Example/JonContextMenu-Example/JonContextMenu-Example/Assets.xcassets/facebook.imageset/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonSurrey/JonContextMenu/b81f5647637a36246545cc8e57c5586827b09fab/Example/JonContextMenu-Example/JonContextMenu-Example/Assets.xcassets/facebook.imageset/facebook.png -------------------------------------------------------------------------------- /Example/JonContextMenu-Example/JonContextMenu-Example/Assets.xcassets/google.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "google.png", 6 | "scale" : "1x" 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 | } -------------------------------------------------------------------------------- /Example/JonContextMenu-Example/JonContextMenu-Example/Assets.xcassets/google.imageset/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonSurrey/JonContextMenu/b81f5647637a36246545cc8e57c5586827b09fab/Example/JonContextMenu-Example/JonContextMenu-Example/Assets.xcassets/google.imageset/google.png -------------------------------------------------------------------------------- /Example/JonContextMenu-Example/JonContextMenu-Example/Assets.xcassets/instagram.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "instagram.png", 6 | "scale" : "1x" 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 | } -------------------------------------------------------------------------------- /Example/JonContextMenu-Example/JonContextMenu-Example/Assets.xcassets/instagram.imageset/instagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonSurrey/JonContextMenu/b81f5647637a36246545cc8e57c5586827b09fab/Example/JonContextMenu-Example/JonContextMenu-Example/Assets.xcassets/instagram.imageset/instagram.png -------------------------------------------------------------------------------- /Example/JonContextMenu-Example/JonContextMenu-Example/Assets.xcassets/twitter.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "twitter.png", 6 | "scale" : "1x" 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 | } -------------------------------------------------------------------------------- /Example/JonContextMenu-Example/JonContextMenu-Example/Assets.xcassets/twitter.imageset/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonSurrey/JonContextMenu/b81f5647637a36246545cc8e57c5586827b09fab/Example/JonContextMenu-Example/JonContextMenu-Example/Assets.xcassets/twitter.imageset/twitter.png -------------------------------------------------------------------------------- /Example/JonContextMenu-Example/JonContextMenu-Example/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Example/JonContextMenu-Example/JonContextMenu-Example/Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extension.swift 3 | // JonContextMenu-Example 4 | // 5 | // Created by Jonathan Martins on 11/09/2018. 6 | // Copyright © 2018 Surrey. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Foundation 11 | 12 | extension UIColor { 13 | 14 | /// Takes an Hexadecimal String value and converts to UIColor 15 | convenience init(hexString: String) { 16 | let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) 17 | var int = UInt32() 18 | Scanner(string: hex).scanHexInt32(&int) 19 | let a, r, g, b: UInt32 20 | 21 | switch hex.count { 22 | case 3: // RGB (12-bit) 23 | (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) 24 | case 6: // RGB (24-bit) 25 | (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) 26 | case 8: // ARGB (32-bit) 27 | (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) 28 | default: 29 | (a, r, g, b) = (255, 0, 0, 0) 30 | } 31 | self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255) 32 | } 33 | } 34 | 35 | extension UITableView { 36 | 37 | /// Register a Cell in the TableView given an UITableViewCell Type 38 | func registerCell(_ cell:T.Type){ 39 | self.register(T.self, forCellReuseIdentifier: String(describing: T.self)) 40 | } 41 | 42 | /// Returns a UITableViewCell for a given Class Type 43 | func getCell(_ indexPath: IndexPath, _ type:T.Type) -> T?{ 44 | return self.dequeueReusableCell(withIdentifier: String(describing: T.self), for: indexPath) as? T 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Example/JonContextMenu-Example/JonContextMenu-Example/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 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Example/JonContextMenu-Example/JonContextMenu-Example/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // JonContextMenu-Example 4 | // 5 | // Created by Jonathan Martins on 10/09/2018. 6 | // Copyright © 2018 Surrey. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import JonContextMenu 11 | 12 | class ExampleCell: UITableViewCell{ 13 | 14 | // Cell's text nuber 15 | let squareTitle: UILabel = { 16 | let label = UILabel() 17 | label.textAlignment = .center 18 | label.backgroundColor = UIColor.init(hexString: "#1976d2") 19 | label.font = UIFont.systemFont(ofSize: 14, weight: .bold) 20 | label.translatesAutoresizingMaskIntoConstraints = false 21 | return label 22 | }() 23 | 24 | // Cell's text info 25 | let title: UILabel = { 26 | let label = UILabel() 27 | label.font = UIFont.systemFont(ofSize: 14, weight: .medium) 28 | label.translatesAutoresizingMaskIntoConstraints = false 29 | return label 30 | }() 31 | 32 | /// Adds the constraints to the views in this cell 33 | private func setupConstraints(){ 34 | self.contentView.addSubview(title) 35 | self.contentView.addSubview(squareTitle) 36 | NSLayoutConstraint.activate([ 37 | squareTitle.widthAnchor .constraint(equalToConstant: 50), 38 | squareTitle.heightAnchor .constraint(equalToConstant: 50), 39 | squareTitle.topAnchor .constraint(equalTo: self.contentView.topAnchor , constant: 12), 40 | squareTitle.bottomAnchor .constraint(equalTo: self.contentView.bottomAnchor , constant: -12), 41 | squareTitle.leadingAnchor .constraint(equalTo: self.contentView.leadingAnchor, constant: 12), 42 | 43 | title.centerYAnchor .constraint(equalTo: squareTitle.centerYAnchor), 44 | title.leadingAnchor .constraint(equalTo: squareTitle.trailingAnchor, constant: 12), 45 | title.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -12) 46 | ]) 47 | } 48 | 49 | /// Configures the cell 50 | func configureCell(_ index:Int, title:String, contextMenu:JonContextMenu){ 51 | self.title.text = title 52 | self.squareTitle.text = "\(index)" 53 | self.addGestureRecognizer(contextMenu.build()) 54 | } 55 | 56 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 57 | super.init(style: style, reuseIdentifier: reuseIdentifier) 58 | selectionStyle = .none 59 | setupConstraints() 60 | } 61 | 62 | required init?(coder aDecoder: NSCoder) { 63 | fatalError("init(coder:) has not been implemented") 64 | } 65 | } 66 | 67 | class ViewController: UITableViewController { 68 | 69 | private let items = ["DEFAULT menu", 70 | "CUSTOM menu 1", 71 | "CUSTOM menu 2", 72 | "CUSTOM menu 3", 73 | "8 items and delegate"] 74 | 75 | private var options:[JonItem] = [] 76 | 77 | override func viewDidLoad() { 78 | super.viewDidLoad() 79 | setupTableViewController() 80 | } 81 | 82 | private func setupTableViewController(){ 83 | self.title = "Example" 84 | 85 | if #available(iOS 11.0, *) { 86 | self.navigationController?.navigationBar.prefersLargeTitles = true 87 | } 88 | self.tableView.registerCell(ExampleCell.self) 89 | self.tableView.tableFooterView = UIView() 90 | } 91 | 92 | /// Converts a valid hexidecimal String value into a colour 93 | private func getColor(_ color:String)->UIColor{ 94 | return UIColor.init(hexString: color) 95 | } 96 | 97 | override func numberOfSections(in tableView: UITableView) -> Int { 98 | return 1 99 | } 100 | 101 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 102 | return items.count 103 | } 104 | 105 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 106 | guard let cell = tableView.getCell(indexPath, ExampleCell.self) else { 107 | return UITableViewCell() 108 | } 109 | 110 | let blue = getColor("#1976d2") 111 | let green = getColor("#388e3c") 112 | let orange = getColor("#e64a19") 113 | 114 | options = [JonItem(id: 1, title: "Google" , icon: UIImage(named:"google")), 115 | JonItem(id: 2, title: "Twitter" , icon: UIImage(named:"twitter")), 116 | JonItem(id: 3, title: "Facebook" , icon: UIImage(named:"facebook")), 117 | JonItem(id: 4, title: "Instagram", icon: UIImage(named:"instagram"))] 118 | 119 | var contextMenu:JonContextMenu! 120 | switch indexPath.row { 121 | case 1: 122 | contextMenu = JonContextMenu() 123 | .setItems(options) 124 | .setBackgroundColorTo(green) 125 | .setItemsDefaultColorTo(blue) 126 | .setItemsActiveColorTo(orange) 127 | .setItemsTitleColorTo(.black) 128 | case 2: 129 | contextMenu = JonContextMenu() 130 | .setItems(options) 131 | .setBackgroundColorTo(orange) 132 | .setItemsDefaultColorTo(green) 133 | .setItemsActiveColorTo(blue) 134 | .setItemsTitleColorTo(.white) 135 | case 3: 136 | contextMenu = JonContextMenu() 137 | .setItems(options) 138 | .setBackgroundColorTo(blue) 139 | .setItemsDefaultColorTo(orange) 140 | .setItemsActiveColorTo(green) 141 | .setItemsTitleColorTo(.white) 142 | .setItemsTitleSizeTo(32) 143 | case 4: 144 | options = [JonItem(id: 1, title: "Google" , icon: UIImage(named:"google")), 145 | JonItem(id: 2, title: "Twitter" , icon: UIImage(named:"twitter")), 146 | JonItem(id: 3, title: "Facebook" , icon: UIImage(named:"facebook")), 147 | JonItem(id: 4, title: "Instagram", icon: UIImage(named:"instagram")), 148 | JonItem(id: 5, title: "Google" , icon: UIImage(named:"google")), 149 | JonItem(id: 6, title: "Twitter" , icon: UIImage(named:"twitter")), 150 | JonItem(id: 7, title: "Facebook" , icon: UIImage(named:"facebook")), 151 | JonItem(id: 8, title: "Instagram", icon: UIImage(named:"instagram"))] 152 | 153 | contextMenu = JonContextMenu() 154 | .setItems(options) 155 | .setDelegate(self) 156 | .setIconsDefaultColorTo(UIColor.init(hexString: "#212121")) 157 | default: 158 | contextMenu = JonContextMenu() 159 | .setItems(options) 160 | .setIconsDefaultColorTo(UIColor.init(hexString: "#212121")) 161 | } 162 | 163 | cell.configureCell(indexPath.row, title: items[indexPath.row], contextMenu: contextMenu) 164 | return cell 165 | } 166 | } 167 | 168 | extension ViewController: JonContextMenuDelegate{ 169 | func menuOpened() { 170 | print("Menu opened") 171 | } 172 | 173 | func menuClosed() { 174 | print("Menu closed") 175 | } 176 | 177 | func menuItemWasSelected(item: JonItem) { 178 | let alert = UIAlertController(title: "Menu Item Was Selected", message: "You selected ''\(item.title)'' option!", preferredStyle: .alert) 179 | alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil)) 180 | self.present(alert, animated: true) 181 | } 182 | 183 | func menuItemWasActivated(item: JonItem) { 184 | print("Item \(item.title) was activated") 185 | } 186 | 187 | func menuItemWasDeactivated(item: JonItem) { 188 | print("Item \(item.title) was activated") 189 | } 190 | } 191 | 192 | -------------------------------------------------------------------------------- /JonContextMenu.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "JonContextMenu" 3 | s.version = "1.0.0" 4 | s.summary = "A beautiful and minimalist arc menu like the Pinterest one, written in Swift " 5 | s.description = "A beautiful and minimalist arc menu like the Pinterest one, written in Swift. Allows you to add up to 8 items and customize it to the way you like!" 6 | s.homepage = "https://github.com/jonSurrey/JonContextMenu" 7 | s.screenshots = "https://raw.githubusercontent.com/jonSurrey/JonContextMenu/master/Example-1.PNG", "https://raw.githubusercontent.com/jonSurrey/JonContextMenu/master/Example-2.PNG", "https://raw.githubusercontent.com/jonSurrey/JonContextMenu/master/Example-3.PNG" 8 | s.license = { :type => "MIT", :file => "LICENSE" } 9 | s.author = { "Jonathan Martins" => "jon.martinsu@gamil.com" } 10 | s.platform = :ios 11 | s.ios.deployment_target = "9.0" 12 | s.source = { :git => "https://github.com/jonSurrey/JonContextMenu.git", :tag => "#{s.version}"} 13 | s.source_files = "JonContextMenu/JonContextMenu/**/*.{swift}" 14 | s.swift_version = "4.1" 15 | s.requires_arc = true 16 | 17 | 18 | end 19 | -------------------------------------------------------------------------------- /JonContextMenu/JonContextMenu.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 7438C02E2146277500F313E2 /* JonContextMenu.h in Headers */ = {isa = PBXBuildFile; fileRef = 7438C02C2146277500F313E2 /* JonContextMenu.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | 7438C036214627C100F313E2 /* JonContextMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7438C035214627C100F313E2 /* JonContextMenuView.swift */; }; 12 | 7438C038214627D400F313E2 /* JonContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7438C037214627D400F313E2 /* JonContextMenu.swift */; }; 13 | 7438C03C2146280B00F313E2 /* JonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7438C03B2146280B00F313E2 /* JonItem.swift */; }; 14 | 7438C0402146296500F313E2 /* UIColor+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7438C03E2146282B00F313E2 /* UIColor+Utils.swift */; }; 15 | 7438C0412146296800F313E2 /* UIView+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7438C03F2146282B00F313E2 /* UIView+Utils.swift */; }; 16 | 746171E8214771620084F8D6 /* UIImage+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 746171E7214771620084F8D6 /* UIImage+Utils.swift */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | 7438C0292146277500F313E2 /* JonContextMenu.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = JonContextMenu.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 21 | 7438C02C2146277500F313E2 /* JonContextMenu.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JonContextMenu.h; sourceTree = ""; }; 22 | 7438C02D2146277500F313E2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 23 | 7438C035214627C100F313E2 /* JonContextMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JonContextMenuView.swift; sourceTree = ""; }; 24 | 7438C037214627D400F313E2 /* JonContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JonContextMenu.swift; sourceTree = ""; }; 25 | 7438C03B2146280B00F313E2 /* JonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JonItem.swift; sourceTree = ""; }; 26 | 7438C03E2146282B00F313E2 /* UIColor+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Utils.swift"; sourceTree = ""; }; 27 | 7438C03F2146282B00F313E2 /* UIView+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Utils.swift"; sourceTree = ""; }; 28 | 746171E7214771620084F8D6 /* UIImage+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Utils.swift"; sourceTree = ""; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | 7438C0252146277500F313E2 /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | ); 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXFrameworksBuildPhase section */ 40 | 41 | /* Begin PBXGroup section */ 42 | 7438C01F2146277500F313E2 = { 43 | isa = PBXGroup; 44 | children = ( 45 | 7438C02B2146277500F313E2 /* JonContextMenu */, 46 | 7438C02A2146277500F313E2 /* Products */, 47 | ); 48 | sourceTree = ""; 49 | }; 50 | 7438C02A2146277500F313E2 /* Products */ = { 51 | isa = PBXGroup; 52 | children = ( 53 | 7438C0292146277500F313E2 /* JonContextMenu.framework */, 54 | ); 55 | name = Products; 56 | sourceTree = ""; 57 | }; 58 | 7438C02B2146277500F313E2 /* JonContextMenu */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 7438C0342146279E00F313E2 /* Extension */, 62 | 7438C02C2146277500F313E2 /* JonContextMenu.h */, 63 | 7438C02D2146277500F313E2 /* Info.plist */, 64 | 7438C03B2146280B00F313E2 /* JonItem.swift */, 65 | 7438C037214627D400F313E2 /* JonContextMenu.swift */, 66 | 7438C035214627C100F313E2 /* JonContextMenuView.swift */, 67 | ); 68 | path = JonContextMenu; 69 | sourceTree = ""; 70 | }; 71 | 7438C0342146279E00F313E2 /* Extension */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | 7438C03E2146282B00F313E2 /* UIColor+Utils.swift */, 75 | 7438C03F2146282B00F313E2 /* UIView+Utils.swift */, 76 | 746171E7214771620084F8D6 /* UIImage+Utils.swift */, 77 | ); 78 | path = Extension; 79 | sourceTree = ""; 80 | }; 81 | /* End PBXGroup section */ 82 | 83 | /* Begin PBXHeadersBuildPhase section */ 84 | 7438C0262146277500F313E2 /* Headers */ = { 85 | isa = PBXHeadersBuildPhase; 86 | buildActionMask = 2147483647; 87 | files = ( 88 | 7438C02E2146277500F313E2 /* JonContextMenu.h in Headers */, 89 | ); 90 | runOnlyForDeploymentPostprocessing = 0; 91 | }; 92 | /* End PBXHeadersBuildPhase section */ 93 | 94 | /* Begin PBXNativeTarget section */ 95 | 7438C0282146277500F313E2 /* JonContextMenu */ = { 96 | isa = PBXNativeTarget; 97 | buildConfigurationList = 7438C0312146277500F313E2 /* Build configuration list for PBXNativeTarget "JonContextMenu" */; 98 | buildPhases = ( 99 | 7438C0242146277500F313E2 /* Sources */, 100 | 7438C0252146277500F313E2 /* Frameworks */, 101 | 7438C0262146277500F313E2 /* Headers */, 102 | 7438C0272146277500F313E2 /* Resources */, 103 | ); 104 | buildRules = ( 105 | ); 106 | dependencies = ( 107 | ); 108 | name = JonContextMenu; 109 | productName = JonContextMenu; 110 | productReference = 7438C0292146277500F313E2 /* JonContextMenu.framework */; 111 | productType = "com.apple.product-type.framework"; 112 | }; 113 | /* End PBXNativeTarget section */ 114 | 115 | /* Begin PBXProject section */ 116 | 7438C0202146277500F313E2 /* Project object */ = { 117 | isa = PBXProject; 118 | attributes = { 119 | LastUpgradeCheck = 1000; 120 | ORGANIZATIONNAME = Surrey; 121 | TargetAttributes = { 122 | 7438C0282146277500F313E2 = { 123 | CreatedOnToolsVersion = 9.0; 124 | LastSwiftMigration = 1000; 125 | ProvisioningStyle = Automatic; 126 | }; 127 | }; 128 | }; 129 | buildConfigurationList = 7438C0232146277500F313E2 /* Build configuration list for PBXProject "JonContextMenu" */; 130 | compatibilityVersion = "Xcode 8.0"; 131 | developmentRegion = en; 132 | hasScannedForEncodings = 0; 133 | knownRegions = ( 134 | en, 135 | ); 136 | mainGroup = 7438C01F2146277500F313E2; 137 | productRefGroup = 7438C02A2146277500F313E2 /* Products */; 138 | projectDirPath = ""; 139 | projectRoot = ""; 140 | targets = ( 141 | 7438C0282146277500F313E2 /* JonContextMenu */, 142 | ); 143 | }; 144 | /* End PBXProject section */ 145 | 146 | /* Begin PBXResourcesBuildPhase section */ 147 | 7438C0272146277500F313E2 /* Resources */ = { 148 | isa = PBXResourcesBuildPhase; 149 | buildActionMask = 2147483647; 150 | files = ( 151 | ); 152 | runOnlyForDeploymentPostprocessing = 0; 153 | }; 154 | /* End PBXResourcesBuildPhase section */ 155 | 156 | /* Begin PBXSourcesBuildPhase section */ 157 | 7438C0242146277500F313E2 /* Sources */ = { 158 | isa = PBXSourcesBuildPhase; 159 | buildActionMask = 2147483647; 160 | files = ( 161 | 7438C036214627C100F313E2 /* JonContextMenuView.swift in Sources */, 162 | 7438C038214627D400F313E2 /* JonContextMenu.swift in Sources */, 163 | 7438C0412146296800F313E2 /* UIView+Utils.swift in Sources */, 164 | 7438C03C2146280B00F313E2 /* JonItem.swift in Sources */, 165 | 7438C0402146296500F313E2 /* UIColor+Utils.swift in Sources */, 166 | 746171E8214771620084F8D6 /* UIImage+Utils.swift in Sources */, 167 | ); 168 | runOnlyForDeploymentPostprocessing = 0; 169 | }; 170 | /* End PBXSourcesBuildPhase section */ 171 | 172 | /* Begin XCBuildConfiguration section */ 173 | 7438C02F2146277500F313E2 /* Debug */ = { 174 | isa = XCBuildConfiguration; 175 | buildSettings = { 176 | ALWAYS_SEARCH_USER_PATHS = NO; 177 | CLANG_ANALYZER_NONNULL = YES; 178 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 179 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 180 | CLANG_CXX_LIBRARY = "libc++"; 181 | CLANG_ENABLE_MODULES = YES; 182 | CLANG_ENABLE_OBJC_ARC = YES; 183 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 184 | CLANG_WARN_BOOL_CONVERSION = YES; 185 | CLANG_WARN_COMMA = YES; 186 | CLANG_WARN_CONSTANT_CONVERSION = YES; 187 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 188 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 189 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 190 | CLANG_WARN_EMPTY_BODY = YES; 191 | CLANG_WARN_ENUM_CONVERSION = YES; 192 | CLANG_WARN_INFINITE_RECURSION = YES; 193 | CLANG_WARN_INT_CONVERSION = YES; 194 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 195 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 196 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 197 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 198 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 199 | CLANG_WARN_STRICT_PROTOTYPES = YES; 200 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 201 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 202 | CLANG_WARN_UNREACHABLE_CODE = YES; 203 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 204 | CODE_SIGN_IDENTITY = "iPhone Developer"; 205 | COPY_PHASE_STRIP = NO; 206 | CURRENT_PROJECT_VERSION = 1; 207 | DEBUG_INFORMATION_FORMAT = dwarf; 208 | ENABLE_STRICT_OBJC_MSGSEND = YES; 209 | ENABLE_TESTABILITY = YES; 210 | GCC_C_LANGUAGE_STANDARD = gnu11; 211 | GCC_DYNAMIC_NO_PIC = NO; 212 | GCC_NO_COMMON_BLOCKS = YES; 213 | GCC_OPTIMIZATION_LEVEL = 0; 214 | GCC_PREPROCESSOR_DEFINITIONS = ( 215 | "DEBUG=1", 216 | "$(inherited)", 217 | ); 218 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 219 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 220 | GCC_WARN_UNDECLARED_SELECTOR = YES; 221 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 222 | GCC_WARN_UNUSED_FUNCTION = YES; 223 | GCC_WARN_UNUSED_VARIABLE = YES; 224 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 225 | MTL_ENABLE_DEBUG_INFO = YES; 226 | ONLY_ACTIVE_ARCH = YES; 227 | SDKROOT = iphoneos; 228 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 229 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 230 | VERSIONING_SYSTEM = "apple-generic"; 231 | VERSION_INFO_PREFIX = ""; 232 | }; 233 | name = Debug; 234 | }; 235 | 7438C0302146277500F313E2 /* Release */ = { 236 | isa = XCBuildConfiguration; 237 | buildSettings = { 238 | ALWAYS_SEARCH_USER_PATHS = NO; 239 | CLANG_ANALYZER_NONNULL = YES; 240 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 241 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 242 | CLANG_CXX_LIBRARY = "libc++"; 243 | CLANG_ENABLE_MODULES = YES; 244 | CLANG_ENABLE_OBJC_ARC = YES; 245 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 246 | CLANG_WARN_BOOL_CONVERSION = YES; 247 | CLANG_WARN_COMMA = YES; 248 | CLANG_WARN_CONSTANT_CONVERSION = YES; 249 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 250 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 251 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 252 | CLANG_WARN_EMPTY_BODY = YES; 253 | CLANG_WARN_ENUM_CONVERSION = YES; 254 | CLANG_WARN_INFINITE_RECURSION = YES; 255 | CLANG_WARN_INT_CONVERSION = YES; 256 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 257 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 258 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 259 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 260 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 261 | CLANG_WARN_STRICT_PROTOTYPES = YES; 262 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 263 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 264 | CLANG_WARN_UNREACHABLE_CODE = YES; 265 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 266 | CODE_SIGN_IDENTITY = "iPhone Developer"; 267 | COPY_PHASE_STRIP = NO; 268 | CURRENT_PROJECT_VERSION = 1; 269 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 270 | ENABLE_NS_ASSERTIONS = NO; 271 | ENABLE_STRICT_OBJC_MSGSEND = YES; 272 | GCC_C_LANGUAGE_STANDARD = gnu11; 273 | GCC_NO_COMMON_BLOCKS = YES; 274 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 275 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 276 | GCC_WARN_UNDECLARED_SELECTOR = YES; 277 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 278 | GCC_WARN_UNUSED_FUNCTION = YES; 279 | GCC_WARN_UNUSED_VARIABLE = YES; 280 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 281 | MTL_ENABLE_DEBUG_INFO = NO; 282 | SDKROOT = iphoneos; 283 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 284 | VALIDATE_PRODUCT = YES; 285 | VERSIONING_SYSTEM = "apple-generic"; 286 | VERSION_INFO_PREFIX = ""; 287 | }; 288 | name = Release; 289 | }; 290 | 7438C0322146277500F313E2 /* Debug */ = { 291 | isa = XCBuildConfiguration; 292 | buildSettings = { 293 | CLANG_ENABLE_MODULES = YES; 294 | CODE_SIGN_IDENTITY = ""; 295 | CODE_SIGN_STYLE = Automatic; 296 | DEFINES_MODULE = YES; 297 | DEVELOPMENT_TEAM = NA36627892; 298 | DYLIB_COMPATIBILITY_VERSION = 1; 299 | DYLIB_CURRENT_VERSION = 1; 300 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 301 | INFOPLIST_FILE = JonContextMenu/Info.plist; 302 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 303 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 304 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 305 | PRODUCT_BUNDLE_IDENTIFIER = com.surrey.JonContextMenu; 306 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 307 | SKIP_INSTALL = YES; 308 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 309 | SWIFT_VERSION = 4.2; 310 | TARGETED_DEVICE_FAMILY = "1,2"; 311 | }; 312 | name = Debug; 313 | }; 314 | 7438C0332146277500F313E2 /* Release */ = { 315 | isa = XCBuildConfiguration; 316 | buildSettings = { 317 | CLANG_ENABLE_MODULES = YES; 318 | CODE_SIGN_IDENTITY = ""; 319 | CODE_SIGN_STYLE = Automatic; 320 | DEFINES_MODULE = YES; 321 | DEVELOPMENT_TEAM = NA36627892; 322 | DYLIB_COMPATIBILITY_VERSION = 1; 323 | DYLIB_CURRENT_VERSION = 1; 324 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 325 | INFOPLIST_FILE = JonContextMenu/Info.plist; 326 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 327 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 328 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 329 | PRODUCT_BUNDLE_IDENTIFIER = com.surrey.JonContextMenu; 330 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 331 | SKIP_INSTALL = YES; 332 | SWIFT_VERSION = 4.2; 333 | TARGETED_DEVICE_FAMILY = "1,2"; 334 | }; 335 | name = Release; 336 | }; 337 | /* End XCBuildConfiguration section */ 338 | 339 | /* Begin XCConfigurationList section */ 340 | 7438C0232146277500F313E2 /* Build configuration list for PBXProject "JonContextMenu" */ = { 341 | isa = XCConfigurationList; 342 | buildConfigurations = ( 343 | 7438C02F2146277500F313E2 /* Debug */, 344 | 7438C0302146277500F313E2 /* Release */, 345 | ); 346 | defaultConfigurationIsVisible = 0; 347 | defaultConfigurationName = Release; 348 | }; 349 | 7438C0312146277500F313E2 /* Build configuration list for PBXNativeTarget "JonContextMenu" */ = { 350 | isa = XCConfigurationList; 351 | buildConfigurations = ( 352 | 7438C0322146277500F313E2 /* Debug */, 353 | 7438C0332146277500F313E2 /* Release */, 354 | ); 355 | defaultConfigurationIsVisible = 0; 356 | defaultConfigurationName = Release; 357 | }; 358 | /* End XCConfigurationList section */ 359 | }; 360 | rootObject = 7438C0202146277500F313E2 /* Project object */; 361 | } 362 | -------------------------------------------------------------------------------- /JonContextMenu/JonContextMenu.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /JonContextMenu/JonContextMenu.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /JonContextMenu/JonContextMenu.xcodeproj/project.xcworkspace/xcuserdata/jonathanmartins.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonSurrey/JonContextMenu/b81f5647637a36246545cc8e57c5586827b09fab/JonContextMenu/JonContextMenu.xcodeproj/project.xcworkspace/xcuserdata/jonathanmartins.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /JonContextMenu/JonContextMenu.xcodeproj/xcuserdata/jonathanmartins.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | JonContextMenu.xcscheme 8 | 9 | orderHint 10 | 1 11 | 12 | JonContextMenu.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 1 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /JonContextMenu/JonContextMenu.xcodeproj/xcuserdata/jussi.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | JonContextMenu.xcscheme 8 | 9 | orderHint 10 | 1 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /JonContextMenu/JonContextMenu/Extension/UIColor+Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+Utils.swift 3 | // Pedida de Hoje 4 | // 5 | // Created by Jonathan Martins on 01/06/2018. 6 | // Copyright © 2018 Jussi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension UIColor { 12 | 13 | /// Takes an Hexadecimal String value and converts to UIColor 14 | convenience init(hexString: String) { 15 | let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) 16 | var int = UInt32() 17 | Scanner(string: hex).scanHexInt32(&int) 18 | let a, r, g, b: UInt32 19 | 20 | switch hex.count { 21 | case 3: // RGB (12-bit) 22 | (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) 23 | case 6: // RGB (24-bit) 24 | (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) 25 | case 8: // ARGB (32-bit) 26 | (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) 27 | default: 28 | (a, r, g, b) = (255, 0, 0, 0) 29 | } 30 | self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /JonContextMenu/JonContextMenu/Extension/UIImage+Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Utils.swift 3 | // JonContextMenu 4 | // 5 | // Created by Jonathan Martins on 11/09/2018. 6 | // Copyright © 2018 Surrey. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Foundation 11 | 12 | extension UIImage{ 13 | 14 | func resize(to newSize: CGFloat)-> UIImage?{ 15 | let scale = newSize / self.size.width 16 | let newHeight = self.size.height * scale 17 | UIGraphicsBeginImageContext(CGSize(width: newSize, height: newHeight)) 18 | self.draw(in: CGRect(x: 0, y: 0, width: newSize, height: newHeight)) 19 | let newImage = UIGraphicsGetImageFromCurrentImageContext() 20 | UIGraphicsEndImageContext() 21 | 22 | return newImage 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /JonContextMenu/JonContextMenu/Extension/UIView+Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Utils.swift 3 | // Pedida de Hoje 4 | // 5 | // Created by Jonathan Martins on 22/06/18. 6 | // Copyright © 2018 Jussi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Foundation 11 | 12 | extension UIView { 13 | 14 | var cornerRadius: CGFloat { 15 | get { 16 | return layer.cornerRadius 17 | } 18 | set { 19 | layer.cornerRadius = newValue 20 | layer.masksToBounds = newValue > 0 21 | } 22 | } 23 | 24 | var fullCircle: Bool { 25 | get{ 26 | return layer.cornerRadius == 0 ? false:true 27 | } 28 | set { 29 | if newValue { 30 | layer.cornerRadius = bounds.size.width/2 31 | } else { 32 | layer.cornerRadius = 0 33 | } 34 | } 35 | } 36 | 37 | var borderWidth: CGFloat{ 38 | get { 39 | return layer.borderWidth 40 | } 41 | set{ 42 | layer.borderWidth = newValue 43 | layer.masksToBounds = newValue > 0 44 | } 45 | } 46 | 47 | var borderColor: UIColor?{ 48 | get { 49 | return UIColor(cgColor: layer.borderColor!) 50 | } 51 | set{ 52 | layer.borderColor = newValue?.cgColor 53 | } 54 | } 55 | 56 | func addDropShadow() { 57 | layer.masksToBounds = false 58 | layer.shadowColor = UIColor.black.cgColor 59 | layer.shadowOpacity = 0.6 60 | layer.shadowOffset = CGSize(width: 0, height: 2) 61 | layer.shadowRadius = 2 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /JonContextMenu/JonContextMenu/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 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /JonContextMenu/JonContextMenu/JonContextMenu.h: -------------------------------------------------------------------------------- 1 | // 2 | // JonContextMenu.h 3 | // JonContextMenu 4 | // 5 | // Created by Jonathan Martins on 10/09/2018. 6 | // Copyright © 2018 Surrey. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for JonContextMenu. 12 | FOUNDATION_EXPORT double JonContextMenuVersionNumber; 13 | 14 | //! Project version string for JonContextMenu. 15 | FOUNDATION_EXPORT const unsigned char JonContextMenuVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /JonContextMenu/JonContextMenu/JonContextMenu.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JonContextMenuView.swift 3 | // JonContextMenu 4 | // 5 | // Created by Jonathan Martins on 10/09/2018. 6 | // Copyright © 2018 Surrey. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Foundation 11 | import UIKit.UIGestureRecognizerSubclass 12 | 13 | @objc public protocol JonContextMenuDelegate{ 14 | func menuOpened() 15 | func menuClosed() 16 | func menuItemWasSelected(item:JonItem) 17 | func menuItemWasActivated(item:JonItem) 18 | func menuItemWasDeactivated(item:JonItem) 19 | } 20 | 21 | @objc open class JonContextMenu:NSObject{ 22 | 23 | /// The items to be displayed 24 | var items:[JonItem] = [] 25 | 26 | /// The delegate to notify the JonContextMenu host when an item is selected 27 | weak var delegate:JonContextMenuDelegate? 28 | 29 | /// The Background's alpha of the view 30 | var backgroundAlpha:CGFloat = 0.9 31 | 32 | /// The Background's colour of the view 33 | var backgroundColor:UIColor = .white 34 | 35 | /// The items' buttons default colour 36 | var buttonsDefaultColor:UIColor = .white 37 | 38 | /// The items' buttons active colour 39 | var buttonsActiveColor:UIColor = UIColor.init(hexString: "#c62828") // Red 40 | 41 | /// The items' icons default colour 42 | var iconsDefaultColor:UIColor? 43 | 44 | /// The items' icons active colour 45 | var iconsActiveColor:UIColor? 46 | 47 | /// The size of the title of the menu items 48 | var itemsTitleSize:CGFloat = 54 49 | 50 | /// The colour of the title of the menu items 51 | var itemsTitleColor:UIColor = UIColor.init(hexString: "#212121") // Dark Gray 52 | 53 | /// The colour of the touch location view 54 | var touchPointColor:UIColor = UIColor.init(hexString: "#212121") // Dark Gray 55 | 56 | /// The view selected by the user 57 | var highlightedView:UIView! 58 | 59 | override public init(){ 60 | super.init() 61 | } 62 | 63 | /// Sets the items for the JonContextMenu 64 | @objc open func setItems(_ items: [JonItem])->JonContextMenu{ 65 | self.items = items 66 | return self 67 | } 68 | 69 | /// Sets the delegate for the JonContextMenu 70 | @objc open func setDelegate(_ delegate: JonContextMenuDelegate?)->JonContextMenu{ 71 | self.delegate = delegate 72 | return self 73 | } 74 | 75 | /// Sets the background of the JonContextMenu 76 | @objc open func setBackgroundColorTo(_ backgroundColor: UIColor, withAlpha alpha:CGFloat = 0.9)->JonContextMenu{ 77 | self.backgroundAlpha = alpha 78 | self.backgroundColor = backgroundColor 79 | return self 80 | } 81 | 82 | /// Sets the colour of the buttons for when there is no interaction 83 | @objc open func setItemsDefaultColorTo(_ colour: UIColor)->JonContextMenu{ 84 | self.buttonsDefaultColor = colour 85 | return self 86 | } 87 | 88 | /// Sets the colour of the buttons for when there is interaction 89 | @objc open func setItemsActiveColorTo(_ colour: UIColor)->JonContextMenu{ 90 | self.buttonsActiveColor = colour 91 | return self 92 | } 93 | 94 | /// Sets the colour of the icons for when there is no interaction 95 | @objc open func setIconsDefaultColorTo(_ colour: UIColor?)->JonContextMenu{ 96 | self.iconsDefaultColor = colour 97 | return self 98 | } 99 | 100 | /// Sets the colour of the icons for when there is interaction 101 | @objc open func setIconsActiveColorTo(_ colour: UIColor?)->JonContextMenu{ 102 | self.iconsActiveColor = colour 103 | return self 104 | } 105 | 106 | /// Sets the colour of the JonContextMenu items title 107 | @objc open func setItemsTitleColorTo(_ color: UIColor)->JonContextMenu{ 108 | self.itemsTitleColor = color 109 | return self 110 | } 111 | 112 | /// Sets the size of the JonContextMenu items title 113 | @objc open func setItemsTitleSizeTo(_ size: CGFloat)->JonContextMenu{ 114 | self.itemsTitleSize = size 115 | return self 116 | } 117 | 118 | /// Sets the colour of the JonContextMenu touch point 119 | @objc open func setTouchPointColorTo(_ color: UIColor)->JonContextMenu{ 120 | self.touchPointColor = color 121 | return self 122 | } 123 | 124 | /// Builds the JonContextMenu 125 | @objc open func build()->Builder{ 126 | return Builder(self) 127 | } 128 | 129 | @objc open class Builder:UILongPressGestureRecognizer{ 130 | 131 | /// The wrapper for the JonContextMenu 132 | private var window:UIWindow! 133 | 134 | /// The selected menu item 135 | private var currentItem:JonItem? 136 | 137 | /// The JonContextMenu view 138 | private var contextMenuView:JonContextMenuView! 139 | 140 | /// The properties configuration to add to the JonContextMenu view 141 | private var properties:JonContextMenu! 142 | 143 | /// Indicates if there is a menu item active 144 | private var isItemActive = false 145 | 146 | @objc init(_ properties:JonContextMenu){ 147 | super.init(target: nil, action: nil) 148 | guard let window = UIApplication.shared.keyWindow else{ 149 | fatalError("No access to UIApplication Window") 150 | } 151 | self.window = window 152 | self.properties = properties 153 | addTarget(self, action: #selector(setupTouchAction)) 154 | } 155 | 156 | /// Gets a copy of the touched view to add to the Window 157 | private func getTouchedView(){ 158 | let highlightedView = self.view!.snapshotView(afterScreenUpdates: true)! 159 | highlightedView.frame = self.view!.superview!.convert(self.view!.frame, to: nil) 160 | highlightedView.borderWidth = 0.5 161 | highlightedView.borderColor = .lightGray 162 | properties.highlightedView = highlightedView 163 | } 164 | 165 | /// Handle the touch events on the view 166 | @objc private func setupTouchAction(){ 167 | let location = self.location(in: window) 168 | switch self.state { 169 | case .began: 170 | longPressBegan(on: location) 171 | case .changed: 172 | longPressMoved(to: location) 173 | case .ended: 174 | longPressEnded() 175 | case .cancelled: 176 | longPressCancelled() 177 | default: 178 | break 179 | } 180 | } 181 | 182 | /// Trigger the events for when the touch begins 183 | private func longPressBegan(on location:CGPoint) { 184 | getTouchedView() 185 | showMenu(on: location) 186 | } 187 | 188 | // Triggers the events for when the touch ends 189 | private func longPressEnded() { 190 | if let currentItem = currentItem, currentItem.isActive{ 191 | properties.delegate?.menuItemWasSelected(item: currentItem) 192 | } 193 | dismissMenu() 194 | } 195 | 196 | // Triggers the events for when the touch is cancelled 197 | private func longPressCancelled() { 198 | dismissMenu() 199 | } 200 | 201 | // Triggers the events for when the touch moves 202 | private func longPressMoved(to location:CGPoint) { 203 | if let currentItem = currentItem, currentItem.frame.contains(location){ 204 | if !currentItem.isActive{ 205 | contextMenuView.activate(currentItem) 206 | properties.delegate?.menuItemWasActivated(item: currentItem) 207 | } 208 | } 209 | else{ 210 | if let currentItem = currentItem, currentItem.isActive{ 211 | contextMenuView.deactivate(currentItem) 212 | properties.delegate?.menuItemWasDeactivated(item: currentItem) 213 | } 214 | for item in properties.items{ 215 | if item.frame.contains(location){ 216 | currentItem = item 217 | break 218 | } 219 | } 220 | } 221 | } 222 | 223 | /// Creates the JonContextMenu view and adds to the Window 224 | private func showMenu(on location:CGPoint){ 225 | currentItem = nil 226 | contextMenuView = JonContextMenuView(properties, touchPoint: location) 227 | 228 | window.addSubview(contextMenuView) 229 | properties.delegate?.menuOpened() 230 | } 231 | 232 | /// Removes the JonContextMenu view from the Window 233 | private func dismissMenu(){ 234 | if let currentItem = currentItem{ 235 | contextMenuView.deactivate(currentItem) 236 | } 237 | 238 | contextMenuView.removeFromSuperview() 239 | properties.delegate?.menuClosed() 240 | contextMenuView = nil 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /JonContextMenu/JonContextMenu/JonContextMenuView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JonContextMenu.swift 3 | // JonContextMenu 4 | // 5 | // Created by Jonathan Martins on 10/09/2018. 6 | // Copyright © 2018 Surrey. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Foundation 11 | 12 | class JonContextMenuView:UIView { 13 | 14 | /// Enum to map the direction of the touch 15 | enum Direction { 16 | case left 17 | case right 18 | case middle 19 | case up 20 | case down 21 | } 22 | 23 | /// The title label that display the name of the touched icon 24 | let label: UILabel = { 25 | let label = UILabel() 26 | label.alpha = 0.0 27 | label.numberOfLines = 1 28 | label.textAlignment = .left 29 | label.adjustsFontSizeToFitWidth = true 30 | return label 31 | }() 32 | 33 | /// The background of the view 34 | let background: UIView = { 35 | let view = UIView() 36 | view.translatesAutoresizingMaskIntoConstraints = false 37 | return view 38 | }() 39 | 40 | /// The view that represents the users's touch point 41 | var touchPointView:UIView! 42 | 43 | /// The properties configuration for the JonContextMenuView 44 | private var properties:JonContextMenu! 45 | 46 | /// The distance between the touch point and the menu items 47 | private let distanceToTouchPoint:CGFloat = 25 48 | 49 | /// The coordinates the the user touched on the screen 50 | private var touchPoint:CGPoint! 51 | 52 | /// The X distance from the menu items to the touched point 53 | private var xDistanceToItem:CGFloat! 54 | 55 | /// The Y distance from the menu items to the touched point 56 | private var yDistanceToItem:CGFloat! 57 | 58 | /// The direction that the items are supposed to appear 59 | private var currentDirection: (Direction, Direction)! 60 | 61 | required init?(coder aDecoder: NSCoder) { 62 | fatalError("init(coder:) has not been implemented") 63 | } 64 | 65 | override init(frame: CGRect) { 66 | super.init(frame: frame) 67 | } 68 | 69 | init(_ properties:JonContextMenu, touchPoint:CGPoint) { 70 | super.init(frame: UIScreen.main.bounds) 71 | self.properties = properties 72 | self.touchPoint = touchPoint 73 | 74 | touchPointView = makeTouchPoint() 75 | currentDirection = calculateDirections(properties.items[0].wrapper.frame.width) 76 | 77 | addSubviews() 78 | configureViews() 79 | displayView() 80 | } 81 | 82 | /// Add the background, touch point and title label to the ContextMenuView 83 | private func addSubviews(){ 84 | 85 | self.addSubview(background) 86 | NSLayoutConstraint.activate([ 87 | background.topAnchor .constraint(equalTo: self.topAnchor ), 88 | background.bottomAnchor .constraint(equalTo: self.bottomAnchor ), 89 | background.leadingAnchor .constraint(equalTo: self.leadingAnchor ), 90 | background.trailingAnchor.constraint(equalTo: self.trailingAnchor) 91 | ]) 92 | 93 | self.addSubview(properties.highlightedView) 94 | self.addSubview(touchPointView) 95 | self.addSubview(label) 96 | } 97 | 98 | /// Configure the views to start with the properties set in the JonContextMenu class 99 | private func configureViews(){ 100 | 101 | /// Sets the default colour of the items 102 | properties.items.forEach({ 103 | $0.setItemColorTo(properties.buttonsDefaultColor, iconColor: properties.iconsDefaultColor) 104 | }) 105 | 106 | /// Sets the touch point colour 107 | touchPointView.borderColor = properties.touchPointColor 108 | 109 | /// Sets the view's background alpha 110 | background.alpha = properties.backgroundAlpha 111 | 112 | /// Sets the views's background colour 113 | background.backgroundColor = properties.backgroundColor 114 | 115 | /// Sets the items' title colour 116 | label.textColor = properties.itemsTitleColor 117 | 118 | /// Sets the size of the items' title 119 | label.font = UIFont.systemFont(ofSize: properties.itemsTitleSize, weight: .heavy) 120 | } 121 | 122 | /// Shows the ContextMenu 123 | private func displayView(){ 124 | 125 | calculateDistanceToItem() 126 | resetItemsPosition() 127 | anglesForDirection() 128 | 129 | for item in properties.items { 130 | self.addSubview(item) 131 | animateItem(item) 132 | } 133 | } 134 | 135 | /// Creates the touch point view 136 | private func makeTouchPoint()->UIView{ 137 | let view = UIView(frame: CGRect(x: 0, y: 0, width: 45, height: 45)) 138 | view.center = touchPoint 139 | view.backgroundColor = .clear 140 | view.fullCircle = true 141 | view.borderWidth = 3 142 | view.alpha = 0.1 143 | return view 144 | } 145 | 146 | /// Sets the menu items' position to the user's touch position 147 | private func resetItemsPosition() { 148 | properties.items.forEach({ 149 | $0.center = touchPoint 150 | }) 151 | } 152 | 153 | /// Calculates the distance from the user's touch location to the menu items 154 | private func calculateDistanceToItem() { 155 | xDistanceToItem = touchPointView.frame.width/2 + distanceToTouchPoint + CGFloat(properties.items[0].frame.width/2) 156 | yDistanceToItem = touchPointView.frame.height/2 + distanceToTouchPoint + CGFloat(properties.items[0].frame.height/2) 157 | } 158 | 159 | /// Calculates which direction the menu items shoud appear 160 | private func calculateDirections(_ menuItemWidth: CGFloat) -> (Direction, Direction) { 161 | 162 | let touchWidth = distanceToTouchPoint + menuItemWidth + touchPointView.frame.width 163 | let touchHeight = distanceToTouchPoint + menuItemWidth + touchPointView.frame.height 164 | 165 | let verticalDirection = determineVerticalDirection (touchHeight) 166 | let horisontalDirection = determineHorisontalDirection(touchWidth ) 167 | 168 | return(verticalDirection, horisontalDirection) 169 | } 170 | 171 | /// Calculates the vertical point that the user touched on the screen 172 | private func determineVerticalDirection(_ size: CGFloat) -> Direction { 173 | 174 | let isBotomBorderOfScreen = touchPoint.y + size > UIScreen.main.bounds.height 175 | let isTopBorderOfScreen = touchPoint.y - size < 0 176 | 177 | if isTopBorderOfScreen { 178 | return .down 179 | } else if isBotomBorderOfScreen { 180 | return .up 181 | } else { 182 | return .middle 183 | } 184 | } 185 | 186 | /// Calculates the horizontal point that the user touched on the screen 187 | private func determineHorisontalDirection(_ size: CGFloat) -> Direction { 188 | 189 | let isRightBorderOfScreen = touchPoint.x + size > UIScreen.main.bounds.width 190 | let isLeftBorderOfScreen = touchPoint.x - size < 0 191 | 192 | if isLeftBorderOfScreen { 193 | return .right 194 | } else if isRightBorderOfScreen { 195 | return .left 196 | } else { 197 | return .middle 198 | } 199 | } 200 | 201 | /// Calculates which angle the menu items should appear 202 | private func anglesForDirection() { 203 | guard let direction = currentDirection else { 204 | return 205 | } 206 | print(direction) 207 | switch (direction) { 208 | case (.down, .right): 209 | positiveQuorterAngle(startAngle: 0) 210 | break 211 | case (.down, .middle): 212 | positiveQuorterAngle(startAngle: 90) 213 | break 214 | case (.middle, .right): 215 | positiveQuorterAngle(startAngle: 270) 216 | break 217 | case (.down, .left): 218 | negativeQuorterAngle(startAngle: 180) 219 | break 220 | case (.up, .right): 221 | negativeQuorterAngle(startAngle: 0) 222 | break 223 | case (.up, .middle), (.up, .left), (.middle,.middle): 224 | positiveQuorterAngle(startAngle: 180) 225 | break 226 | case (.middle, .left): 227 | positiveQuorterAngle(startAngle: 135) 228 | break 229 | default: 230 | break 231 | } 232 | } 233 | 234 | /// Calculates the clockwise angle that each of the menu items should appear based on the given start angle 235 | private func positiveQuorterAngle(startAngle: CGFloat) { 236 | properties.items.forEach({ item in 237 | let index = CGFloat(properties.items.index(of: item)!) 238 | item.angle = (startAngle + 45 * index) 239 | }) 240 | } 241 | 242 | /// Calculates the counterclockwise angle that each of the menu items should appear based on the given start angle 243 | private func negativeQuorterAngle(startAngle: CGFloat) { 244 | properties.items.forEach({ item in 245 | let index = CGFloat(properties.items.index(of: item)!) 246 | item.angle = (startAngle - 45 * index) 247 | }) 248 | } 249 | 250 | /// Caculates the final position of the mennu items 251 | private func calculatePointCoordiantes(_ angle: CGFloat) -> CGPoint { 252 | let x = (touchPoint.x + CGFloat(__cospi(Double(angle/180))) * xDistanceToItem) 253 | let y = (touchPoint.y + CGFloat(__sinpi(Double(angle/180))) * yDistanceToItem) 254 | return CGPoint(x: x, y: y) 255 | } 256 | 257 | /// Animates the menu items to appear at the calculated positions 258 | private func animateItem(_ item: JonItem) { 259 | UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 0.4, initialSpringVelocity: 1, options: [], animations: { 260 | item.center = self.calculatePointCoordiantes(item.angle) 261 | }, completion: nil) 262 | } 263 | 264 | /// Activates the selected menu item 265 | func activate(_ item:JonItem){ 266 | 267 | item.isActive = true 268 | item.setItemColorTo(properties.buttonsActiveColor, iconColor: properties.iconsActiveColor) 269 | 270 | let newX = (item.wrapper.center.x + CGFloat(__cospi(Double(item.angle/180))) * 25) 271 | let newY = (item.wrapper.center.y + CGFloat(__sinpi(Double(item.angle/180))) * 25) 272 | 273 | showLabel(with: item.title) 274 | UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 0.4, initialSpringVelocity: 1, options: [], animations: { 275 | self.label.alpha = 1.0 276 | item.wrapper.center = CGPoint(x: newX, y: newY) 277 | item.wrapper.transform = CGAffineTransform(scaleX: 1.2, y: 1.2) 278 | }, completion: nil) 279 | } 280 | 281 | /// Deactivate the item 282 | func deactivate(_ item:JonItem){ 283 | 284 | item.isActive = false 285 | item.setItemColorTo(properties.buttonsDefaultColor, iconColor: properties.iconsDefaultColor) 286 | 287 | let newX = (item.wrapper.center.x + CGFloat(__cospi(Double(item.angle/180))) * -25) 288 | let newY = (item.wrapper.center.y + CGFloat(__sinpi(Double(item.angle/180))) * -25) 289 | 290 | UIView.animate(withDuration: 0.2, animations: { 291 | self.label.alpha = 0.0 292 | item.wrapper.center = CGPoint(x: newX, y: newY) 293 | item.wrapper.transform = CGAffineTransform.identity 294 | }) 295 | } 296 | 297 | /// Calculates where the label should appear 298 | private func showLabel(with title:String){ 299 | self.label.text = title 300 | 301 | let labelWidth = self.label.intrinsicContentSize.width 302 | let labelHeight = self.label.intrinsicContentSize.height 303 | 304 | let labelSize = CGSize(width: labelWidth, height: labelHeight) 305 | var labelOrigin = CGPoint() 306 | 307 | if touchPoint.x > UIScreen.main.bounds.width/2{ // Align on the left 308 | self.label.textAlignment = .left 309 | labelOrigin.x = calculateLabelLeftPosition(labelWidth) 310 | } 311 | else{ // Align on the right 312 | self.label.textAlignment = .right 313 | labelOrigin.x = calculateLabelRightPosition(labelWidth) 314 | } 315 | 316 | if touchPoint.y > UIScreen.main.bounds.height/2.7{ //Show Label at the top 317 | let topItem = properties.items.min(by: { (a, b) -> Bool in 318 | return a.center.y < b.center.y 319 | }) 320 | labelOrigin.y = topItem!.center.y - (labelHeight + 50) 321 | } 322 | else{ // Show Label at the bottom 323 | let bottomItem = properties.items.max(by: { (a, b) -> Bool in 324 | return a.center.y < b.center.y 325 | }) 326 | labelOrigin.y = bottomItem!.center.y + 50 327 | } 328 | label.frame = CGRect(origin: labelOrigin, size: labelSize) 329 | } 330 | 331 | /// Calculates where on the leftside of the screen the label should be placed 332 | /// Obs: before calling this function the touchPoint.x must be > than the half of the screen 333 | private func calculateLabelLeftPosition(_ labelWidth:CGFloat)->CGFloat{ 334 | if touchPoint.x > (labelWidth + 30){ 335 | return touchPoint.x - (labelWidth + 30) 336 | } 337 | else{ 338 | return 10 339 | } 340 | } 341 | 342 | /// Calculates where on the rightside of the screen the label should be placed 343 | /// Obs: before calling this function the touchPoint.x must be < than the half of the screen 344 | private func calculateLabelRightPosition(_ labelWidth:CGFloat)->CGFloat{ 345 | if (UIScreen.main.bounds.width - (touchPoint.x + 100)) > labelWidth{ 346 | return touchPoint.x + 100 347 | } 348 | else{ 349 | return 10 350 | } 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /JonContextMenu/JonContextMenu/JonItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JonItem.swift 3 | // JonContextMenu 4 | // 5 | // Created by Jonathan Martins on 10/09/2018. 6 | // Copyright © 2018 Surrey. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Foundation 11 | 12 | @objc open class JonItem:UIView { 13 | 14 | private static let vWidth = 45 15 | private static let vHeight = 45 16 | 17 | /// The id of the item 18 | open var id:NSInteger? 19 | 20 | /// The title of the item 21 | @objc open var title:String = "" 22 | 23 | /// The data that the item holds 24 | @objc open var data:Any? 25 | 26 | /// The angle that the item will appear 27 | var angle: CGFloat = 0 28 | 29 | /// Indicates if the item is active 30 | var isActive:Bool = false 31 | 32 | /// The icon of the button 33 | private let icon: UIImageView = { 34 | let icon = UIImageView() 35 | icon.contentMode = .scaleAspectFit 36 | icon.translatesAutoresizingMaskIntoConstraints = false 37 | return icon 38 | }() 39 | 40 | /// The button that represents the item 41 | private let button: UIButton = { 42 | let button = UIButton(frame: CGRect(x: 0, y: 0 , width: JonItem.vWidth, height: JonItem.vHeight)) 43 | button.fullCircle = true 44 | button.addDropShadow() 45 | button.translatesAutoresizingMaskIntoConstraints = false 46 | return button 47 | }() 48 | 49 | /// The warpper for the button and icon 50 | let wrapper: UIView = { 51 | let view = UIView(frame: CGRect(x: 0, y: 0 , width: JonItem.vWidth, height: JonItem.vHeight)) 52 | view.translatesAutoresizingMaskIntoConstraints = false 53 | return view 54 | }() 55 | 56 | /// Adds the constraints to the views 57 | private func setupConstraints(){ 58 | 59 | wrapper.addSubview(button) 60 | wrapper.addSubview(icon) 61 | self.addSubview(wrapper) 62 | NSLayoutConstraint.activate([ 63 | icon.heightAnchor .constraint(equalToConstant: 20), 64 | icon.widthAnchor .constraint(equalToConstant: 20), 65 | icon.centerYAnchor.constraint(equalTo: button.centerYAnchor), 66 | icon.centerXAnchor.constraint(equalTo: button.centerXAnchor), 67 | 68 | button.topAnchor .constraint(equalTo: wrapper.topAnchor ), 69 | button.bottomAnchor .constraint(equalTo: wrapper.bottomAnchor ), 70 | button.leadingAnchor .constraint(equalTo: wrapper.leadingAnchor ), 71 | button.trailingAnchor.constraint(equalTo: wrapper.trailingAnchor), 72 | 73 | wrapper.topAnchor .constraint(equalTo: self.topAnchor ), 74 | wrapper.bottomAnchor .constraint(equalTo: self.bottomAnchor ), 75 | wrapper.leadingAnchor .constraint(equalTo: self.leadingAnchor ), 76 | wrapper.trailingAnchor.constraint(equalTo: self.trailingAnchor) 77 | ]) 78 | } 79 | 80 | @objc public init(id:NSInteger, title:String, icon:UIImage?, data:Any?=nil){ 81 | super.init(frame: CGRect(x: 0, y: 0, width: JonItem.vWidth, height: JonItem.vHeight)) 82 | self.id = id 83 | self.data = data 84 | self.title = title 85 | self.icon.image = icon 86 | setupConstraints() 87 | } 88 | 89 | override init(frame: CGRect) { 90 | super.init(frame: CGRect(x: 0, y: 0, width: JonItem.vWidth, height: JonItem.vHeight)) 91 | setupConstraints() 92 | } 93 | 94 | required public init?(coder aDecoder: NSCoder) { 95 | fatalError("init(coder:) has not been implemented") 96 | } 97 | 98 | /// Changes the colours of the button and the icon 99 | @objc func setItemColorTo(_ itemColour:UIColor, iconColor:UIColor?=nil){ 100 | if let colour = iconColor{ 101 | let templateImage = icon.image?.withRenderingMode(.alwaysTemplate) 102 | icon.image = templateImage 103 | icon.tintColor = colour 104 | } 105 | else{ 106 | let templateImage = icon.image?.withRenderingMode(.alwaysOriginal) 107 | icon.image = templateImage 108 | icon.tintColor = nil 109 | } 110 | button.backgroundColor = itemColour 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jonathan Martins 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 | 2 | JonContextMenu 3 | =========== 4 | [![CocoaPods Compatible](https://img.shields.io/badge/pod-1.0.0-red.svg)](https://github.com/jonSurrey/JonContextMenu) 5 | [![License](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/jonSurrey/JonContextMenu/blob/master/LICENSE) 6 | 7 | A beautiful and minimalist arc menu like the Pinterest one, written in Swift. Allows you to add up to 8 items and customize it to the way you like! 8 | 9 | ![Gif](https://media.giphy.com/media/31UCkk6Hs1bMVb2bo7/giphy.gif) 10 | 11 | ### How to use 12 | 13 | ```swift 14 | // First import the library to your class file. 15 | import JonContextMenu 16 | 17 | // Create an array of type "JonItem" for the items you desire to show. 18 | // "JonItem" constructor takes an id to identify the object, a String to be the title of the item and 19 | // an UIImage to be the icon of the item. 20 | let items = [JonItem(id: 1, title: "Google" , icon: UIImage(named:"google")), 21 | JonItem(id: 2, title: "Twitter" , icon: UIImage(named:"twitter")), 22 | JonItem(id: 3, title: "Facebook" , icon: UIImage(named:"facebook")), 23 | JonItem(id: 4, title: "Instagram", icon: UIImage(named:"instagram"))] 24 | ``` 25 | 26 | The JonItem contructor has also a fourth optional parameter called "data". You can use it to add some object that you would like to have access later when selecting this item 27 | 28 | ```swift 29 | JonItem(id: 1, title: "Google", icon: UIImage(named:"google", data: <>) 30 | ``` 31 | 32 | ```swift 33 | // Then, create an instance of JonContextMenu setting your array to it. 34 | let contextMenu = JonContextMenu().setItems(options).build() 35 | 36 | // Finally, add the "contextMenu" to the view you wish. 37 | <>.addGestureRecognizer(contextMenu) 38 | ``` 39 | To show the menu, just long press the view you set the JonContextMenu. 40 | 41 | ### Delegates 42 | 43 | If you want to be notified when some interaction happens on the context menu, JonContextMenu has with 5 functions that you can implemement: 44 | 45 | 46 | ```swift 47 | // Implement the JonContextMenuDelegate to your ViewController 48 | class ViewController:JonContextMenuDelegate{ 49 | 50 | func menuOpened(){ 51 | // Called when the JonContextMenu opens 52 | } 53 | func menuClosed(){ 54 | // Called when the JonContextMenu closes 55 | } 56 | func menuItemWasSelected(item:JonItem){ 57 | // Called when the user selects one of the items 58 | } 59 | func menuItemWasActivated(item:JonItem){ 60 | // Called when the user interacts with one of the items 61 | } 62 | func menuItemWasDeactivated(item:JonItem){ 63 | // Called when the user stops interacting with one of the items 64 | } 65 | } 66 | 67 | // Then, when creating an instance of JonContextMenu, set the delegate. 68 | let contextMenu = JonContextMenu() 69 | .setItems(options) 70 | .setDelegate(self) 71 | .build() 72 | ``` 73 | 74 | Custom Configuration 75 | =========== 76 | 77 | The default visual configuration for JonContextMenu will probably be enough for you, but if you wish to make a few custom modification, the library has some functions that allow you to personalize it as you wish. 78 | 79 | ```swift 80 | JonContextMenu() 81 | 82 | // The number of items to be displayed, it can be up to 8 items. 83 | .setItems(options) 84 | 85 | // The delegate to notify when an item is selected. 86 | .setDelegate(self) 87 | 88 | // The background colour of the view. The default colour is white and the default alpha is 0.9 89 | .setBackgroundColorTo(.white, withAlpha: 0.5) 90 | 91 | // The idle colour of the items(when there is no interaction). The default colour is white 92 | .setItemsDefaultColorTo(.black) 93 | 94 | // The idle colour of the items' icon(when there is no interaction). There is no deafault colour. 95 | // If you don't set it, the icon will have no tint colour and will display the original image's colour 96 | .setIconsDefaultColorTo(.white) 97 | 98 | // The active colour of the items(when there is interaction). The default colour is darkRed 99 | .setItemsActiveColorTo(.white) 100 | 101 | // The active colour of the items' icon(when there is interaction). There is no deafault colour. 102 | // If you don't set it, the icon will have no tint colour and will display the original image's colour 103 | .setIconsActiveColorTo(.black) 104 | 105 | // The colour of the title of the items. The default colour is darkGray 106 | .setItemsTitleColorTo(.black) 107 | 108 | // The size of the title of the items. The default size is 72 109 | .setItemsTitleSizeTo(50) 110 | 111 | // The colour of touch point circle. The default colour is darkGray 112 | .setTouchPointColorTo(.black) 113 | ``` 114 | Installation 115 | =========== 116 | 117 | ### CocoaPods 118 | 119 | To integrate JonContextMenu to your project using CocoaPods, specify it in your `Podfile`: 120 | 121 | ```ruby 122 | target '' do 123 | pod 'JonContextMenu', :git => 'https://github.com/jonSurrey/JonContextMenu.git', :branch => 'master' 124 | end 125 | ``` 126 | 127 | Then, run the following command: 128 | 129 | ```bash 130 | $ pod install 131 | ``` 132 | 133 | ### Manual installation 134 | 135 | First download the "JonContextMenu" folder. Then, in Xcode, right-click on the root of your project node in the project navigator. Click "Add Files" to “YOURPROJECTNAME”. In the file chooser, navigate to where "JonContextMenu" folder is and select JonContextMenu.xcodeproj. Click "Add" to add JonContextMenu.xcodeproj as a sub-project. 136 | 137 | Select the top level of your project node to open the project editor. Click the YOUR_PROJECT_NAME target, and then go to the General tab. 138 | 139 | Scroll down to the Embedded Binaries section. Drag JonContextMenu.framework from the Products folder of JonContextMenu.xcodeproj onto this section. 140 | 141 | Clean and rebuild your project, and you should be good to go! 142 | 143 | Collaboration 144 | =========== 145 | 146 | This is a simple libray that I created to help myself in my own work since I didnt find any other that would do what I wanted. I know that there is still here a lot of space for improvements and adding new features, so please, any ideas or issues, feel free to collaborate! 147 | 148 | ## Author 149 | 150 | Jonathan Martins, jon.martinsu@gmail.com 151 | 152 | ## License 153 | 154 | JonContextMenu is available under the MIT license. See the LICENSE file for more info. 155 | 156 | --------------------------------------------------------------------------------