├── .gitignore ├── ESSSubtitledMenuItemView_Demo ├── ESSSubtitledMenuItemView_Demo.xcodeproj │ └── project.pbxproj └── ESSSubtitledMenuItemView_Demo │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ └── Main.storyboard │ ├── ESSMainViewController.h │ ├── ESSMainViewController.m │ ├── ESSSubtitledMenuItemView.h │ ├── ESSSubtitledMenuItemView.m │ ├── ESSSubtitledMenuItemView_Demo.entitlements │ ├── Info.plist │ └── main.m └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | # CocoaPods 34 | # 35 | # We recommend against adding the Pods directory to your .gitignore. However 36 | # you should judge for yourself, the pros and cons are mentioned at: 37 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 38 | # 39 | # Pods/ 40 | # 41 | # Add this line if you want to avoid checking in source code from the Xcode workspace 42 | # *.xcworkspace 43 | 44 | # Carthage 45 | # 46 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 47 | # Carthage/Checkouts 48 | 49 | Carthage/Build/ 50 | 51 | # fastlane 52 | # 53 | # It is recommended to not store the screenshots in the git repo. 54 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 55 | # For more information about the recommended setup visit: 56 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 57 | 58 | fastlane/report.xml 59 | fastlane/Preview.html 60 | fastlane/screenshots/**/*.png 61 | fastlane/test_output 62 | 63 | # Code Injection 64 | # 65 | # After new code Injection tools there's a generated folder /iOSInjectionProject 66 | # https://github.com/johnno1962/injectionforxcode 67 | 68 | iOSInjectionProject/ 69 | -------------------------------------------------------------------------------- /ESSSubtitledMenuItemView_Demo/ESSSubtitledMenuItemView_Demo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3920747425FCB993006995D4 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 3920747325FCB993006995D4 /* AppDelegate.m */; }; 11 | 3920747725FCB993006995D4 /* ESSMainViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3920747625FCB993006995D4 /* ESSMainViewController.m */; }; 12 | 3920747925FCB995006995D4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3920747825FCB995006995D4 /* Assets.xcassets */; }; 13 | 3920747C25FCB995006995D4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3920747A25FCB995006995D4 /* Main.storyboard */; }; 14 | 3920747F25FCB995006995D4 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 3920747E25FCB995006995D4 /* main.m */; }; 15 | 3920748925FCB9B4006995D4 /* ESSSubtitledMenuItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3920748725FCB9B3006995D4 /* ESSSubtitledMenuItemView.m */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXFileReference section */ 19 | 3920746F25FCB993006995D4 /* ESSSubtitledMenuItemView_Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ESSSubtitledMenuItemView_Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 20 | 3920747225FCB993006995D4 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 21 | 3920747325FCB993006995D4 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 22 | 3920747525FCB993006995D4 /* ESSMainViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ESSMainViewController.h; sourceTree = ""; }; 23 | 3920747625FCB993006995D4 /* ESSMainViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ESSMainViewController.m; sourceTree = ""; }; 24 | 3920747825FCB995006995D4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 25 | 3920747B25FCB995006995D4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 26 | 3920747D25FCB995006995D4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 27 | 3920747E25FCB995006995D4 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 28 | 3920748025FCB995006995D4 /* ESSSubtitledMenuItemView_Demo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ESSSubtitledMenuItemView_Demo.entitlements; sourceTree = ""; }; 29 | 3920748725FCB9B3006995D4 /* ESSSubtitledMenuItemView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ESSSubtitledMenuItemView.m; sourceTree = ""; }; 30 | 3920748825FCB9B4006995D4 /* ESSSubtitledMenuItemView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ESSSubtitledMenuItemView.h; sourceTree = ""; }; 31 | /* End PBXFileReference section */ 32 | 33 | /* Begin PBXFrameworksBuildPhase section */ 34 | 3920746C25FCB993006995D4 /* Frameworks */ = { 35 | isa = PBXFrameworksBuildPhase; 36 | buildActionMask = 2147483647; 37 | files = ( 38 | ); 39 | runOnlyForDeploymentPostprocessing = 0; 40 | }; 41 | /* End PBXFrameworksBuildPhase section */ 42 | 43 | /* Begin PBXGroup section */ 44 | 3920746625FCB993006995D4 = { 45 | isa = PBXGroup; 46 | children = ( 47 | 3920747125FCB993006995D4 /* ESSSubtitledMenuItemView_Demo */, 48 | 3920747025FCB993006995D4 /* Products */, 49 | ); 50 | sourceTree = ""; 51 | }; 52 | 3920747025FCB993006995D4 /* Products */ = { 53 | isa = PBXGroup; 54 | children = ( 55 | 3920746F25FCB993006995D4 /* ESSSubtitledMenuItemView_Demo.app */, 56 | ); 57 | name = Products; 58 | sourceTree = ""; 59 | }; 60 | 3920747125FCB993006995D4 /* ESSSubtitledMenuItemView_Demo */ = { 61 | isa = PBXGroup; 62 | children = ( 63 | 3920748825FCB9B4006995D4 /* ESSSubtitledMenuItemView.h */, 64 | 3920748725FCB9B3006995D4 /* ESSSubtitledMenuItemView.m */, 65 | 3920747A25FCB995006995D4 /* Main.storyboard */, 66 | 3920747525FCB993006995D4 /* ESSMainViewController.h */, 67 | 3920747625FCB993006995D4 /* ESSMainViewController.m */, 68 | 3920748B25FCBA20006995D4 /* Irrelevant files for this demo */, 69 | ); 70 | path = ESSSubtitledMenuItemView_Demo; 71 | sourceTree = ""; 72 | }; 73 | 3920748B25FCBA20006995D4 /* Irrelevant files for this demo */ = { 74 | isa = PBXGroup; 75 | children = ( 76 | 3920747225FCB993006995D4 /* AppDelegate.h */, 77 | 3920747325FCB993006995D4 /* AppDelegate.m */, 78 | 3920747825FCB995006995D4 /* Assets.xcassets */, 79 | 3920747D25FCB995006995D4 /* Info.plist */, 80 | 3920747E25FCB995006995D4 /* main.m */, 81 | 3920748025FCB995006995D4 /* ESSSubtitledMenuItemView_Demo.entitlements */, 82 | ); 83 | name = "Irrelevant files for this demo"; 84 | sourceTree = ""; 85 | }; 86 | /* End PBXGroup section */ 87 | 88 | /* Begin PBXNativeTarget section */ 89 | 3920746E25FCB993006995D4 /* ESSSubtitledMenuItemView_Demo */ = { 90 | isa = PBXNativeTarget; 91 | buildConfigurationList = 3920748325FCB995006995D4 /* Build configuration list for PBXNativeTarget "ESSSubtitledMenuItemView_Demo" */; 92 | buildPhases = ( 93 | 3920746B25FCB993006995D4 /* Sources */, 94 | 3920746C25FCB993006995D4 /* Frameworks */, 95 | 3920746D25FCB993006995D4 /* Resources */, 96 | ); 97 | buildRules = ( 98 | ); 99 | dependencies = ( 100 | ); 101 | name = ESSSubtitledMenuItemView_Demo; 102 | productName = ESSSubtitledMenuItemView_Demo; 103 | productReference = 3920746F25FCB993006995D4 /* ESSSubtitledMenuItemView_Demo.app */; 104 | productType = "com.apple.product-type.application"; 105 | }; 106 | /* End PBXNativeTarget section */ 107 | 108 | /* Begin PBXProject section */ 109 | 3920746725FCB993006995D4 /* Project object */ = { 110 | isa = PBXProject; 111 | attributes = { 112 | LastUpgradeCheck = 1240; 113 | TargetAttributes = { 114 | 3920746E25FCB993006995D4 = { 115 | CreatedOnToolsVersion = 12.4; 116 | }; 117 | }; 118 | }; 119 | buildConfigurationList = 3920746A25FCB993006995D4 /* Build configuration list for PBXProject "ESSSubtitledMenuItemView_Demo" */; 120 | compatibilityVersion = "Xcode 9.3"; 121 | developmentRegion = en; 122 | hasScannedForEncodings = 0; 123 | knownRegions = ( 124 | en, 125 | Base, 126 | ); 127 | mainGroup = 3920746625FCB993006995D4; 128 | productRefGroup = 3920747025FCB993006995D4 /* Products */; 129 | projectDirPath = ""; 130 | projectRoot = ""; 131 | targets = ( 132 | 3920746E25FCB993006995D4 /* ESSSubtitledMenuItemView_Demo */, 133 | ); 134 | }; 135 | /* End PBXProject section */ 136 | 137 | /* Begin PBXResourcesBuildPhase section */ 138 | 3920746D25FCB993006995D4 /* Resources */ = { 139 | isa = PBXResourcesBuildPhase; 140 | buildActionMask = 2147483647; 141 | files = ( 142 | 3920747925FCB995006995D4 /* Assets.xcassets in Resources */, 143 | 3920747C25FCB995006995D4 /* Main.storyboard in Resources */, 144 | ); 145 | runOnlyForDeploymentPostprocessing = 0; 146 | }; 147 | /* End PBXResourcesBuildPhase section */ 148 | 149 | /* Begin PBXSourcesBuildPhase section */ 150 | 3920746B25FCB993006995D4 /* Sources */ = { 151 | isa = PBXSourcesBuildPhase; 152 | buildActionMask = 2147483647; 153 | files = ( 154 | 3920747725FCB993006995D4 /* ESSMainViewController.m in Sources */, 155 | 3920747F25FCB995006995D4 /* main.m in Sources */, 156 | 3920747425FCB993006995D4 /* AppDelegate.m in Sources */, 157 | 3920748925FCB9B4006995D4 /* ESSSubtitledMenuItemView.m in Sources */, 158 | ); 159 | runOnlyForDeploymentPostprocessing = 0; 160 | }; 161 | /* End PBXSourcesBuildPhase section */ 162 | 163 | /* Begin PBXVariantGroup section */ 164 | 3920747A25FCB995006995D4 /* Main.storyboard */ = { 165 | isa = PBXVariantGroup; 166 | children = ( 167 | 3920747B25FCB995006995D4 /* Base */, 168 | ); 169 | name = Main.storyboard; 170 | sourceTree = ""; 171 | }; 172 | /* End PBXVariantGroup section */ 173 | 174 | /* Begin XCBuildConfiguration section */ 175 | 3920748125FCB995006995D4 /* Debug */ = { 176 | isa = XCBuildConfiguration; 177 | buildSettings = { 178 | ALWAYS_SEARCH_USER_PATHS = NO; 179 | CLANG_ANALYZER_NONNULL = YES; 180 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 181 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 182 | CLANG_CXX_LIBRARY = "libc++"; 183 | CLANG_ENABLE_MODULES = YES; 184 | CLANG_ENABLE_OBJC_ARC = YES; 185 | CLANG_ENABLE_OBJC_WEAK = YES; 186 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 187 | CLANG_WARN_BOOL_CONVERSION = YES; 188 | CLANG_WARN_COMMA = YES; 189 | CLANG_WARN_CONSTANT_CONVERSION = YES; 190 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 191 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 192 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 193 | CLANG_WARN_EMPTY_BODY = YES; 194 | CLANG_WARN_ENUM_CONVERSION = YES; 195 | CLANG_WARN_INFINITE_RECURSION = YES; 196 | CLANG_WARN_INT_CONVERSION = YES; 197 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 198 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 199 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 200 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 201 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 202 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 203 | CLANG_WARN_STRICT_PROTOTYPES = YES; 204 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 205 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 206 | CLANG_WARN_UNREACHABLE_CODE = YES; 207 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 208 | COPY_PHASE_STRIP = NO; 209 | DEBUG_INFORMATION_FORMAT = dwarf; 210 | ENABLE_STRICT_OBJC_MSGSEND = YES; 211 | ENABLE_TESTABILITY = YES; 212 | GCC_C_LANGUAGE_STANDARD = gnu11; 213 | GCC_DYNAMIC_NO_PIC = NO; 214 | GCC_NO_COMMON_BLOCKS = YES; 215 | GCC_OPTIMIZATION_LEVEL = 0; 216 | GCC_PREPROCESSOR_DEFINITIONS = ( 217 | "DEBUG=1", 218 | "$(inherited)", 219 | ); 220 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 221 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 222 | GCC_WARN_UNDECLARED_SELECTOR = YES; 223 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 224 | GCC_WARN_UNUSED_FUNCTION = YES; 225 | GCC_WARN_UNUSED_VARIABLE = YES; 226 | MACOSX_DEPLOYMENT_TARGET = 10.11; 227 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 228 | MTL_FAST_MATH = YES; 229 | ONLY_ACTIVE_ARCH = YES; 230 | SDKROOT = macosx; 231 | }; 232 | name = Debug; 233 | }; 234 | 3920748225FCB995006995D4 /* Release */ = { 235 | isa = XCBuildConfiguration; 236 | buildSettings = { 237 | ALWAYS_SEARCH_USER_PATHS = NO; 238 | CLANG_ANALYZER_NONNULL = YES; 239 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 240 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 241 | CLANG_CXX_LIBRARY = "libc++"; 242 | CLANG_ENABLE_MODULES = YES; 243 | CLANG_ENABLE_OBJC_ARC = YES; 244 | CLANG_ENABLE_OBJC_WEAK = 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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 261 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 262 | CLANG_WARN_STRICT_PROTOTYPES = YES; 263 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 264 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 265 | CLANG_WARN_UNREACHABLE_CODE = YES; 266 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 267 | COPY_PHASE_STRIP = NO; 268 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 269 | ENABLE_NS_ASSERTIONS = NO; 270 | ENABLE_STRICT_OBJC_MSGSEND = YES; 271 | GCC_C_LANGUAGE_STANDARD = gnu11; 272 | GCC_NO_COMMON_BLOCKS = YES; 273 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 274 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 275 | GCC_WARN_UNDECLARED_SELECTOR = YES; 276 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 277 | GCC_WARN_UNUSED_FUNCTION = YES; 278 | GCC_WARN_UNUSED_VARIABLE = YES; 279 | MACOSX_DEPLOYMENT_TARGET = 10.11; 280 | MTL_ENABLE_DEBUG_INFO = NO; 281 | MTL_FAST_MATH = YES; 282 | SDKROOT = macosx; 283 | }; 284 | name = Release; 285 | }; 286 | 3920748425FCB995006995D4 /* Debug */ = { 287 | isa = XCBuildConfiguration; 288 | buildSettings = { 289 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 290 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 291 | CODE_SIGN_ENTITLEMENTS = ESSSubtitledMenuItemView_Demo/ESSSubtitledMenuItemView_Demo.entitlements; 292 | CODE_SIGN_STYLE = Automatic; 293 | COMBINE_HIDPI_IMAGES = YES; 294 | DEVELOPMENT_TEAM = ""; 295 | ENABLE_HARDENED_RUNTIME = YES; 296 | INFOPLIST_FILE = ESSSubtitledMenuItemView_Demo/Info.plist; 297 | LD_RUNPATH_SEARCH_PATHS = ( 298 | "$(inherited)", 299 | "@executable_path/../Frameworks", 300 | ); 301 | PRODUCT_BUNDLE_IDENTIFIER = "at.EternalStorms.ESSSubtitledMenuItemView-Demo"; 302 | PRODUCT_NAME = "$(TARGET_NAME)"; 303 | }; 304 | name = Debug; 305 | }; 306 | 3920748525FCB995006995D4 /* Release */ = { 307 | isa = XCBuildConfiguration; 308 | buildSettings = { 309 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 310 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 311 | CODE_SIGN_ENTITLEMENTS = ESSSubtitledMenuItemView_Demo/ESSSubtitledMenuItemView_Demo.entitlements; 312 | CODE_SIGN_STYLE = Automatic; 313 | COMBINE_HIDPI_IMAGES = YES; 314 | DEVELOPMENT_TEAM = ""; 315 | ENABLE_HARDENED_RUNTIME = YES; 316 | INFOPLIST_FILE = ESSSubtitledMenuItemView_Demo/Info.plist; 317 | LD_RUNPATH_SEARCH_PATHS = ( 318 | "$(inherited)", 319 | "@executable_path/../Frameworks", 320 | ); 321 | PRODUCT_BUNDLE_IDENTIFIER = "at.EternalStorms.ESSSubtitledMenuItemView-Demo"; 322 | PRODUCT_NAME = "$(TARGET_NAME)"; 323 | }; 324 | name = Release; 325 | }; 326 | /* End XCBuildConfiguration section */ 327 | 328 | /* Begin XCConfigurationList section */ 329 | 3920746A25FCB993006995D4 /* Build configuration list for PBXProject "ESSSubtitledMenuItemView_Demo" */ = { 330 | isa = XCConfigurationList; 331 | buildConfigurations = ( 332 | 3920748125FCB995006995D4 /* Debug */, 333 | 3920748225FCB995006995D4 /* Release */, 334 | ); 335 | defaultConfigurationIsVisible = 0; 336 | defaultConfigurationName = Release; 337 | }; 338 | 3920748325FCB995006995D4 /* Build configuration list for PBXNativeTarget "ESSSubtitledMenuItemView_Demo" */ = { 339 | isa = XCConfigurationList; 340 | buildConfigurations = ( 341 | 3920748425FCB995006995D4 /* Debug */, 342 | 3920748525FCB995006995D4 /* Release */, 343 | ); 344 | defaultConfigurationIsVisible = 0; 345 | defaultConfigurationName = Release; 346 | }; 347 | /* End XCConfigurationList section */ 348 | }; 349 | rootObject = 3920746725FCB993006995D4 /* Project object */; 350 | } 351 | -------------------------------------------------------------------------------- /ESSSubtitledMenuItemView_Demo/ESSSubtitledMenuItemView_Demo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // ESSSubtitledMenuItemView_Demo 4 | // 5 | // Created by Matthias Gansrigler on 13.03.2021. 6 | // 7 | 8 | #import 9 | 10 | @interface AppDelegate : NSObject 11 | 12 | 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /ESSSubtitledMenuItemView_Demo/ESSSubtitledMenuItemView_Demo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // ESSSubtitledMenuItemView_Demo 4 | // 5 | // Created by Matthias Gansrigler on 13.03.2021. 6 | // 7 | 8 | #import "AppDelegate.h" 9 | 10 | @interface AppDelegate () 11 | 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { 18 | // Insert code here to initialize your application 19 | } 20 | 21 | 22 | - (void)applicationWillTerminate:(NSNotification *)aNotification { 23 | // Insert code here to tear down your application 24 | } 25 | 26 | - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender 27 | { 28 | return YES; 29 | } 30 | 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /ESSSubtitledMenuItemView_Demo/ESSSubtitledMenuItemView_Demo/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ESSSubtitledMenuItemView_Demo/ESSSubtitledMenuItemView_Demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "author" : "xcode", 56 | "version" : 1 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /ESSSubtitledMenuItemView_Demo/ESSSubtitledMenuItemView_Demo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ESSSubtitledMenuItemView_Demo/ESSSubtitledMenuItemView_Demo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | Default 531 | 532 | 533 | 534 | 535 | 536 | 537 | Left to Right 538 | 539 | 540 | 541 | 542 | 543 | 544 | Right to Left 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | Default 556 | 557 | 558 | 559 | 560 | 561 | 562 | Left to Right 563 | 564 | 565 | 566 | 567 | 568 | 569 | Right to Left 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | -------------------------------------------------------------------------------- /ESSSubtitledMenuItemView_Demo/ESSSubtitledMenuItemView_Demo/ESSMainViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // ESSSubtitledMenuItemView_Demo 4 | // 5 | // Created by Matthias Gansrigler on 13.03.2021. 6 | // 7 | 8 | #import 9 | 10 | @interface ESSMainViewController : NSViewController 11 | 12 | 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /ESSSubtitledMenuItemView_Demo/ESSSubtitledMenuItemView_Demo/ESSMainViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // ESSSubtitledMenuItemView_Demo 4 | // 5 | // Created by Matthias Gansrigler on 13.03.2021. 6 | // 7 | 8 | #import "ESSMainViewController.h" 9 | #import "ESSSubtitledMenuItemView.h" 10 | #import 11 | 12 | @interface ESSMainViewController () 13 | 14 | @property (strong) IBOutlet NSButton *button; 15 | 16 | - (IBAction)showMenuWithSubtitledItems:(NSButton *)sender; 17 | 18 | @end 19 | 20 | @implementation ESSMainViewController 21 | 22 | - (void)viewDidLoad 23 | { 24 | [super viewDidLoad]; 25 | } 26 | 27 | - (void)showMenuWithSubtitledItems:(NSButton *)sender 28 | { 29 | NSMenu *men = [[NSMenu alloc] initWithTitle:@""]; 30 | 31 | NSImage *menuItemImage = nil; 32 | if (@available(macOS 11.0, *)) 33 | menuItemImage = [NSImage imageWithSystemSymbolName:@"dot.radiowaves.left.and.right" accessibilityDescription:nil]; 34 | 35 | NSUInteger amount = 10; 36 | NSUInteger count = 0; 37 | for (count = 0; count < amount; count++) 38 | { 39 | NSUInteger runningCount = count+1; 40 | 41 | /* 42 | This is where it all happens! 43 | */ 44 | NSMenuItem *item = [[NSMenuItem alloc] init]; 45 | item.target = self; 46 | item.representedObject = @(runningCount); 47 | item.action = @selector(showAlert:); 48 | ESSSubtitledMenuItemView *view = [[ESSSubtitledMenuItemView alloc] initWithTitle:[NSString stringWithFormat:@"Shake %ld time(s)",runningCount] subtitle:[NSString stringWithFormat:@"This will shake the button %ld time(s)",runningCount] image:menuItemImage]; 49 | item.view = view; 50 | 51 | [men addItem:item]; 52 | } 53 | 54 | [men popUpMenuPositioningItem:nil 55 | atLocation:NSMakePoint(sender.bounds.size.width, 56 | sender.flipped ? sender.frame.size.height:0) 57 | inView:sender]; 58 | } 59 | 60 | - (void)showAlert:(NSMenuItem *)sender 61 | { 62 | NSString *previousTitle = self.button.title; 63 | self.button.title = @"Wheeeeeeeeeeee, look at me go!"; 64 | 65 | dispatch_async(dispatch_get_main_queue(), ^{ 66 | CGFloat degreesInRadians = ((120) / (180.0 * M_PI)); 67 | if (self.button.layer.anchorPoint.x < 0.499999 || self.button.layer.anchorPoint.x > 0.50000000001) 68 | { 69 | self.button.layer.anchorPoint = NSMakePoint(0.5, 0.5); 70 | self.button.layer.position = CGPointMake(self.button.layer.position.x+(self.button.frame.size.width/2.0), 71 | self.button.layer.position.y+(self.button.frame.size.height/2.0)); 72 | } 73 | CATransform3D zRotation = self.button.layer.transform; 74 | zRotation = CATransform3DRotate(CATransform3DIdentity, degreesInRadians, 0, 0, 1.0); 75 | CATransform3D zRotationMinus = self.button.layer.transform; 76 | zRotationMinus = CATransform3DRotate(CATransform3DIdentity, -degreesInRadians, 0, 0, 1.0); 77 | CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform"]; 78 | anim.fromValue = [NSValue valueWithCATransform3D:zRotationMinus]; 79 | anim.toValue = [NSValue valueWithCATransform3D:zRotation]; 80 | anim.duration = 0.07; 81 | NSUInteger number = ((NSNumber *)sender.representedObject).integerValue; 82 | anim.repeatCount = number; 83 | anim.removedOnCompletion = YES; 84 | anim.autoreverses = YES; 85 | 86 | [self.button.layer addAnimation:anim forKey:@"shake"]; 87 | 88 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(anim.duration*anim.repeatCount*2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 89 | self.button.title = previousTitle; 90 | }); 91 | }); 92 | } 93 | 94 | @end 95 | -------------------------------------------------------------------------------- /ESSSubtitledMenuItemView_Demo/ESSSubtitledMenuItemView_Demo/ESSSubtitledMenuItemView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ESSSubtitledMenuItemView.h 3 | // 4 | // Created by Matthias Gansrigler on 23.11.2020. 5 | // Copyright © 2020-2021 Eternal Storms Software. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | /*! 11 | @abstract The base class for NSMenuItem.view views. Only provides hover effects, and the execution of the menu item's action on its target. Must be subclassed to be actually useful, as this does not contain any view setup. 12 | */ 13 | API_AVAILABLE(macos(10.11)) 14 | @interface ESSBaseCustomMenuItemView : NSVisualEffectView 15 | 16 | @end 17 | 18 | 19 | 20 | 21 | /*! 22 | @abstract Provides an NSMenuItem.view with an icon, title and subtitle. 23 | */ 24 | API_AVAILABLE(macos(10.11)) 25 | @interface ESSSubtitledMenuItemView : ESSBaseCustomMenuItemView 26 | 27 | @property (readonly) NSString *title; 28 | @property (readonly) NSString *subtitle; 29 | 30 | /*! 31 | @abstract Instantiates a view to use with an @c NSMenuItem object's -view property, showing an icon, title and subtitle. 32 | @param title The title of the menu item. May not be nil. (Otherwise you could just use NSMenuItem itself, without the view) 33 | @param subtitle The subtitle of the menu item. May not be nil. (Otherwise you could just use NSMenuItem itself, without the view) 34 | @param image The image of the menu item. May be nil. 35 | */ 36 | - (instancetype)initWithTitle:(NSString *)title subtitle:(NSString *)subtitle image:(NSImage *)image; 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /ESSSubtitledMenuItemView_Demo/ESSSubtitledMenuItemView_Demo/ESSSubtitledMenuItemView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ESSSubtitledMenuItemView.m 3 | // 4 | // Created by Matthias Gansrigler on 23.11.2020. 5 | // Copyright © 2020-2021 Eternal Storms Software. All rights reserved. 6 | // 7 | 8 | #import "ESSSubtitledMenuItemView.h" 9 | 10 | @interface ESSBaseCustomMenuItemView () 11 | 12 | @end 13 | 14 | @implementation ESSBaseCustomMenuItemView 15 | 16 | - (instancetype)initWithFrame:(NSRect)frameRect 17 | { 18 | if (self = [super initWithFrame:NSMakeRect(0, 0, 1, 1)]) 19 | { 20 | self.material = NSVisualEffectMaterialMenu; 21 | return self; 22 | } 23 | 24 | return nil; 25 | } 26 | 27 | - (BOOL)isEmphasized 28 | { 29 | return YES; 30 | } 31 | 32 | - (NSVisualEffectState)state 33 | { 34 | return NSVisualEffectStateActive; 35 | } 36 | 37 | - (NSVisualEffectBlendingMode)blendingMode 38 | { 39 | return NSVisualEffectBlendingModeBehindWindow; 40 | } 41 | 42 | - (BOOL)allowsVibrancy 43 | { 44 | return NO; 45 | } 46 | 47 | - (void)viewDidMoveToWindow 48 | { 49 | [super viewDidMoveToWindow]; 50 | 51 | while (self.trackingAreas.count != 0) 52 | [self removeTrackingArea:self.trackingAreas[0]]; 53 | 54 | if (self.window == nil) 55 | return; 56 | 57 | NSTrackingArea *ta = [[NSTrackingArea alloc] initWithRect:self.bounds options:NSTrackingActiveAlways|NSTrackingMouseEnteredAndExited|NSTrackingInVisibleRect|NSTrackingEnabledDuringMouseDrag owner:self userInfo:nil]; 58 | [self addTrackingArea:ta]; 59 | } 60 | 61 | - (void)mouseEntered:(NSEvent *)event 62 | { 63 | if (@available(macOS 11.0, *)) 64 | { 65 | self.maskImage = [NSImage imageWithSize:self.bounds.size flipped:NO drawingHandler:^BOOL(NSRect dstRect) { 66 | [[NSColor clearColor] set]; 67 | [NSBezierPath fillRect:dstRect]; 68 | 69 | [[NSColor blackColor] set]; 70 | NSBezierPath *bp = [NSBezierPath bezierPathWithRoundedRect:NSMakeRect(5, 0, dstRect.size.width-10, dstRect.size.height) xRadius:4 yRadius:4]; 71 | [bp fill]; 72 | 73 | return YES; 74 | }]; 75 | } 76 | 77 | self.material = NSVisualEffectMaterialSelection; 78 | } 79 | 80 | - (void)mouseExited:(NSEvent *)event 81 | { 82 | self.material = NSVisualEffectMaterialMenu; 83 | } 84 | 85 | - (void)mouseDown:(NSEvent *)event 86 | { 87 | } 88 | 89 | - (void)mouseUp:(NSEvent *)event 90 | { 91 | if (self.material != NSVisualEffectMaterialSelection) 92 | return; 93 | 94 | self.material = NSVisualEffectMaterialMenu; 95 | [self setNeedsDisplay:YES]; 96 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.06 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 97 | self.material = NSVisualEffectMaterialSelection; 98 | [self setNeedsDisplay:YES]; 99 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.02 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 100 | [self.enclosingMenuItem.menu cancelTracking]; 101 | 102 | if (self.enclosingMenuItem.target != nil && self.enclosingMenuItem.action != nil) 103 | { 104 | NSMethodSignature *sig = [self.enclosingMenuItem.target methodSignatureForSelector:self.enclosingMenuItem.action]; 105 | NSInvocation *inv = [NSInvocation invocationWithMethodSignature:sig]; 106 | inv.target = self.enclosingMenuItem.target; 107 | inv.selector = self.enclosingMenuItem.action; 108 | id obj = self.enclosingMenuItem; 109 | 110 | if (sig.numberOfArguments >= 3) 111 | [inv setArgument:&obj atIndex:2]; 112 | 113 | [inv invokeWithTarget:inv.target]; 114 | } 115 | }); 116 | }); 117 | } 118 | 119 | @end 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | @interface ESSSubtitledMenuItemView () 128 | 129 | @property (strong) NSString *title; 130 | @property (strong) NSString *subtitle; 131 | 132 | @property (strong) NSTextField *titleField; 133 | @property (strong) NSTextField *subtitleField; 134 | 135 | @end 136 | 137 | @implementation ESSSubtitledMenuItemView 138 | 139 | - (instancetype)initWithTitle:(NSString *)title subtitle:(NSString *)subtitle image:(NSImage *)image 140 | { 141 | NSAssert(title.length != 0, @"Title may not be nil - otherwise, use an ordinary NSMenuItem without .view instead"); 142 | NSAssert(subtitle.length != 0, @"Subtitle may not be nil - otherwise, use an ordinary NSMenuItem without .view instead"); 143 | 144 | if (self = [super initWithFrame:NSMakeRect(0, 0, 1, 1)]) 145 | { 146 | self.title = title; 147 | self.subtitle = subtitle; 148 | [self setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal]; 149 | [self setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationVertical]; 150 | [self setContentHuggingPriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal]; 151 | [self setContentHuggingPriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationVertical]; 152 | 153 | NSImage *img = image.copy; 154 | 155 | NSImageView *iv = [[NSImageView alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)]; 156 | iv.image = img; 157 | iv.imageScaling = NSImageScaleProportionallyUpOrDown; 158 | 159 | self.titleField = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)]; 160 | [self.titleField setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal]; 161 | [self.titleField setContentHuggingPriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal]; 162 | self.titleField.stringValue = title; 163 | self.titleField.font = [NSFont menuFontOfSize:12.0]; 164 | self.titleField.textColor = [NSColor labelColor]; 165 | 166 | self.subtitleField = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)]; 167 | [self.subtitleField setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal]; 168 | [self.subtitleField setContentHuggingPriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal]; 169 | self.subtitleField.stringValue = subtitle; 170 | self.subtitleField.font = [NSFont menuFontOfSize:10]; 171 | self.subtitleField.textColor = [NSColor secondaryLabelColor]; 172 | 173 | self.titleField.bordered = self.subtitleField.bordered = NO; 174 | self.titleField.drawsBackground = self.subtitleField.drawsBackground = NO; 175 | self.titleField.editable = self.subtitleField.editable = self.titleField.selectable = self.subtitleField.selectable = NO; 176 | 177 | self.translatesAutoresizingMaskIntoConstraints = iv.translatesAutoresizingMaskIntoConstraints = self.titleField.translatesAutoresizingMaskIntoConstraints = self.subtitleField.translatesAutoresizingMaskIntoConstraints = NO; 178 | 179 | [self addSubview:iv]; 180 | [self addSubview:self.titleField]; 181 | [self addSubview:self.subtitleField]; 182 | 183 | static CGFloat superviewLeadingTrailingDistance = 14.0; 184 | static CGFloat imageWidthAndHeight = 28.0; 185 | 186 | [NSLayoutConstraint activateConstraints:@[[iv.heightAnchor constraintEqualToConstant:imageWidthAndHeight], 187 | [iv.widthAnchor constraintEqualToConstant:(img != nil ? imageWidthAndHeight:0)], 188 | [iv.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:superviewLeadingTrailingDistance], 189 | [iv.topAnchor constraintEqualToAnchor:self.topAnchor constant:4], 190 | [iv.bottomAnchor constraintEqualToAnchor:self.bottomAnchor constant:-4], 191 | 192 | [self.titleField.leadingAnchor constraintEqualToAnchor:iv.trailingAnchor constant:(img != nil ? 4:0)], 193 | [self.titleField.topAnchor constraintEqualToAnchor:iv.topAnchor], 194 | [self.titleField.trailingAnchor constraintLessThanOrEqualToAnchor:self.trailingAnchor constant:-superviewLeadingTrailingDistance], 195 | 196 | [self.subtitleField.leadingAnchor constraintEqualToAnchor:self.titleField.leadingAnchor], 197 | [self.subtitleField.bottomAnchor constraintEqualToAnchor:iv.bottomAnchor], 198 | [self.subtitleField.trailingAnchor constraintLessThanOrEqualToAnchor:self.trailingAnchor constant:-superviewLeadingTrailingDistance], 199 | 200 | ]]; 201 | [self setFrameSize:[self fittingSize]]; 202 | 203 | return self; 204 | } 205 | 206 | return nil; 207 | } 208 | 209 | - (void)mouseEntered:(NSEvent *)event 210 | { 211 | [super mouseEntered:event]; 212 | 213 | self.titleField.textColor = self.subtitleField.textColor = [NSColor selectedMenuItemTextColor]; 214 | } 215 | 216 | - (void)mouseExited:(NSEvent *)event 217 | { 218 | [super mouseExited:event]; 219 | 220 | self.titleField.textColor = [NSColor labelColor]; 221 | self.subtitleField.textColor = [NSColor secondaryLabelColor]; 222 | } 223 | 224 | @end 225 | -------------------------------------------------------------------------------- /ESSSubtitledMenuItemView_Demo/ESSSubtitledMenuItemView_Demo/ESSSubtitledMenuItemView_Demo.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ESSSubtitledMenuItemView_Demo/ESSSubtitledMenuItemView_Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSMainStoryboardFile 26 | Main 27 | NSPrincipalClass 28 | NSApplication 29 | 30 | 31 | -------------------------------------------------------------------------------- /ESSSubtitledMenuItemView_Demo/ESSSubtitledMenuItemView_Demo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // ESSSubtitledMenuItemView_Demo 4 | // 5 | // Created by Matthias Gansrigler on 13.03.2021. 6 | // 7 | 8 | #import 9 | 10 | int main(int argc, const char * argv[]) { 11 | @autoreleasepool { 12 | // Setup code that might create autoreleased objects goes here. 13 | } 14 | return NSApplicationMain(argc, argv); 15 | } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESSSubtitledMenuItemView 2 | 3 | ## What is ESSSubtitledMenuItemView 4 | 5 | It's used for having NSMenuItems with subtitles! 6 | 7 | 8 | 9 | Or see it in a video [on my blog](https://blog.eternalstorms.at/2021/03/13/ess-nsmenuitems-with-subtitles/). 10 | 11 | ## First, the License Agreement 12 | 13 | 1) You can use the code in your own products. 14 | 2) You can modify the code as you wish, and use the modified code in your products. 15 | 3) You can redistribute the original, unmodified code, but you have to include the full license text below. 16 | 4) You can redistribute the modified code as you wish (without the full license text below). 17 | 5) In all cases, you must include a credit mentioning Matthias Gansrigler as the original author of the source. 18 | 6) I’m not liable for anything you do with the code, no matter what. So be sensible. 19 | 7) You can’t use my name or other marks to promote your products based on the code. 20 | 8) If you agree to all of that, go ahead and download the source. Otherwise, don’t. 21 | 22 | # How To use ESSSubtitledMenuItemView 23 | 24 | It's very easy, and best explained in this sample Xcode project. 25 | 26 | ## Requirements 27 | This code works on macOS X 10.11 and newer 28 | Developed with Xcode 12.4 29 | 30 | ## Support 31 | The framework and code is provided as-is, but if you need help or have suggestions, you can contact me anytime at [opensource@eternalstorms.at](mailto:opensource@eternalstorms.at) or [@eternalstorms on twitter](http://twitter.com/eternalstorms) 32 | 33 | For other Open Source projects of mine, please visit http://www.eternalstorms.at/opensource 34 | 35 | # I'd like to hear from you 36 | If you use ESSSubtitledMenuItemView in one of your projects, please [let me know](mailto:opensource@eternalstorms.at)! 37 | I'd love to hear about your apps and mention them here on this project page :) !! 38 | --------------------------------------------------------------------------------