向 iOS 应用添加深色模式
Adding dark mode to iOS app
我正在尝试为我的应用程序添加一个主题(深色主题)。因此,当用户单击 activity 开关时,它会使整个应用程序进入黑暗模式。我已经对黑暗模式进行了硬编码,只是为了看看它会是什么样子;但是现在我希望能够通过 UISwitch 启用和禁用它,但我不确定该怎么做?
class DarkModeTableViewCell: UITableViewCell {
var DarkisOn = Bool()
let userDefaults = UserDefaults.standard
@IBOutlet var darkModeSwitchOutlet: UISwitch!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
@IBAction func darkModeSwitched(_ sender: Any) {
if darkModeSwitchOutlet.isOn == true {
//enable dark mode
DarkisOn = true
userDefaults.set(true, forKey: "DarkDefault")
userDefaults.set(false, forKey: "LightDefault")
} else {
//enable light mode
DarkisOn = false
userDefaults.set(false, forKey: "DarkDefault")
userDefaults.set(true, forKey: "LightDefault")
}
}
}
class DarkModeViewController: UIViewController {
func set(for viewController: UIViewController) {
viewController.view.backgroundColor = UIColor(red: 0.1, green: 0.1, blue: 0.1, alpha: 1.0)
viewController.navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.white]
viewController.navigationController?.navigationBar.tintColor = UIColor.white
viewController.navigationController?.navigationBar.barStyle = UIBarStyle.black
viewController.tabBarController?.tabBar.barStyle = UIBarStyle.black
}
static let instance = DarkModeViewController()
}
然后我所做的是调用每个视图控制器中的函数以查看它的外观,但是如果开关打开或关闭,我需要能够访问 bool 值然后让它执行该功能,否则只是保持不变。如果您还有其他问题,请告诉我,我知道其中一些可能没有多大意义。
更新:这个问题(因此,这个答案)是在 iOS 13 公布之前写的,因此它不使用 iOS 13 个特定的 API。
我会使用通知(NSNotificationCenter
API)来解决这个问题。
这个想法是在暗模式启用和禁用时实时通知您的视图控制器,因此它们也可以实时适应变化。您不需要检查开关的状态或类似的东西。
首先创建两个通知(您也可以只创建一个并在 userInfo
字典中传入所需的主题,但在这种情况下创建两个通知更容易,因为您需要投射Swift).
NotificationsName+Extensions.swift
:
import Foundation
extension Notification.Name {
static let darkModeEnabled = Notification.Name("com.yourApp.notifications.darkModeEnabled")
static let darkModeDisabled = Notification.Name("com.yourApp.notifications.darkModeDisabled")
}
在所有 "themable" 视图控制器上,收听这些通知:
override func viewDidLoad() {
super.viewDidLoad()
// Add Observers
NotificationCenter.default.addObserver(self, selector: #selector(darkModeEnabled(_:)), name: .darkModeEnabled, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(darkModeDisabled(_:)), name: .darkModeDisabled, object: nil)
}
不要忘记在 deinit
中删除它们,因为向无效对象发送通知会引发异常:
deinit {
NotificationCenter.default.removeObserver(self, name: .darkModeEnabled, object: nil)
NotificationCenter.default.removeObserver(self, name: .darkModeDisabled, object: nil)
}
在您的 "themable" 视图控制器中,实现 darkModeEnabled(_:)
和 darkModeDisabled(_:)
:
@objc private func darkModeEnabled(_ notification: Notification) {
// Write your dark mode code here
}
@objc private func darkModeDisabled(_ notification: Notification) {
// Write your non-dark mode code here
}
最后,切换您的开关将触发任一通知:
@IBAction func darkModeSwitched(_ sender: Any) {
if darkModeSwitchOutlet.isOn == true {
userDefaults.set(true, forKey: "darkModeEnabled")
// Post the notification to let all current view controllers that the app has changed to dark mode, and they should theme themselves to reflect this change.
NotificationCenter.default.post(name: .darkModeEnabled, object: nil)
} else {
userDefaults.set(false, forKey: "darkModeEnabled")
// Post the notification to let all current view controllers that the app has changed to non-dark mode, and they should theme themselves to reflect this change.
NotificationCenter.default.post(name: .darkModeDisabled, object: nil)
}
}
这样,当 "theme" 发生变化时,您的所有视图控制器都会收到实时通知,并且它们会做出相应的反应。请注意,您需要采取措施在应用程序启动时显示正确的模式,但我确信您正在这样做,因为您正在使用 UserDefaults 并且可能会检查它们。另外值得一提的是 NSNotificationCenter 不是线程安全的,尽管这无关紧要,因为所有 UI 代码无论如何都应该放在主线程中。
更多信息,您可以查看NSNotificationCenter documentation。
注意:这段代码是建立在 OP 的基础上的。它可以简化(例如,您不需要同时跟踪 "light" 和 "dark" 状态,只需跟踪一个)。
基本上有两种方法可以为您的应用设置主题。第一种方式:使用 Apple 的 UIAppearance proxy. This works very well if your app is very consistent about color usuage across all your views and controls, and not so well if you have a bunch of exceptions. In that case I recommend using a third party pod like SwiftTheme
请注意,这种方法已被 Apple 在全球范围内将 "dark mode" 引入(几乎)所有平台所取代。现在要走的路是 "named colors" 外观变体。
来自 iOS 13 apple 推出了深色主题,如果你想在你的 iOS 应用程序中添加深色主题,你可以在 viewDidLoad() 上应用以下代码行,例如:
if #available(iOS 13.0, *) {
overrideUserInterfaceStyle = .dark
} else {
// Fallback on earlier versions
}
因此您可以更改主题,例如有 2 个选项浅色或深色主题。但是,如果您正在编写上面提到的代码,它将始终只在 iOS 13 运行 台设备上采用深色主题。
overrideUserInterfaceStyle = .light
或者,如果您的设备已经 运行 iOS 13,您可以更改主题,例如:
您甚至可以查看当前设置的主题,例如:
if self.traitCollection.userInterfaceStyle == .dark{
print("Dark theme")
}else{
print("Light theme")
}
来看例子:
override func viewDidLoad() {
super.viewDidLoad()
if self.traitCollection.userInterfaceStyle == .dark{
self.view.backgroundColor = UIColor.black
}else{
self.view.backgroundColor = UIColor.white
}
}
结果:
这是相同的视频: https://youtu.be/_k6YHMFCpas
要在 iOS 13 及更高版本中支持深色模式,您可以使用单色闭包。
@objc open class DynamicColor : NSObject{
public var light : UIColor
public var dark : UIColor
public init(light : UIColor,dark : UIColor) {
self.light = light
self.dark = dark
}
}
extension DynamicColor{
public func resolve() -> UIColor{
return UIColor.DynamicResolved(color: self)
}
}
extension UIColor{
class func DynamicResolved(color: DynamicColor) -> UIColor{
if #available(iOS 13.0, *) {
let dynamicColor = UIColor { (traitCollection: UITraitCollection) -> UIColor in
if traitCollection.userInterfaceStyle == .dark {
return color.dark
} else {
return color.light
}
}
return dynamicColor
} else {
// Fallback on earlier versions
return color.light
}
}
}
在视图中使用它时
UIView().backgroundColor = DynamicColor(light: .white, dark: .black).resolve()
//OR
UIView().backgroundColor = UIColor.DynamicColor(light: .white, dark: .black)
在 SwiftUI 中,这变得更加容易。只需在视图中包含环境变量 colorScheme
并检查它的暗模式,如下所示:
struct DarkModeView: View {
@Environment(\.colorScheme) var colorScheme: ColorScheme
var body: some View {
Text("Hi")
.foregroundColor(colorScheme == .dark ? .white : .black)
}
}
关于这一切如何运作的一篇很棒的文章here
我正在尝试为我的应用程序添加一个主题(深色主题)。因此,当用户单击 activity 开关时,它会使整个应用程序进入黑暗模式。我已经对黑暗模式进行了硬编码,只是为了看看它会是什么样子;但是现在我希望能够通过 UISwitch 启用和禁用它,但我不确定该怎么做?
class DarkModeTableViewCell: UITableViewCell {
var DarkisOn = Bool()
let userDefaults = UserDefaults.standard
@IBOutlet var darkModeSwitchOutlet: UISwitch!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
@IBAction func darkModeSwitched(_ sender: Any) {
if darkModeSwitchOutlet.isOn == true {
//enable dark mode
DarkisOn = true
userDefaults.set(true, forKey: "DarkDefault")
userDefaults.set(false, forKey: "LightDefault")
} else {
//enable light mode
DarkisOn = false
userDefaults.set(false, forKey: "DarkDefault")
userDefaults.set(true, forKey: "LightDefault")
}
}
}
class DarkModeViewController: UIViewController {
func set(for viewController: UIViewController) {
viewController.view.backgroundColor = UIColor(red: 0.1, green: 0.1, blue: 0.1, alpha: 1.0)
viewController.navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.white]
viewController.navigationController?.navigationBar.tintColor = UIColor.white
viewController.navigationController?.navigationBar.barStyle = UIBarStyle.black
viewController.tabBarController?.tabBar.barStyle = UIBarStyle.black
}
static let instance = DarkModeViewController()
}
然后我所做的是调用每个视图控制器中的函数以查看它的外观,但是如果开关打开或关闭,我需要能够访问 bool 值然后让它执行该功能,否则只是保持不变。如果您还有其他问题,请告诉我,我知道其中一些可能没有多大意义。
更新:这个问题(因此,这个答案)是在 iOS 13 公布之前写的,因此它不使用 iOS 13 个特定的 API。
我会使用通知(NSNotificationCenter
API)来解决这个问题。
这个想法是在暗模式启用和禁用时实时通知您的视图控制器,因此它们也可以实时适应变化。您不需要检查开关的状态或类似的东西。
首先创建两个通知(您也可以只创建一个并在 userInfo
字典中传入所需的主题,但在这种情况下创建两个通知更容易,因为您需要投射Swift).
NotificationsName+Extensions.swift
:
import Foundation
extension Notification.Name {
static let darkModeEnabled = Notification.Name("com.yourApp.notifications.darkModeEnabled")
static let darkModeDisabled = Notification.Name("com.yourApp.notifications.darkModeDisabled")
}
在所有 "themable" 视图控制器上,收听这些通知:
override func viewDidLoad() {
super.viewDidLoad()
// Add Observers
NotificationCenter.default.addObserver(self, selector: #selector(darkModeEnabled(_:)), name: .darkModeEnabled, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(darkModeDisabled(_:)), name: .darkModeDisabled, object: nil)
}
不要忘记在 deinit
中删除它们,因为向无效对象发送通知会引发异常:
deinit {
NotificationCenter.default.removeObserver(self, name: .darkModeEnabled, object: nil)
NotificationCenter.default.removeObserver(self, name: .darkModeDisabled, object: nil)
}
在您的 "themable" 视图控制器中,实现 darkModeEnabled(_:)
和 darkModeDisabled(_:)
:
@objc private func darkModeEnabled(_ notification: Notification) {
// Write your dark mode code here
}
@objc private func darkModeDisabled(_ notification: Notification) {
// Write your non-dark mode code here
}
最后,切换您的开关将触发任一通知:
@IBAction func darkModeSwitched(_ sender: Any) {
if darkModeSwitchOutlet.isOn == true {
userDefaults.set(true, forKey: "darkModeEnabled")
// Post the notification to let all current view controllers that the app has changed to dark mode, and they should theme themselves to reflect this change.
NotificationCenter.default.post(name: .darkModeEnabled, object: nil)
} else {
userDefaults.set(false, forKey: "darkModeEnabled")
// Post the notification to let all current view controllers that the app has changed to non-dark mode, and they should theme themselves to reflect this change.
NotificationCenter.default.post(name: .darkModeDisabled, object: nil)
}
}
这样,当 "theme" 发生变化时,您的所有视图控制器都会收到实时通知,并且它们会做出相应的反应。请注意,您需要采取措施在应用程序启动时显示正确的模式,但我确信您正在这样做,因为您正在使用 UserDefaults 并且可能会检查它们。另外值得一提的是 NSNotificationCenter 不是线程安全的,尽管这无关紧要,因为所有 UI 代码无论如何都应该放在主线程中。
更多信息,您可以查看NSNotificationCenter documentation。
注意:这段代码是建立在 OP 的基础上的。它可以简化(例如,您不需要同时跟踪 "light" 和 "dark" 状态,只需跟踪一个)。
基本上有两种方法可以为您的应用设置主题。第一种方式:使用 Apple 的 UIAppearance proxy. This works very well if your app is very consistent about color usuage across all your views and controls, and not so well if you have a bunch of exceptions. In that case I recommend using a third party pod like SwiftTheme
请注意,这种方法已被 Apple 在全球范围内将 "dark mode" 引入(几乎)所有平台所取代。现在要走的路是 "named colors" 外观变体。
来自 iOS 13 apple 推出了深色主题,如果你想在你的 iOS 应用程序中添加深色主题,你可以在 viewDidLoad() 上应用以下代码行,例如:
if #available(iOS 13.0, *) {
overrideUserInterfaceStyle = .dark
} else {
// Fallback on earlier versions
}
因此您可以更改主题,例如有 2 个选项浅色或深色主题。但是,如果您正在编写上面提到的代码,它将始终只在 iOS 13 运行 台设备上采用深色主题。
overrideUserInterfaceStyle = .light
或者,如果您的设备已经 运行 iOS 13,您可以更改主题,例如:
您甚至可以查看当前设置的主题,例如:
if self.traitCollection.userInterfaceStyle == .dark{
print("Dark theme")
}else{
print("Light theme")
}
来看例子:
override func viewDidLoad() {
super.viewDidLoad()
if self.traitCollection.userInterfaceStyle == .dark{
self.view.backgroundColor = UIColor.black
}else{
self.view.backgroundColor = UIColor.white
}
}
结果:
这是相同的视频: https://youtu.be/_k6YHMFCpas
要在 iOS 13 及更高版本中支持深色模式,您可以使用单色闭包。
@objc open class DynamicColor : NSObject{
public var light : UIColor
public var dark : UIColor
public init(light : UIColor,dark : UIColor) {
self.light = light
self.dark = dark
}
}
extension DynamicColor{
public func resolve() -> UIColor{
return UIColor.DynamicResolved(color: self)
}
}
extension UIColor{
class func DynamicResolved(color: DynamicColor) -> UIColor{
if #available(iOS 13.0, *) {
let dynamicColor = UIColor { (traitCollection: UITraitCollection) -> UIColor in
if traitCollection.userInterfaceStyle == .dark {
return color.dark
} else {
return color.light
}
}
return dynamicColor
} else {
// Fallback on earlier versions
return color.light
}
}
}
在视图中使用它时
UIView().backgroundColor = DynamicColor(light: .white, dark: .black).resolve()
//OR
UIView().backgroundColor = UIColor.DynamicColor(light: .white, dark: .black)
在 SwiftUI 中,这变得更加容易。只需在视图中包含环境变量 colorScheme
并检查它的暗模式,如下所示:
struct DarkModeView: View {
@Environment(\.colorScheme) var colorScheme: ColorScheme
var body: some View {
Text("Hi")
.foregroundColor(colorScheme == .dark ? .white : .black)
}
}
关于这一切如何运作的一篇很棒的文章here