How to remove displayImage tint in Edit Widget view? - swift

I have an IntentHandler where I am setting the displayImage value for the configuration options being provided to my app widget.
On the 'Edit Widget' screen (accessed by long-pressing the Widget), a tint is being applied that is rendering the image entirely blue:
If I tap the value to see all available options, the images are rendered normally.
I suspect the tint is due to the image being part of a button, but as far as I'm aware I don't have direct access to the button to change its options.
Here is a simplified version of the IntentHandler class:
class IntentHandler: INExtension, ConfigurationIntentHandling {
func provideMyDataOptionsCollection(for intent: ConfigurationIntent, with completion: #escaping (INObjectCollection<MyData>?, Error?) -> Void) {
var dataForWidget = [MyData]()
// Retrieve dynamic data here...
let myData = MyData(identifier: String(id), display: name)
// Retrieve corresponding image here...
myData.displayImage = INImage(imageData: (retrievedImage.pngData())!)
dataForWidget.append(myData)
let collection = INObjectCollection(items: dataForWidget)
completion(collection, nil)
}
What is the best way around this?

I was having the exact same issue and came across this post - How do I turn off the blue overlay on the image I'm using with my UIButton?
I ended up following the 2nd approach and going to my Asset Catalog and changing the image's default rendering type to "Original" for the affected images.
This removed the blue tint on the widget customization screen when a selection is made.

Related

Customising the sidebar with NSTintConfiguration

In the Adopt the new look of macOS video, you can use NSTintConfiguration to customise the sidebar icon colour using this code.
func outlineView(_ outlineView: NSOutlineView, tintConfigurationForItem item: Any) -> NSTintConfiguration? {
if case let sectionItem as SectionItem = item {
/*
This outline view uses a type called "SectionItem" to populate its top-level sections.
Here we choose a tint configuration based on a hypothetical `isSecondarySection` property on that type.
*/
return sectionItem.isSecondarySection ? .monochrome : .default
}
// For all other cases, a return value of `nil` indicates that the item should inherit a tint from its parent.
return nil
}
When I tried to force a colour on my app sidebar, let's say yellow for an example like this.
func outlineView(_ outlineView: NSOutlineView, tintConfigurationForItem item: Any) -> NSTintConfiguration? {
return .init(fixedColor: .systemYellow)
}
It (sort of) worked as intended, meaning that any rows that are not selected are customised as intended but when you select a row, the SF Symbols changes colour. I expected it to be yellow.
This is how it looks. You can see the icon tint colours are indeed yellow but only if you do not select them.
What I want is the highlighted row tint colour to be the same as the unselected rows. Like how Apple does it in their apps.
You’re seeing the green accent color row selection highlight (NSTableRowView.emphasized = true) and white icon because your outline view is currently the window’s first responder. If you select a different control outside of the outline view, like an NSTextField in the window’s main content area for example, the row selection will change to the inactive appearance (emphasized = false). This is independent of the new tint configuration API.
Mail and Finder go to some effort to prevent their sidebar outline views from becoming first responder in most situations, so they usually has the inactive appearance like in your second screenshot. You can approximate this behaviour by setting refusesFirstResponder = true on your outline view or by subclassing NSOutlineView and overriding acceptsFirstResponder if you need more fine-grained control.

How to get rid of the white shadow in dark mode in macOS?

I am making a macOS app in Swift and am seeing a weird issue when I switch b/w light and dark modes. The shadow that appears around the image on the light mode is great but the one I am getting by default on the dark mode doesn't look nice. Does anyone know how to fix this? I am getting the same for all other UI elements like Checkboxes, Radio Buttons, etc.
Not nice
Nice
Note:
This image is added via the Interface Builder in Xcode.
I have the shadow checkbox unchecked as well. See below:
Make sure that the superviews of your view don't define a shadow either or that it has an opaque background.
If your superview doesn't have an opaque background, child views will also have a shadow.
You can either try implementing viewDidChangeEffectiveAppearance() on your NSView or do key-value observations on NSApp.effectiveAppearance. This way you'll be notified about appearance changes between light/dark mode so you can react accordingly.
You can detect whether a view is Dark Mode or not. And you hide shadow when in dark mode.
Add an extension to NSView:
extension NSView {
func isDarkMode() -> Bool {
if #available(OSX 10.14, *) {
return effectiveAppearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua
}
return false
}
}
Create a custom view (view, button etc). Override viewDidChangeEffectiveAppearance method in your custom view and update layer.
class CustomView: NSView {
override func viewDidChangeEffectiveAppearance() {
// edit shadow's properties. ex: shadowOpacity.
if isDarkMode() {
// dark mode
} else {
// light mode
}
}
}

How to disable lazy loading in NSTabViewController?

I am designing a SwiftUI wrapper for NSTabViewController with the toolbar style. I want it to be a drop-in replacement for TabView. TabView uses a modifier tabItem(_:) to specify the tab name and icon. So I designed a similar modifier for my own ToolbarTabView:
extension View {
func toolbarTabItem(_ label: LocalizedStringKey, nsImage: NSImage? = nil, tooltip: LocalizedStringKey? = nil) -> some View {
self.preference(key: ToolbarTabItemPreferenceKey.self, value: ToolbarTabItemPreference(label: label, nsImage: nsImage, tooltip: tooltip))
}
}
I wrap each View in a NSHostingController and create a NSTabViewItem. Then I use onPreferenceChange to set the NSTabViewItem's label and image property. Finally, I have a NSViewControllerRepresentable to pass my array of NSTabViewItem to a NSTabViewController. This all works well except for the following issue.
By design NSTabViewController will only load its first tab. This loads the first NSHostingController which lays out the first View. That calls onPreferenceChange and sets the label for the first tab. However, the remaining tabs are not loaded and therefore the label remains unset.
I know that I can re-design my APIs to pass in the labels and images explicitly and that works, but then how does Apple implement their TabView? They must have the same issue with the views being lazy loaded because the macOS implementation of TabView looks like NSTabViewController.
I think a workaround would be to force all the tabs to load, which is the title of this question, but I am open to other ideas as well.
Reference:
https://github.com/utmapp/UTM/blob/dev/Platform/macOS/ToolbarTabView.swift
https://github.com/utmapp/UTM/blob/dev/Platform/macOS/ToolbarTabViewController.swift
Here is the dumb workaround I came up with
public class UTMTabViewController: NSTabViewController {
public override func viewDidAppear() {
super.viewDidAppear()
for i in self.tabViewItems.indices {
self.selectedTabViewItemIndex = i
}
self.selectedTabViewItemIndex = 0
}
}
Basically I force load every tab once the view appears. I really hope there's a better answer than this but I'll leave it here just in case.

How to implement DarkMode Into app in Swift

sorry if that question was asked but couldn't find the right answer across stackOverFlow so I'm asking ..
I'm trying to implement dark mode into my app, but unfortunately it doesn't work well for me while using tableviews, it does changes my background and stuff, but I can't change the color of my groups in my tableview.
Here's an image to illustrate the problem:
https://imgur.com/a/h4A3zOZ (can't upload it here cause its too big).
Also Here is my Code:
// MARK: - Premium Section - DarkMode + Graph:
#IBAction func darkModeSwitch(_ sender: UISwitch) {
let current = sender.isOn ? Theme.dark : Theme.light
if #available(iOS 13.0, *) {
// overrideUserInterfaceStyle = UIUserInterfaceStyle(rawValue: current.stateMode)!
//STEP1: Saving User Defaults Switcher:
saveSwitchToggleDarkMode(switcherState: sender.isOn)
//STEP2: Setting UI Colors Of Settings View:
self.tableView.backgroundColor = current.backgroundColor
///Setting up the barTint Color:
self.navigationController?.navigationBar.barTintColor = current.barTintColor
///Setting up the title text color:
self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor:current.textColor]
///Changing back color in navigation controller:
self.navigationController?.navigationBar.backItem?.backBarButtonItem?.tintColor = current.backItemColor
}
}
You should change the mode on the window's level to apply changes to all your controls e.g:
if #available(iOS 13, *) {
UIApplication.shared.delegate?.window??.overrideUserInterfaceStyle = .dark
}
An alternative (and perhaps easier) method to implement dark mode is to use the iOS dark mode feature that you can trigger in settings.If you want to implement this you can create a custom color set by going to your Assets.xcassets and pressing the plus mark on the bottom -> new color set. On the attributes inspector, name your color under name, and under Appearances, select 'Any, Light, Dark' now you will have a place for 3 different colors. Under Light, put the light mode color, on the dark, the dark mode color.
Then on the place where you wish to implement this color,you can change the color to your custom color in the storyboard like so :-
or you can change it in code with something like
myButton.backgroundColor = UIColor(named: "TestColor")
When the user triggers the Dark mode through their control center or settings, the app will also automatically change accordingly. You can test this by going to settings -> Developer -> Dark appearance or by going to Features -> Toggle Appearance or simply press Shift + Command + A
However this method means that you will not have an independent dark mode because it will only be triggered if the device itself is dark-mode enabled.

How can I read the user selection for OS X menu bar in Xcode (Swift)

I'm creating an agent app for OS X in swift (only showing the app icon in the menu bar). I'm loading the icon for the app from the AppDelegate using:
statusItem.image = NSImage(named: "BlackIcon")
and it works fine.
However, if the user has chosen to use the dark menu bar from the System Preferences -> General, the user won't see the icon as it's black.
So I need to display a different 'WhiteIcon' to the user if they have the option selected.
How can I check whether the user has this option active from my app?
With Swift 3.0, you can use the UserDefaults to access the macOS appearance, or "AppleInterfaceStyle", using the following code:
let mode = UserDefaults.standard.string(forKey: "AppleInterfaceStyle")
If the user has enabled dark mode, the defaults will return a string "Dark". If they have the "light mode" enabled it will return nil. So you will need to wrap that in the following code:
if UserDefaults.standard.string(forKey: "AppleInterfaceStyle") == "Dark" {
statusItem.image = NSImage(named: "WhiteIcon")
} else {
statusItem.image = NSImage(named: "BlackIcon")
}
I'm sure there might be a cleaner way, maybe with a guard but that will get you where you need to be in Swift 3.0
Edit:
The above code will determine the users current "mode". However, using a simple check for the users preference will not result in the correct behavior (e.g. it will only fire when the application starts).
The correct method of performing this is to set the menu icon as a black icon. Then, browse to the asset in the Asset Catalog, and select the menu icon. With the menu selected, browse to the Attributes Inspector and make sure the image is checked with a “Mac” device. Then choose “Render As” set to “Template Image”.
This only requires you to have one icon, in black, and macOS will handle the conversion of the images from dark to light modes.
It appears that you are trying to invert menulet icon color for dark mode. By default OSX handles darkmode and inverts the image color, however you need to specifically add [image setTemplate:YES] to have this work for you if it already doesnt.
Objective-c:
self.statusItem = [[NSStatusBar systemStatusBar]
statusItemWithLength:NSSquareStatusItemLength];
NSImage *image = [NSImage imageNamed:#"statusItemIcon"];
[image setTemplate:YES];
[self.statusItem setImage:image];
swift: (Originally answered by Zhi-Wei Cai at link below)
var isDark = false
func isDarkMode() {
// Swift2
// isDark = NSAppearance.currentAppearance().name.hasPrefix("NSAppearanceNameVibrantDark")
// Swift3+
isDark = NSAppearance.current.name.rawValue.hasPrefix("NSAppearanceNameVibrantDark")
}
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
isDarkMode()
// Now use "isDark" to determine the drawing colour.
if isDark {
// ...
} else {
// ...
}
}
This answer explains it in the detail: NSStatusItem change image for dark tint