├── .gitignore ├── GRStatusBar.podspec ├── GRStatusBar.xcodeproj ├── project.pbxproj └── xcshareddata │ └── xcschemes │ └── GRStatusBar.xcscheme ├── GRStatusBar ├── GRStatusBar.h ├── GRStatusBar.swift ├── GRStatusBarBackgroundView.swift ├── GRStatusBarStyle.swift ├── Info.plist ├── LayoutConstants.swift ├── NSWindowExtension.swift └── RoundedVisualEffectView.swift ├── LICENSE ├── README.md └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | __MACOSX 3 | *.pbxuser 4 | !default.pbxuser 5 | *.mode1v3 6 | !default.mode1v3 7 | *.mode2v3 8 | !default.mode2v3 9 | *.perspectivev3 10 | !default.perspectivev3 11 | *.xcworkspace 12 | !default.xcworkspace 13 | xcuserdata 14 | profile 15 | *.moved-aside 16 | DerivedData 17 | .idea/ 18 | Crashlytics.sh 19 | generatechangelog.sh 20 | Podfile.lock 21 | Pods/ -------------------------------------------------------------------------------- /GRStatusBar.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # GRStatusBar 3 | # 4 | 5 | Pod::Spec.new do |s| 6 | s.name = "GRStatusBar" 7 | s.version = "1.1.1" 8 | s.summary = "Safari-like status bar for macOS apps." 9 | s.description = <<-DESC 10 | Safari-like status bar for macOS apps. 11 | This adds a `statusBar` property to NSWindow, the easiest way to show a status message 12 | is by just setting the `text` property on your window's `statusBar`. 13 | 14 | [See demo app to learn more](https://github.com/insidegui/GRStatusBarDemo). 15 | DESC 16 | s.homepage = "https://github.com/insidegui/GRStatusBar" 17 | s.license = "BSD" 18 | s.author = { "Guilherme Rambo" => "eu@guilhermerambo.me" } 19 | s.source = { :git => "https://github.com/insidegui/GRStatusBar.git", :tag => "#{s.version}" } 20 | 21 | s.osx.deployment_target = "10.11" 22 | s.requires_arc = true 23 | 24 | s.source_files = "GRStatusBar/*.swift" 25 | end -------------------------------------------------------------------------------- /GRStatusBar.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | DD4D00001C5997BE00827ACB /* GRStatusBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4DFFFF1C5997BE00827ACB /* GRStatusBar.swift */; }; 11 | DD4D00071C5998D200827ACB /* NSWindowExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4D00061C5998D200827ACB /* NSWindowExtension.swift */; }; 12 | DD4D00091C599D9F00827ACB /* GRStatusBarBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4D00081C599D9F00827ACB /* GRStatusBarBackgroundView.swift */; }; 13 | DD4D000B1C59A81500827ACB /* LayoutConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4D000A1C59A81500827ACB /* LayoutConstants.swift */; }; 14 | DD4D000D1C59B25E00827ACB /* RoundedVisualEffectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4D000C1C59B25E00827ACB /* RoundedVisualEffectView.swift */; }; 15 | DD4D00131C59C0DF00827ACB /* GRStatusBarStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4D00121C59C0DF00827ACB /* GRStatusBarStyle.swift */; }; 16 | DD4DFFEB1C59976E00827ACB /* GRStatusBar.h in Headers */ = {isa = PBXBuildFile; fileRef = DD4DFFEA1C59976E00827ACB /* GRStatusBar.h */; settings = {ATTRIBUTES = (Public, ); }; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | DD4D00061C5998D200827ACB /* NSWindowExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSWindowExtension.swift; sourceTree = ""; }; 21 | DD4D00081C599D9F00827ACB /* GRStatusBarBackgroundView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GRStatusBarBackgroundView.swift; sourceTree = ""; }; 22 | DD4D000A1C59A81500827ACB /* LayoutConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LayoutConstants.swift; sourceTree = ""; }; 23 | DD4D000C1C59B25E00827ACB /* RoundedVisualEffectView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoundedVisualEffectView.swift; sourceTree = ""; }; 24 | DD4D00121C59C0DF00827ACB /* GRStatusBarStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GRStatusBarStyle.swift; sourceTree = ""; }; 25 | DD4DFFE71C59976E00827ACB /* GRStatusBar.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GRStatusBar.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 26 | DD4DFFEA1C59976E00827ACB /* GRStatusBar.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GRStatusBar.h; sourceTree = ""; }; 27 | DD4DFFEC1C59976E00827ACB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 28 | DD4DFFFF1C5997BE00827ACB /* GRStatusBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GRStatusBar.swift; sourceTree = ""; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | DD4DFFE31C59976E00827ACB /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | ); 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXFrameworksBuildPhase section */ 40 | 41 | /* Begin PBXGroup section */ 42 | DD4D00011C59980D00827ACB /* Categories */ = { 43 | isa = PBXGroup; 44 | children = ( 45 | DD4D00061C5998D200827ACB /* NSWindowExtension.swift */, 46 | ); 47 | name = Categories; 48 | sourceTree = ""; 49 | }; 50 | DD4DFFDD1C59976E00827ACB = { 51 | isa = PBXGroup; 52 | children = ( 53 | DD4DFFE91C59976E00827ACB /* GRStatusBar */, 54 | DD4DFFE81C59976E00827ACB /* Products */, 55 | ); 56 | sourceTree = ""; 57 | }; 58 | DD4DFFE81C59976E00827ACB /* Products */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | DD4DFFE71C59976E00827ACB /* GRStatusBar.framework */, 62 | ); 63 | name = Products; 64 | sourceTree = ""; 65 | }; 66 | DD4DFFE91C59976E00827ACB /* GRStatusBar */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | DD4DFFFE1C59979700827ACB /* GRStatusBar */, 70 | DD4D00011C59980D00827ACB /* Categories */, 71 | DD4DFFFD1C59979300827ACB /* Views */, 72 | DD4DFFEA1C59976E00827ACB /* GRStatusBar.h */, 73 | DD4DFFEC1C59976E00827ACB /* Info.plist */, 74 | ); 75 | path = GRStatusBar; 76 | sourceTree = ""; 77 | }; 78 | DD4DFFFD1C59979300827ACB /* Views */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | DD4D00081C599D9F00827ACB /* GRStatusBarBackgroundView.swift */, 82 | DD4D000C1C59B25E00827ACB /* RoundedVisualEffectView.swift */, 83 | ); 84 | name = Views; 85 | sourceTree = ""; 86 | }; 87 | DD4DFFFE1C59979700827ACB /* GRStatusBar */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | DD4DFFFF1C5997BE00827ACB /* GRStatusBar.swift */, 91 | DD4D000A1C59A81500827ACB /* LayoutConstants.swift */, 92 | DD4D00121C59C0DF00827ACB /* GRStatusBarStyle.swift */, 93 | ); 94 | name = GRStatusBar; 95 | sourceTree = ""; 96 | }; 97 | /* End PBXGroup section */ 98 | 99 | /* Begin PBXHeadersBuildPhase section */ 100 | DD4DFFE41C59976E00827ACB /* Headers */ = { 101 | isa = PBXHeadersBuildPhase; 102 | buildActionMask = 2147483647; 103 | files = ( 104 | DD4DFFEB1C59976E00827ACB /* GRStatusBar.h in Headers */, 105 | ); 106 | runOnlyForDeploymentPostprocessing = 0; 107 | }; 108 | /* End PBXHeadersBuildPhase section */ 109 | 110 | /* Begin PBXNativeTarget section */ 111 | DD4DFFE61C59976E00827ACB /* GRStatusBar */ = { 112 | isa = PBXNativeTarget; 113 | buildConfigurationList = DD4DFFEF1C59976E00827ACB /* Build configuration list for PBXNativeTarget "GRStatusBar" */; 114 | buildPhases = ( 115 | DD4DFFE21C59976E00827ACB /* Sources */, 116 | DD4DFFE31C59976E00827ACB /* Frameworks */, 117 | DD4DFFE41C59976E00827ACB /* Headers */, 118 | DD4DFFE51C59976E00827ACB /* Resources */, 119 | ); 120 | buildRules = ( 121 | ); 122 | dependencies = ( 123 | ); 124 | name = GRStatusBar; 125 | productName = GRStatusBar; 126 | productReference = DD4DFFE71C59976E00827ACB /* GRStatusBar.framework */; 127 | productType = "com.apple.product-type.framework"; 128 | }; 129 | /* End PBXNativeTarget section */ 130 | 131 | /* Begin PBXProject section */ 132 | DD4DFFDE1C59976E00827ACB /* Project object */ = { 133 | isa = PBXProject; 134 | attributes = { 135 | LastUpgradeCheck = 0820; 136 | ORGANIZATIONNAME = "Guilherme Rambo"; 137 | TargetAttributes = { 138 | DD4DFFE61C59976E00827ACB = { 139 | CreatedOnToolsVersion = 7.2; 140 | LastSwiftMigration = 0800; 141 | }; 142 | }; 143 | }; 144 | buildConfigurationList = DD4DFFE11C59976E00827ACB /* Build configuration list for PBXProject "GRStatusBar" */; 145 | compatibilityVersion = "Xcode 3.2"; 146 | developmentRegion = English; 147 | hasScannedForEncodings = 0; 148 | knownRegions = ( 149 | en, 150 | ); 151 | mainGroup = DD4DFFDD1C59976E00827ACB; 152 | productRefGroup = DD4DFFE81C59976E00827ACB /* Products */; 153 | projectDirPath = ""; 154 | projectRoot = ""; 155 | targets = ( 156 | DD4DFFE61C59976E00827ACB /* GRStatusBar */, 157 | ); 158 | }; 159 | /* End PBXProject section */ 160 | 161 | /* Begin PBXResourcesBuildPhase section */ 162 | DD4DFFE51C59976E00827ACB /* Resources */ = { 163 | isa = PBXResourcesBuildPhase; 164 | buildActionMask = 2147483647; 165 | files = ( 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXResourcesBuildPhase section */ 170 | 171 | /* Begin PBXSourcesBuildPhase section */ 172 | DD4DFFE21C59976E00827ACB /* Sources */ = { 173 | isa = PBXSourcesBuildPhase; 174 | buildActionMask = 2147483647; 175 | files = ( 176 | DD4D00071C5998D200827ACB /* NSWindowExtension.swift in Sources */, 177 | DD4D000D1C59B25E00827ACB /* RoundedVisualEffectView.swift in Sources */, 178 | DD4D00001C5997BE00827ACB /* GRStatusBar.swift in Sources */, 179 | DD4D00091C599D9F00827ACB /* GRStatusBarBackgroundView.swift in Sources */, 180 | DD4D00131C59C0DF00827ACB /* GRStatusBarStyle.swift in Sources */, 181 | DD4D000B1C59A81500827ACB /* LayoutConstants.swift in Sources */, 182 | ); 183 | runOnlyForDeploymentPostprocessing = 0; 184 | }; 185 | /* End PBXSourcesBuildPhase section */ 186 | 187 | /* Begin XCBuildConfiguration section */ 188 | DD4DFFED1C59976E00827ACB /* Debug */ = { 189 | isa = XCBuildConfiguration; 190 | buildSettings = { 191 | ALWAYS_SEARCH_USER_PATHS = NO; 192 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 193 | CLANG_CXX_LIBRARY = "libc++"; 194 | CLANG_ENABLE_MODULES = YES; 195 | CLANG_ENABLE_OBJC_ARC = YES; 196 | CLANG_WARN_BOOL_CONVERSION = YES; 197 | CLANG_WARN_CONSTANT_CONVERSION = YES; 198 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 199 | CLANG_WARN_EMPTY_BODY = YES; 200 | CLANG_WARN_ENUM_CONVERSION = YES; 201 | CLANG_WARN_INFINITE_RECURSION = YES; 202 | CLANG_WARN_INT_CONVERSION = YES; 203 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 204 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 205 | CLANG_WARN_UNREACHABLE_CODE = YES; 206 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 207 | CODE_SIGN_IDENTITY = "-"; 208 | COPY_PHASE_STRIP = NO; 209 | CURRENT_PROJECT_VERSION = 1; 210 | DEBUG_INFORMATION_FORMAT = dwarf; 211 | ENABLE_STRICT_OBJC_MSGSEND = YES; 212 | ENABLE_TESTABILITY = YES; 213 | GCC_C_LANGUAGE_STANDARD = gnu99; 214 | GCC_DYNAMIC_NO_PIC = NO; 215 | GCC_NO_COMMON_BLOCKS = YES; 216 | GCC_OPTIMIZATION_LEVEL = 0; 217 | GCC_PREPROCESSOR_DEFINITIONS = ( 218 | "DEBUG=1", 219 | "$(inherited)", 220 | ); 221 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 222 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 223 | GCC_WARN_UNDECLARED_SELECTOR = YES; 224 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 225 | GCC_WARN_UNUSED_FUNCTION = YES; 226 | GCC_WARN_UNUSED_VARIABLE = YES; 227 | MACOSX_DEPLOYMENT_TARGET = 10.11; 228 | MTL_ENABLE_DEBUG_INFO = YES; 229 | ONLY_ACTIVE_ARCH = YES; 230 | SDKROOT = macosx; 231 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 232 | VERSIONING_SYSTEM = "apple-generic"; 233 | VERSION_INFO_PREFIX = ""; 234 | }; 235 | name = Debug; 236 | }; 237 | DD4DFFEE1C59976E00827ACB /* Release */ = { 238 | isa = XCBuildConfiguration; 239 | buildSettings = { 240 | ALWAYS_SEARCH_USER_PATHS = NO; 241 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 242 | CLANG_CXX_LIBRARY = "libc++"; 243 | CLANG_ENABLE_MODULES = YES; 244 | CLANG_ENABLE_OBJC_ARC = YES; 245 | CLANG_WARN_BOOL_CONVERSION = YES; 246 | CLANG_WARN_CONSTANT_CONVERSION = YES; 247 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 248 | CLANG_WARN_EMPTY_BODY = YES; 249 | CLANG_WARN_ENUM_CONVERSION = YES; 250 | CLANG_WARN_INFINITE_RECURSION = YES; 251 | CLANG_WARN_INT_CONVERSION = YES; 252 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 253 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 254 | CLANG_WARN_UNREACHABLE_CODE = YES; 255 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 256 | CODE_SIGN_IDENTITY = "-"; 257 | COPY_PHASE_STRIP = NO; 258 | CURRENT_PROJECT_VERSION = 1; 259 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 260 | ENABLE_NS_ASSERTIONS = NO; 261 | ENABLE_STRICT_OBJC_MSGSEND = YES; 262 | GCC_C_LANGUAGE_STANDARD = gnu99; 263 | GCC_NO_COMMON_BLOCKS = YES; 264 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 265 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 266 | GCC_WARN_UNDECLARED_SELECTOR = YES; 267 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 268 | GCC_WARN_UNUSED_FUNCTION = YES; 269 | GCC_WARN_UNUSED_VARIABLE = YES; 270 | MACOSX_DEPLOYMENT_TARGET = 10.11; 271 | MTL_ENABLE_DEBUG_INFO = NO; 272 | SDKROOT = macosx; 273 | VERSIONING_SYSTEM = "apple-generic"; 274 | VERSION_INFO_PREFIX = ""; 275 | }; 276 | name = Release; 277 | }; 278 | DD4DFFF01C59976E00827ACB /* Debug */ = { 279 | isa = XCBuildConfiguration; 280 | buildSettings = { 281 | CLANG_ENABLE_MODULES = YES; 282 | COMBINE_HIDPI_IMAGES = YES; 283 | DEFINES_MODULE = YES; 284 | DYLIB_COMPATIBILITY_VERSION = 1; 285 | DYLIB_CURRENT_VERSION = 1; 286 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 287 | FRAMEWORK_VERSION = A; 288 | INFOPLIST_FILE = GRStatusBar/Info.plist; 289 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 290 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 291 | PRODUCT_BUNDLE_IDENTIFIER = br.com.guilhermerambo.GRStatusBar; 292 | PRODUCT_NAME = "$(TARGET_NAME)"; 293 | SKIP_INSTALL = YES; 294 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 295 | SWIFT_VERSION = 3.0; 296 | }; 297 | name = Debug; 298 | }; 299 | DD4DFFF11C59976E00827ACB /* Release */ = { 300 | isa = XCBuildConfiguration; 301 | buildSettings = { 302 | CLANG_ENABLE_MODULES = YES; 303 | COMBINE_HIDPI_IMAGES = YES; 304 | DEFINES_MODULE = YES; 305 | DYLIB_COMPATIBILITY_VERSION = 1; 306 | DYLIB_CURRENT_VERSION = 1; 307 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 308 | FRAMEWORK_VERSION = A; 309 | INFOPLIST_FILE = GRStatusBar/Info.plist; 310 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 311 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 312 | PRODUCT_BUNDLE_IDENTIFIER = br.com.guilhermerambo.GRStatusBar; 313 | PRODUCT_NAME = "$(TARGET_NAME)"; 314 | SKIP_INSTALL = YES; 315 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 316 | SWIFT_VERSION = 3.0; 317 | }; 318 | name = Release; 319 | }; 320 | /* End XCBuildConfiguration section */ 321 | 322 | /* Begin XCConfigurationList section */ 323 | DD4DFFE11C59976E00827ACB /* Build configuration list for PBXProject "GRStatusBar" */ = { 324 | isa = XCConfigurationList; 325 | buildConfigurations = ( 326 | DD4DFFED1C59976E00827ACB /* Debug */, 327 | DD4DFFEE1C59976E00827ACB /* Release */, 328 | ); 329 | defaultConfigurationIsVisible = 0; 330 | defaultConfigurationName = Release; 331 | }; 332 | DD4DFFEF1C59976E00827ACB /* Build configuration list for PBXNativeTarget "GRStatusBar" */ = { 333 | isa = XCConfigurationList; 334 | buildConfigurations = ( 335 | DD4DFFF01C59976E00827ACB /* Debug */, 336 | DD4DFFF11C59976E00827ACB /* Release */, 337 | ); 338 | defaultConfigurationIsVisible = 0; 339 | defaultConfigurationName = Release; 340 | }; 341 | /* End XCConfigurationList section */ 342 | }; 343 | rootObject = DD4DFFDE1C59976E00827ACB /* Project object */; 344 | } 345 | -------------------------------------------------------------------------------- /GRStatusBar.xcodeproj/xcshareddata/xcschemes/GRStatusBar.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /GRStatusBar/GRStatusBar.h: -------------------------------------------------------------------------------- 1 | // 2 | // GRStatusBar.h 3 | // GRStatusBar 4 | // 5 | // Created by Guilherme Rambo on 27/01/16. 6 | // Copyright © 2016 Guilherme Rambo. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for GRStatusBar. 12 | FOUNDATION_EXPORT double GRStatusBarVersionNumber; 13 | 14 | //! Project version string for GRStatusBar. 15 | FOUNDATION_EXPORT const unsigned char GRStatusBarVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | -------------------------------------------------------------------------------- /GRStatusBar/GRStatusBar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GRStatusBar.swift 3 | // GRStatusBar 4 | // 5 | // Created by Guilherme Rambo on 27/01/16. 6 | // Copyright © 2016 Guilherme Rambo. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | /// Manages a status bar associated with a particular window 12 | /// 13 | /// The status bar is a thin translucent bar displayed on be bottom-left of the window 14 | @objc public class GRStatusBar: NSObject { 15 | 16 | /// The style used for the status bar vibrancy (Light or Dark) 17 | public var style = GRStatusBarStyle.light { 18 | didSet { 19 | guard backgroundView != nil else { return } 20 | 21 | backgroundView.appearance = style.appearance 22 | } 23 | } 24 | 25 | /// The color to use as the status bar's background (will have opacity reduced to keep translucency) 26 | public var backgroundColor: NSColor? { 27 | didSet { 28 | tintView.backgroundColor = backgroundColor?.withAlphaComponent(LayoutConstants.backgroundColorAlpha) 29 | } 30 | } 31 | 32 | /// The color for the text (if you want to customize other text properties, use `attributedText`) 33 | public var textColor: NSColor? = LayoutConstants.defaultTextColor { 34 | didSet { 35 | label.textColor = textColor ?? LayoutConstants.defaultTextColor 36 | } 37 | } 38 | 39 | /// The text to display in the status bar (if you want to customize text properties, set `attributedText` instead) 40 | /// 41 | /// *The status bar is automatically shown for `displayTime` seconds when this property is changed, unless It is set to nil* 42 | public var text: String? { 43 | didSet { 44 | guard label != nil else { return } 45 | 46 | label.stringValue = text ?? "" 47 | 48 | if text != nil { 49 | show() 50 | } 51 | } 52 | } 53 | 54 | /// The attributed text to display in the status bar 55 | /// 56 | /// *The status bar is automatically shown for `displayTime` seconds when this property is changed, unless It is set to nil* 57 | public var attributedText: NSAttributedString? { 58 | didSet { 59 | label.attributedStringValue = attributedText ?? NSAttributedString() 60 | 61 | if attributedText != nil { 62 | show() 63 | } 64 | } 65 | } 66 | 67 | /// How long the status bar should stay on screen by default 68 | public var displayTime: Double = 4.0 69 | 70 | /// Whether the status bar is currently being displayed or not 71 | /// 72 | /// *This is set to `true` when the show animation starts and `false` when the hide animation starts* 73 | public var isVisible = false 74 | 75 | private var window: NSWindow 76 | 77 | private var containerView: NSView! 78 | private var tintView: GRStatusBarBackgroundView! 79 | private var backgroundView: NSVisualEffectView! 80 | private var label: NSTextField! 81 | 82 | private let windowContentViewObserverContext: UnsafeMutableRawPointer? = nil 83 | init(window: NSWindow) { 84 | self.window = window 85 | 86 | super.init() 87 | 88 | self.window.addObserver(self, forKeyPath: "contentView", options: [.initial, .new], context: windowContentViewObserverContext) 89 | 90 | fixContentViewIfNeeded() 91 | buildViews() 92 | } 93 | 94 | /// Shows the status bar for the specified duration. 95 | /// 96 | /// If no duration is specified, the status bar is shown for the duration specified in `displayTime`. 97 | /// If the duration specified is `zero`, the status bar is shown until `hide()` is called manually 98 | public func show(forDuration duration: Double? = nil) { 99 | bringToFront() 100 | 101 | let duration = duration ?? self.displayTime 102 | 103 | isVisible = true 104 | 105 | NSAnimationContext.runAnimationGroup({ context in 106 | context.duration = 0.4 107 | self.containerView.animator().alphaValue = 1.0 108 | }) { 109 | guard duration > 0.0 else { return } 110 | 111 | let delayTime = DispatchTime.now() + Double(Int64(duration * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) 112 | DispatchQueue.main.asyncAfter(deadline: delayTime) { 113 | self.hide() 114 | } 115 | } 116 | } 117 | 118 | /// Hides the status bar after the specified delay 119 | /// 120 | /// If no delay is specified, the status bar is hidden immediately 121 | public func hide(afterDelay delay: Double? = 0.0) { 122 | let hideBlock = { 123 | self.isVisible = false 124 | 125 | NSAnimationContext.runAnimationGroup({ context in 126 | context.duration = 0.4 127 | self.containerView.animator().alphaValue = 0.0 128 | }, completionHandler: nil) 129 | } 130 | 131 | guard let delay = delay else { return hideBlock() } 132 | 133 | let delayTime = DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) 134 | DispatchQueue.main.asyncAfter(deadline: delayTime) { 135 | hideBlock() 136 | } 137 | } 138 | 139 | private func fixContentViewIfNeeded() { 140 | guard let contentView = window.contentView else { return } 141 | guard !contentView.wantsLayer else { return } 142 | 143 | contentView.wantsLayer = true 144 | print("[GRStatusBar] WARNING: Window contentView must have wantsLayer = true") 145 | } 146 | 147 | private func buildViews() { 148 | let defaultRect = NSMakeRect(0, 0, LayoutConstants.defaultWidth, LayoutConstants.defaultHeight) 149 | 150 | // Container View 151 | 152 | containerView = NSView(frame: defaultRect) 153 | containerView.translatesAutoresizingMaskIntoConstraints = false 154 | containerView.addConstraint(NSLayoutConstraint(item: containerView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: LayoutConstants.defaultHeight)) 155 | 156 | // Visual Effect View 157 | 158 | backgroundView = RoundedVisualEffectView(frame: defaultRect) 159 | backgroundView.blendingMode = .withinWindow 160 | backgroundView.translatesAutoresizingMaskIntoConstraints = false 161 | backgroundView.material = .appearanceBased 162 | backgroundView.appearance = style.appearance 163 | backgroundView.state = .active 164 | containerView.addSubview(backgroundView) 165 | 166 | // Tint View 167 | 168 | tintView = GRStatusBarBackgroundView(frame: defaultRect) 169 | tintView.backgroundColor = backgroundColor 170 | tintView.translatesAutoresizingMaskIntoConstraints = false 171 | containerView.addSubview(tintView) 172 | 173 | // Label 174 | 175 | label = NSTextField(frame: defaultRect) 176 | label.translatesAutoresizingMaskIntoConstraints = false 177 | label.stringValue = text ?? "" 178 | if let attributedText = attributedText { 179 | label.attributedStringValue = attributedText 180 | } 181 | label.isEditable = false 182 | label.isSelectable = false 183 | label.isBezeled = false 184 | label.isBordered = false 185 | label.drawsBackground = false 186 | label.font = NSFont.systemFont(ofSize: LayoutConstants.defaultFontSize) 187 | label.textColor = textColor 188 | label.lineBreakMode = .byTruncatingMiddle 189 | label.sizeToFit() 190 | backgroundView.addSubview(label) 191 | 192 | // Configure frames and constraints 193 | 194 | containerView.setFrameSize(label.bounds.size) 195 | backgroundView.setFrameSize(label.bounds.size) 196 | tintView.setFrameSize(label.bounds.size) 197 | 198 | backgroundView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true 199 | backgroundView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true 200 | backgroundView.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true 201 | backgroundView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true 202 | 203 | tintView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true 204 | tintView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true 205 | tintView.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true 206 | tintView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true 207 | 208 | // Constraint from label to visual effect view, LEFT with padding 209 | let labelLeadingAnchor = label.leadingAnchor.constraint(equalTo: backgroundView.leadingAnchor) 210 | labelLeadingAnchor.constant = LayoutConstants.padding 211 | labelLeadingAnchor.isActive = true 212 | // Constraint from label to visual effect view, RIGHT with padding 213 | let labelTrailingAnchor = label.trailingAnchor.constraint(equalTo: backgroundView.trailingAnchor) 214 | labelTrailingAnchor.constant = -LayoutConstants.padding 215 | labelTrailingAnchor.isActive = true 216 | // Constraint to center label vertically inside visual effect view 217 | let labelCenterAnchor = label.centerYAnchor.constraint(equalTo: backgroundView.centerYAnchor) 218 | labelCenterAnchor.constant = LayoutConstants.textYOffset 219 | labelCenterAnchor.isActive = true 220 | 221 | // Start with the container hidden 222 | containerView.alphaValue = 0.0 223 | 224 | if let contentView = window.contentView { 225 | // Start with the best style for the contentView's appearance 226 | style = GRStatusBarStyle(appearance: contentView.appearance) 227 | } 228 | 229 | bringToFront() 230 | } 231 | 232 | private func bringToFront() { 233 | guard containerView != nil else { return } 234 | 235 | if containerView.superview != nil { 236 | containerView.removeFromSuperview() 237 | } 238 | 239 | guard let contentView = window.contentView else { return } 240 | 241 | contentView.addSubview(containerView) 242 | 243 | let leadingConstraint = containerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor) 244 | leadingConstraint.constant = LayoutConstants.margin 245 | leadingConstraint.isActive = true 246 | let bottomConstraint = containerView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) 247 | bottomConstraint.constant = -LayoutConstants.margin 248 | bottomConstraint.isActive = true 249 | } 250 | 251 | public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 252 | if context == windowContentViewObserverContext { 253 | bringToFront() 254 | } else { 255 | super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) 256 | } 257 | } 258 | 259 | deinit { 260 | window.removeObserver(self, forKeyPath: "contentView", context: windowContentViewObserverContext) 261 | } 262 | 263 | } 264 | -------------------------------------------------------------------------------- /GRStatusBar/GRStatusBarBackgroundView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GRStatusBarBackgroundView.swift 3 | // GRStatusBar 4 | // 5 | // Created by Guilherme Rambo on 27/01/16. 6 | // Copyright © 2016 Guilherme Rambo. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class GRStatusBarBackgroundView: NSView { 12 | 13 | var backgroundColor: NSColor? { 14 | didSet { 15 | setNeedsDisplay(bounds) 16 | } 17 | } 18 | 19 | override func draw(_ dirtyRect: NSRect) { 20 | super.draw(dirtyRect) 21 | 22 | guard let backgroundColor = backgroundColor else { return } 23 | 24 | NSBezierPath(roundedRect: bounds, xRadius: LayoutConstants.cornerRadius, yRadius: LayoutConstants.cornerRadius).addClip() 25 | backgroundColor.setFill() 26 | NSRectFill(dirtyRect) 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /GRStatusBar/GRStatusBarStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GRStatusBarStyle.swift 3 | // GRStatusBar 4 | // 5 | // Created by Guilherme Rambo on 28/01/16. 6 | // Copyright © 2016 Guilherme Rambo. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | /// Defines the style of vibrancy to be used by a `GRStatusBar` 12 | public enum GRStatusBarStyle: String { 13 | /// Uses the vibrant light appearance and material 14 | case light 15 | /// Uses the vibrant dark appearance and material 16 | case dark 17 | 18 | /// Gets the most appropriate style based on the appearance provided 19 | /// 20 | /// You can use this to automatically match a window's appearance 21 | init(appearance: NSAppearance?) { 22 | guard let appearance = appearance else { 23 | self.init(rawValue: "light")! 24 | return 25 | } 26 | 27 | switch appearance.name { 28 | case NSAppearanceNameVibrantDark: 29 | self.init(rawValue: "dark")! 30 | default: 31 | self.init(rawValue: "light")! 32 | } 33 | } 34 | 35 | var appearance: NSAppearance { 36 | switch self { 37 | case .light: 38 | return NSAppearance(named: NSAppearanceNameVibrantLight)! 39 | case .dark: 40 | return NSAppearance(named: NSAppearanceNameVibrantDark)! 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /GRStatusBar/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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.1.1 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Copyright © 2016 Guilherme Rambo. All rights reserved. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /GRStatusBar/LayoutConstants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LayoutConstants.swift 3 | // GRStatusBar 4 | // 5 | // Created by Guilherme Rambo on 27/01/16. 6 | // Copyright © 2016 Guilherme Rambo. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct LayoutConstants { 12 | static let defaultTextColor = NSColor.secondaryLabelColor 13 | static let backgroundColorAlpha = CGFloat(0.3) 14 | static let defaultWidth = CGFloat(100.0) 15 | static let defaultHeight = CGFloat(20.0) 16 | static let margin = CGFloat(12.0) 17 | static let padding = CGFloat(4.0) 18 | static let textYOffset = CGFloat(-1.0) 19 | static let cornerRadius = CGFloat(4.0) 20 | static let defaultFontSize = CGFloat(12.0) 21 | } 22 | -------------------------------------------------------------------------------- /GRStatusBar/NSWindowExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSWindowExtension.swift 3 | // GRStatusBar 4 | // 5 | // Created by Guilherme Rambo on 27/01/16. 6 | // Copyright © 2016 Guilherme Rambo. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | /// This extension adds the statusBar property to every NSWindow instance 12 | /// 13 | /// A `GRStatusBar` is automatically initialized and associated with the window the first time the property is accessed 14 | public extension NSWindow { 15 | 16 | private struct AssociatedKeys { 17 | static var statusBar = "NSWindowGRStatusBarObject" 18 | } 19 | 20 | /// The window's status bar 21 | public var statusBar: GRStatusBar { 22 | get { 23 | if let existingBar = objc_getAssociatedObject(self, &AssociatedKeys.statusBar) as? GRStatusBar { 24 | return existingBar 25 | } else { 26 | let newBar = GRStatusBar(window: self) 27 | objc_setAssociatedObject(self, &AssociatedKeys.statusBar, newBar, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 28 | return newBar 29 | } 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /GRStatusBar/RoundedVisualEffectView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RoundedVisualEffectView.swift 3 | // GRStatusBar 4 | // 5 | // Created by Guilherme Rambo on 28/01/16. 6 | // Copyright © 2016 Guilherme Rambo. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class RoundedVisualEffectView: NSVisualEffectView { 12 | 13 | var cornerRadius: CGFloat = 4.0 { 14 | didSet { 15 | invalidateCornerImage() 16 | } 17 | } 18 | 19 | private var cornerImage: NSImage! 20 | 21 | private func invalidateCornerImage() { 22 | cornerImage = NSImage(size: bounds.size) 23 | cornerImage.lockFocus() 24 | NSColor.clear.setFill() 25 | NSRectFill(bounds) 26 | NSBezierPath(roundedRect: bounds, xRadius: cornerRadius, yRadius: cornerRadius).addClip() 27 | NSColor.white.setFill() 28 | NSRectFill(bounds) 29 | cornerImage.unlockFocus() 30 | 31 | maskImage = cornerImage 32 | } 33 | 34 | override func viewDidMoveToSuperview() { 35 | super.viewDidMoveToSuperview() 36 | 37 | invalidateCornerImage() 38 | } 39 | 40 | override func setFrameSize(_ newSize: NSSize) { 41 | super.setFrameSize(newSize) 42 | 43 | invalidateCornerImage() 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Guilherme Rambo 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | - Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | - Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 17 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 20 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 21 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | If you want to support my open source projects financially, you can do so by purchasing a copy of [BrowserFreedom](https://getbrowserfreedom.com), [Mediunic](https://itunes.apple.com/app/mediunic-medium-client/id1088945121?mt=12) or sending Bitcoin to `3DH9B42m6k2A89hy1Diz3Vr3cpDNQTQCbJ` 😁 2 | 3 | # GRStatusBar 4 | 5 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![CocoaPods](https://img.shields.io/cocoapods/v/GRStatusBar.svg)]() 6 | 7 | Safari-like status bar for macOS apps. 8 | 9 | This adds a `statusBar` property to NSWindow, the easiest way to show a status message is by just setting the `text` property on your window's `statusBar`. 10 | 11 | [See demo app to learn more](https://github.com/insidegui/GRStatusBarDemo). 12 | 13 | [Read the docs](http://cocoadocs.org/docsets/GRStatusBar). 14 | 15 | ![screenshot](https://raw.githubusercontent.com/insidegui/GRStatusBar/master/screenshot.png) 16 | 17 | ## Installing 18 | 19 | ### Using Carthage: 20 | 21 | Add the following line to your `Cartfile` and follow the [integration instructions for Carthage](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application): 22 | 23 | ``` 24 | github "insidegui/GRStatusBar" ~> 1.1 25 | ``` 26 | 27 | ### Using CocoaPods: 28 | 29 | Add a `Podfile` that contains at least the following information to the root of your app project, then do `pod install`. 30 | If you're unfamiliar with CocoaPods, read [using CocoaPods](http://guides.cocoapods.org/using/using-cocoapods.html). 31 | 32 | ```ruby 33 | platform :osx, '10.11' # only 10.11 and up supported 34 | pod 'GRStatusBar' 35 | use_frameworks! 36 | ``` 37 | 38 | ### Manually 39 | 40 | Just download the code and add `GRStatusBar.framework` to the `Embedded Binaries` section of your app's target config. -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insidegui/GRStatusBar/72465c264d57ace12ebcd0bfe1cbdd9d78e4678f/screenshot.png --------------------------------------------------------------------------------