How to implement DarkMode Into app in Swift - 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.

Related

SwiftUI Color doesn't update on main page

All my text in my app use the color Color.textColor which changes due to a UserDefault setting:
extension Color {
public static var textColor: Color {
let color = UserDefaults.standard.bool(forKey: "redText") ? Color.red : Color.white
return color
}
}
This is changed via a toggle in my settings section. The problem is, when I toggle this it successfully changes all text colours all my views, apart from the main view.
I've made a gif here to showcase the problem: https://imgur.com/a/LcN7TBi
The bug is there in both simulation and preview.
Edit: When I switch off/on the preview the main page colours actually update to the correct colour.

White background in table section header in iOS14

This issue appeared after building to iOS14 with xcode12.
I have a section header with transparent background, on iOS14 it becomes white with new _UISystemBackgroundView added to the hierarchy.
iOS 14 comes with new two cell configurations:
Content configurations. UIContentConfiguration
As the name suggests, content configurations can help you manipulate the content of the cell like image, text, secondary text, layout metrics and behaviors.
Background configurations UIBackgroundConfiguration
can help with the manipulation of background color, visual effect, stroke, insets and corner radius. All cells will inherit a default background configuration even if we don’t specify one.
The Solution
To get rid of the default iOS14 white background you need to change the UITableViewCell or UITableViewHeaderFooterView backgroundConfiguration as follows
// Add this code in your AppDelegate didFinishLauncingWithOptions
// or you can change configuration of certain subclass using self. backgroundConfiguration = ...
if #available(iOS 14.0, *) {
var bgConfig = UIBackgroundConfiguration.listPlainCell()
bgConfig.backgroundColor = UIColor.clear
UITableViewHeaderFooterView.appearance().backgroundConfiguration = bgConfig
//For cell use: UITableViewCell.appearance().backgroundConfiguration = bgConfig
}
Read this article for more
In your UITableViewHeaderFooterView / UITableViewCell custom class - override next method with implementation example:
Swift:
#available(iOS 14.0, *)
override func updateConfiguration(using state: UICellConfigurationState) {
backgroundConfiguration = UIBackgroundConfiguration.clear()
}
Objective-C:
- (void)updateConfigurationUsingState:(UICellConfigurationState *)state {
self.backgroundConfiguration = [UIBackgroundConfiguration clearConfiguration];
}
Objective-C version of #Husam solution:
if (#available(iOS 14.0, *)) {
UIBackgroundConfiguration *bgConfig = [UIBackgroundConfiguration listPlainCellConfiguration];
bgConfig.backgroundColor = UIColor.clearColor;
[UITableViewHeaderFooterView appearance].backgroundConfiguration = bgConfig;
}
Use iOS 14's configuration based APIs may disable the functions of those legacy APIs (e.g. cell.textLabel, cell.detailTextLabel).
To prevent this system behavior, you can set a backgroundView (legacy API) to your header/footer/cell, and then set a custom backgroundColor for that view.

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
}
}
}

Default tableview cells don't respond to dark mode

Using a custom cell I'm able to get dark mode/normal mode to work properly. But when using the default framework cell Apple has provided it remains white regardless of what mode I enable. I read here
ios13 Dark Mode change not recognized by tableview Cell?
about the same problem. The answer tells me to use this:
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
removeAndReaddGradientIfNeeded()
}
}
But I'm unsure how exactly I'm supposed to use this and how it relates to my cells. My code right now for my cells is this:
if #available(iOS 13, *) {
cell.backgroundColor = UIColor.systemBackground
cell.textLabel?.textColor = UIColor(named: "MainLabelColor")
cell.detailTextLabel?.textColor = UIColor(named: "SubLabelColor")
}
I use system color and custom colors in assets with two modes, one for light and one for dark. Now, this works fine in custom cell, but not in default.
Could anyone show me how to use the delegate function with cells?
Did you try to change the contentView background color? because the content view sits on top of the cell.
if #available(iOS 13, *) {
cell.contentView.backgroundColor = UIColor.systemBackground
//For named color you have to resolve it.
cell.textLabel?.textColor = UIColor(named: "MainLabelColor")?.resolvedColor(with: self.traitCollection)
cell.detailTextLabel?.textColor = UIColor(named: "SubLabelColor")?.resolvedColor(with: self.traitCollection)
//MARK:- Even If your Viewcontroller disabled dark mode, tableView cell will be enabled.
self.overrideUserInterfaceStyle = .unspecified
}
To Support Dark Mode make sure you removed following overrides:-
UserInterfaceStyle default value is unspecified . So, You might have enabled userInterfaceStyle to light in somewhere in your code or list file.
In Plist file check for following key-value and remove them:-
<key>UIUserInterfaceStyle</key>
<string>light</string>
In Code check for the following the line and remove them.
i) If the key window is overridden to light mode, your entire app will be forced to light mode.
UIApplication.shared.keyWindow?.overrideUserInterfaceStyle = .light
ii) If View Controller is overridden to light mode, your entire ViewController will be forced to light mode.
self.overrideUserInterfaceStyle = .light

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