Statusbar.image has wrong color - swift

I am using following code for my status bar image:
let icon = NSImage(imageLiteralResourceName:"flag")
statusBarItem.image = icon
This leads to wrong color for certain background colors / modes. In the picture, what's white should be black. The image resource is white/transparent. If I change that, I get the same problem. All other status bar images will turn white on certain configurations, mine will stay black.
I was thinking that MacOS would add effects to make all statusbar icons look uniform on it's own, but apparently thats not the case...
Any ideas how to fix that?
Thanks!

MacOs can do what you want. I recommend reading Apple documentation:
https://developer.apple.com/documentation/uikit/appearance_customization/supporting_dark_mode_in_your_interface
Basically you have 2 options if you don‘t provide the code manually.
Option 1. In Xcode, navigate to your image asset in assets.xcassets. In the attributes pane, in „Render as…“ specify „Template Image“. This worked well for my menu bar app.
Option 2. Supply different versions of your icon in one image asset, macOs will then choose the appropriate version.

I found a solution. Again I realize that MacOS development is way less supported by Apple than iOS. I think the color adjustment of statusbar icons should be the task of the operating system, but Apple lets the developer do the work. Whatever.
Here is the solution:
You have to provide two versions of your icon, one in black, the other in white.
When the app launches, you have to check wether the user's MacOs is in dark or light mode. This can be done with following code:
let mode = UserDefaults.standard.string(forKey: "AppleInterfaceStyle")
if (mode == "Dark"){
let icon = NSImage(imageLiteralResourceName:"flag")
statusBarItem.image = icon
} else {
let icon = NSImage(imageLiteralResourceName:"flagDark")
statusBarItem.image = icon
}
One problem remains here now: When the user changes the mode while your app is running, the icon color won't update. Also: If the user uses the automatic mode (i.e. it's light at day and dark in the night), the icon color won't switch as well.
You can tackle that problem by listening to a certain notification that is fired when the dark mode settings changes:
DistributedNotificationCenter.default.addObserver(self, selector: #selector(updateIcon), name: NSNotification.Name(rawValue: "AppleInterfaceThemeChangedNotification"), object: nil)
#objc func updateIcon(){
print("updateIcon ausgeführt")
let mode = UserDefaults.standard.string(forKey: "AppleInterfaceStyle")
if (mode == "Dark"){
let icon = NSImage(imageLiteralResourceName:"flag")
statusBarItem.image = icon
} else {
let icon = NSImage(imageLiteralResourceName:"flagDark")
statusBarItem.image = icon
}
}
In my tests, this worked in all scenarios.

For me this worked:
class AppDelegate: NSObject, NSApplicationDelegate {
let statusItem = NSStatusBar.system.statusItem(withLength:NSStatusItem.squareLength)
// ...
func applicationDidFinishLaunching(_ aNotification: Notification) {
// ...
if let button = statusItem.button {
let image = NSImage(named: NSImage.Name("TrayIcon"))
image?.isTemplate = true
button.image = image
}
// ...
}
// ...
}

Related

How can you allow for a user changing to dark or light mode with the app open on iOS?

I have updated views in my app to support dark mode by adding
if #available(iOS 12.0, *) {
if self.traitCollection.userInterfaceStyle == .dark {
//Adapt to dark Bg
} else {
//Adapt to light Bg
}
}
Then, to allow for the case where the user backgrounds the app and returns to it after switching mode, I attach an observer in my viewDidLoad
if #available(iOS 12.0, *) {
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
} else {
// Fallback on earlier versions
}
which triggers the function
#available(iOS 12.0, *)
#objc func willEnterForeground() {
if self.traitCollection.userInterfaceStyle == .dark {
print("App moving to foreground - dark")
//Adapt to dark Bg
} else {
print("App moving to foreground - light")
//Adapt to light Bg
}
}
However, self.traitCollection.userInterfaceStyle still gives the old value so a full reload of the view is required to produce the desired update to the interface.
Using UIApplication.didBecomeActiveNotification instead makes no difference.
You don't need all those messy if statements! Just add your colours to your Asset Catalogue and the right one will automatically be selected. This is similar to how you can add x1, x2 and x3 images, and the right one will be selected.
Go to the Asset Catalogue and at the bottom left, click on the plus button, select "New Color Set":
Give the colour a name, and in the property inspector, set "Appearance" to "Any, Dark":
Choose a colour for each appearance:
Finally, use the UIColor(named:) initialiser to initialise the colours and they will automatically change when the device's dark mode settings change:
someView.backgroundColor = UIColor(named: "myColor")
EDIT:
If the colours are only known at runtime, you can use the init(dynamicProvider:) initialiser (iOS 13 only though):
someView.backgroundColor = UIColor {
traits in
if traits.userInterfaceStyle == .dark {
// return color for dark mode
} else {
// return color for light mode
}
}

Remove pale-effect on NSStatusBarButton

I'm looking for a way to remove the pale effect from the NSStatusBarButton. This is a picture of how it currently looks:
This is how it should look:
After looking at Apple's documentation, I found a solution to the problem. If you set the appearance of the button directly (e.g. Aqua or DarkAqua), the pale effect disappears:
if let button = statusBarItem.button {
...
button.appearance = NSAppearance.current // or aqua / darkAqua
}
But the problem is when the user changes the interface theme (e.g. from dark mode to light mode), the NSStatusBarButton does not change its appearance automatically:
I could monitor AppleInterfaceThemeChangedNotification and then change the appearance, but that's not a clean solution and I'm not happy with it.
Is there an elegant solution to this? The image in the NSStatusBarButton should simply be displayed without changes (e.g. pale). Because I offer all flags of the world, I only have the images in png format, no PDF images.
Since the solution of vadian, unfortunately, did not work for me, I would like to show my alternative solution here. Maybe it helps somebody else.
Create the NSStatusItem and customize NSStatusBarButton:
...
if let button = statusBarItem.button {
...
button.appearance = NSAppearance.current // removes the pale effect
}
...
Write an extension for Notification.Name to react on AppleInterfaceThemeChangedNotification:
extension Notification.Name {
static let AppleInterfaceThemeChangedNotification = Notification.Name("AppleInterfaceThemeChangedNotification")
}
Add an Observer to the new notification name:
DistributedNotificationCenter.default.addObserver(self, selector: #selector(interfaceChanged), name: .AppleInterfaceThemeChangedNotification, object: nil)
Respond to the change between light/dark mode:
#objc private static func interfaceChanged() {
// change button.appearance
}
Make sure that button.appearance is only changed if the necessary macOS version is available:
guard #available(OSX 10.14, *) else {
return
}
I am sure that there is a cleaner solution. If anyone has an idea, please tell me.
Write an extension of NSStatusBarButton and override viewDidChangeEffectiveAppearance.
In the body of the method change the appearance explicitly
extension NSStatusBarButton {
#available(macOS 10.14, *)
override public func viewDidChangeEffectiveAppearance() {
print("viewDidChangeEffectiveAppearance")
self.appearance = .current
}
}

Restore menubar after exiting full screen NSView

I am setting an NSView in fullscreen mode and I hide the menubar without problem. My issue is when exiting fullscreen mode. How can I restore the menubar? I tried with and without options but result remains the same: menubar is missing:
func exitFullScreen() {
let presOptions: NSApplication.PresentationOptions = [.hideDock, .hideMenuBar]
let optionsDictionary = [NSView.FullScreenModeOptionKey.fullScreenModeApplicationPresentationOptions: NSNumber(value: presOptions.rawValue), NSView.FullScreenModeOptionKey.fullScreenModeAllScreens: false]
presentationWindowController.window?.contentView?.exitFullScreenMode(options: optionsDictionary)
}
Actually, I just needed to set the app presentation options again:
NSApplication.shared.presentationOptions = []

Changing AVPlayerView background color

Is it possible to change the background color of a AVPlayerView when used in a macOS application. I want to do this to remove the black bars when playing a video.
I've tried the following:
videoView.contentOverlayView?.wantsLayer = true
videoView.contentOverlayView?.layer?.backgroundColor = NSColor.blue.cgColor
also tried adding these:
view.wantsLayer = true
videoView.wantsLayer = true
but the background is still black.
AVPlayerView does not have layer after the initialization or setting wantsLayer property, but creates it later at some point. I was able to change the background with the next code in my AVPlayerView subclass:
override var layer: CALayer? {
get { super.layer }
set {
newValue?.backgroundColor = CGColor.clear
super.layer = newValue
}
}
videoView.contentOverlayView?.layer?.setNeedsDisplay()
try this maybe you just need to update the view. However if you would post more of your code I could try to help more.

Is it possible to (programmatically) set wallpapers for each separate "space"/desktop in macOS?

I'm making a small app for myself to change the desktop background image periodically.
My program contains this block of code:
let screen = NSScreen.main()!
let newWallpaperURL = URL(/* ... */)
// ...
try! NSWorkspace.shared().setDesktopImageURL(newWallpaperURL, for: screen, options: [:])
This works, but only for the current "space" the keyboard is focused on.
e.g. if I'm in a fullscreen app, only the background of the Space occupied fullscreen app will be changed (not the background of my normal desktop).
If I have two Spaces/desktops, it only changes the background image of one of them.
Is it possible to individually set wallpapers for each space programmatically?
You can get all the screens and set all of them.
let screens = NSScreen.screens
let newWallpaperURL = URL(/* ... */)
for i in screens {
try! NSWorkspace.shared().setDesktopImageURL(newWallpaperURL, for: i, options: [:])
}
Use this in Xcode 8.x:
if let screens = NSScreen.screens() {
let newWallpaperURL = URL(/* ... */))
for screen in screens {
try? NSWorkspace.shared().setDesktopImageURL(newWallpaperURL, for: screen, options: [:])
}
}
Unlike other solutions posted here this one will work in the current Xcode 8. NSScreen.screens is a class var in Xcode 9 (currently beta) but a class func in Xcode 8 which is why you need to put .screens() instead of .screens. Also, screens returns an optional so you need to safely unwrap it before passing it to the for loop.