android ios 夜间模式切换,iOS实现夜间模式

本文实现思路主要参考了这里,大概就是为日间模式与夜间模式各提供一份资源文件,资源文件中包含颜色值与图标名,切换主题加载相应主题的资源并刷新页面的控件即可,这和实现国际化有点类似。

这是本文附带的Demo,Github地址

11a0cc6cf405

Demo

定义资源文件

首先定义资源文件,我们使用JSON做为配置的格式,大概如下:

{

"colors": {

"tint": "#404146",

"background": "#FFFFFF",

"text": "#404146",

"placeholder": "#AAAAAA",

"separator": "#C8C7CC",

"shadow_layer": "#00000026",

"tabBar_background": "#FFFFFF",

"tabBar_normal": "#8A8A8F",

"tabBar_selected": "#404146",

"navigationBar_background": "#FFFFFF",

"cell_background": "#FFFFFF",

"cell_selected_background": "#B8B8B8",

"switch_tint": "#3F72AF"

},

"images": {

"article_loading": "article_loading"

}

}

colors 定义颜色值

images 定义图片

大多数情况下,我们可以把纯色图标的Render AS 设置为 Template Image 来满足不同颜色的渲染,对于不是纯色图标才使用多张图片来定义。

控件样式

首先通用的样式,比如主题色、字体色、背景色等,页面上NavigationBar、UILabel、UIButton等控件基本都固定使用了这些样式,那么这部分我们就可以自动更新。

而需要自定义的 属性样式,我们通过扩展一系列key配置好属性样式名就行了,比如backgroundColorKey、textColorKey,而之后自动更新样式的过程就可以优先判断这些值是否不为空,否则就使用上面的通用样式。

extension UILabel {

/// 自动更新文本色的配置key

@IBInspectable var textColorKey: String? {

get {

return objc_getAssociatedObject(self, &ThemeUILabelTextColorKey) as? String

}

set {

objc_setAssociatedObject(self, &ThemeUILabelTextColorKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)

}

}

}

主题管理类

负责切换主题,获取相应主题的资源,并自动更新控件通用样式或者自定义的属性样式

切换主题

/// 当前主题

fileprivate(set) var style: ThemeStyle {

get {

if let currentStyleString = df.string(forKey: ThemeCurrentStyle),

let currentStyle = ThemeStyle(rawValue: currentStyleString) {

return currentStyle

}

return .default

}

set {

df.set(newValue.rawValue, forKey: ThemeCurrentStyle)

df.synchronize()

//加载主题资源

setup()

//通知现有页面更新

NotificationCenter.default.post(name: .ThemeStyleChange, object: nil)

}

}

/// 切换主题

func switchStyle() {

style = style == .default ? .night : .default

}

获取主题资源

let style = self.style //当前样式

//从应用Bundle中拿相应主题名.theme文件

let path = Bundle.main.path(forResource: style.rawValue, ofType: "theme")!

let url = URL(fileURLWithPath: path)

let string = try! String(contentsOf: url)

let json = JSON(parseJSON: string)

self.colors = [:]

self.images = [:]

//颜色

let colorsJSON = json["colors"].dictionaryValue

colorsJSON.forEach { (key, value) in

self.colors[key] = UIColor(value.stringValue)

}

//图片

let imagesJSON = json["images"].dictionaryValue

imagesJSON.forEach { (key, value) in

self.images[key] = value.stringValue

}

自动更新样式

/// 自动更新到当前主题下的通用样式

///

/// - Parameter view: View

func updateThemeSubviews(with view: UIView) {

guard view.autoUpdateTheme else { //不需要自动切换样式

//更新subviews

//UIButton中有UILabel,所以不需要更新subviews

guard !(view is UIButton) else {

return

}

view.subviews.forEach { (subView) in

updateThemeSubviews(with: subView)

}

return

}

//各种视图更新

if let tableView = view as? UITableView {

//取消当前选择行

if let selectedRow = tableView.indexPathForSelectedRow {

tableView.deselectRow(at: selectedRow, animated: false)

}

tableView.backgroundColor = Theme.backgroundColor

tableView.separatorColor = Theme.separatorColor

}

else if let cell = view as? UITableViewCell {

cell.backgroundColor = Theme.cellBackgroundColor

cell.contentView.backgroundColor = cell.backgroundColor

cell.selectedBackgroundView?.backgroundColor = Theme.cellSelectedBackgroundColor

}

else if let collectionView = view as? UICollectionView {

collectionView.backgroundColor = C.theme.backgroundColor

}

else if let cell = view as? UICollectionViewCell {

cell.backgroundColor = Theme.cellBackgroundColor

cell.selectedBackgroundView?.backgroundColor = Theme.cellSelectedBackgroundColor

}

else if let lab = view as? UILabel {

if let key = lab.textColorKey {

lab.textColor = self.color(forKey: key)

} else {

lab.textColor = Theme.textColor

}

}

else if let btn = view as? UIButton {

if let key = btn.titleColorKey {

btn.setTitleColor(self.color(forKey: key), for: .normal)

} else {

btn.setTitleColor(Theme.textColor, for: .normal)

}

if let key = btn.selectedColorKey {

btn.setTitleColor(self.color(forKey: key), for: .selected)

}

}

else if let textField = view as? UITextField {

if let key = textField.textColorKey {

textField.textColor = self.color(forKey: key)

} else {

textField.textColor = Theme.textColor

}

if let key = textField.placeholderColorKey {

textField.placeholderColor = self.color(forKey: key)

}

}

else if let textView = view as? UITextView {

if let key = textView.textColorKey {

textView.textColor = self.color(forKey: key)

} else {

textView.textColor = Theme.textColor

}

//UITextView不能通过appearance设置keyboardAppearance,所以在此处设置

let keyboardAppearance: UIKeyboardAppearance = self.style == .default ? .default : .dark

textView.keyboardAppearance = keyboardAppearance

}

else if let imageView = view as? UIImageView {

if let key = imageView.imageNamedKey {

imageView.image = self.image(forKey: key)

}

}

else if let switchView = view as? UISwitch {

switchView.onTintColor = Theme.switchTintColor

}

else if let datePicker = view as? UIDatePicker {

datePicker.setValue(Theme.textColor, forKey: "textColor")

datePicker.setValue(false, forKey: "highlightsToday")

}

//主题色

if let key = view.tintColorKey {

view.tintColor = self.color(forKey: key)

}

//背景色

if let key = view.backgroundColorKey {

view.backgroundColor = self.color(forKey: key)

}

//更新subviews

//UIButton中有UILabel,所以不需要更新subviews

guard !(view is UIButton) else {

return

}

view.subviews.forEach { (subView) in

updateThemeSubviews(with: subView)

}

}

其中Theme.xxxColor是扩展的getter属性,用于访问当前样式某个颜色值,建议自定义的颜色与图片也基于Theme扩展。

由于自动更新过程就是对view递归设置,而该方法需要手动调用,调用时机一般是在viewDidLoad中或者收到ThemeStyleChange通知时。对于UITableView与UICollectionView中,通常会在cell的awakeFromNib中调用一次。

BaseXXX

切换样式后会通知ThemeStyleChange,我们在各种BaseXXX中调用updateThemeSubviews。

使用BaseXXX基类的方式确实不优雅,在意的读者可以看下 DKNightVersion 代码,它是基于NSObject扩展的,对业务代码耦合低,但遗憾没有自动更新通用样式功能。

class BaseVC: UIViewController {

deinit {

NotificationCenter.default.removeObserver(self)

}

override func viewDidLoad() {

super.viewDidLoad()

updateTheme()

//监听主题改变通知

NotificationCenter.default.addObserver(self, selector: #selector(self.onThemeChange), name: .ThemeStyleChange, object: nil)

}

@objc func onThemeChange() {

UIView.animate(withDuration: 0.25) {

self.updateTheme()

}

}

/// 更新当前ViewController的主题

func updateTheme() {

if view.backgroundColorKey == nil {

view.backgroundColor = Theme.backgroundColor //顶层View

}

Theme.shared.updateThemeSubviews(with: view)

}

}

其它BaseXXX直接套用以上的代码,放在updateTheme中就行了

BaseTabBarController

tabBar.tintColor = Theme.tabBarSelectedColor

tabBar.barTintColor = Theme.tabBarBackgroundColor

tabBar.backgroundColor = Theme.tabBarBackgroundColor

tabBar.isTranslucent = false

if #available(iOS 10.0, *) {

tabBar.unselectedItemTintColor = Theme.tabBarNormalColor

} else {

UIView.performWithoutAnimation {

self.viewControllers?.forEach({ (vc) in

vc.tabBarItem.setTitleTextAttributes([NSAttributedStringKey.foregroundColor: Theme.tabBarNormalColor],

for: .normal)

vc.tabBarItem.setTitleTextAttributes([NSAttributedStringKey.foregroundColor: Theme.tabBarSelectedColor],

for: .selected)

})

}

}

BaseNavigationController

//背景

let bgImageSize = CGSize(width: view.frame.width, height: 64)

UIGraphicsBeginImageContext(bgImageSize)

Theme.navigationBarBackgroundColor.setFill()

UIGraphicsGetCurrentContext()!.fill(CGRect(origin: CGPoint(), size: bgImageSize))

let bgImage = UIGraphicsGetImageFromCurrentImageContext()

UIGraphicsEndImageContext()

navigationBar.setBackgroundImage(bgImage, for: .default)

navigationBar.backgroundColor = Theme.navigationBarBackgroundColor

navigationBar.barTintColor = Theme.textColor

navigationBar.tintColor = Theme.textColor

navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: Theme.textColor]

UIBarButtonItem.appearance().tintColor = Theme.textColor

//已打开的页面使用appearance无效

viewControllers.forEach { (vc) in

vc.navigationItem.backBarButtonItem?.tintColor = Theme.textColor

vc.navigationItem.leftBarButtonItems?.forEach({ (item) in

item.tintColor = Theme.textColor

})

vc.navigationItem.rightBarButtonItems?.forEach({ (item) in

item.tintColor = Theme.textColor

})

}

BaseXXXCell

class BaseTableViewCell: UITableViewCell {

override func awakeFromNib() {

super.awakeFromNib()

if selectionStyle != .none {

selectedBackgroundView = UIView(frame: frame)

}

Theme.shared.updateThemeSubviews(with: self)

}

}

这里没有监听ThemeStyleChange通知是因为自动更新的过程会更新到TableView下所有可见的UITableViewCell,当然不可见的UITableViewCell也需要更新,我们可以用以下代码手动更新

if let dataSource = tableView.dataSource {

let sectionNumber = dataSource.numberOfSections?(in: tableView) ?? tableView.numberOfSections

for section in 0..

for row in 0..

let cell = dataSource.tableView(tableView, cellForRowAt: IndexPath(row: row, section: section))

Theme.shared.updateThemeSubviews(with: cell)

}

}

}

Cell的Selection不可以设置颜色,我们通过自定义selectedBackgroundView来实现,在自动更新的过程中设置cell.selectedBackgroundView.backgroundColor。

另外如果TableView处于选中状态,选中行的selectedBackgroundView会为nil,我们在设置前先deselectRow。

web页面夜间模式

由于css样式优先级的机制,最新的样式可覆盖旧的样式,所以我们只需要为每种样式添加一种夜间模式样式就行。

/*夜间模式样式*/

.night-mode {

background-color: #333333;

}

.night-mode #articleCon p,

.night-mode #articleCon ol li,

.night-mode #articleCon ul li {

color: #CDCDCD;

}

在原生端切换样式时,通过JS函数把夜间模式的css附加上去就行了,切换回默认主题删除样式即可。

//JS代码

//切换至夜间模式

Enclave.switchToNightMode = function() {

document.querySelector('html').classList.add('night-mode')

}

//切换至白天模式

Enclave.switchToLightMode = function() {

document.querySelector('html').classList.remove('night-mode')

}

细节

UIApplication.shared.statusBarStyle设置

iOS默认不可以通过UIApplication.shared.statusBarStyle设置样式,需要info.plist中把UIViewControllerBasedStatusBarAppearance设置为false

设置UIPickerView文字颜色

func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {

let string = self.dataSource[row]

return NSAttributedString(string: string, attributes: [NSForegroundColorAttributeName: C.theme.textColor])

}

设置UIDatePicker文字颜色

datePicker.setValue(C.theme.textColor, forKey: "textColor")

datePicker.setValue(false, forKey: "highlightsToday") //取消datePicker.date当前日期高亮

UITextView通过appearance设置keyboardAppearance会crash

切换到夜间主题时可能需要把keyboardAppearance设置为UIKeyboardAppearance.dark

let keyboardAppearance: UIKeyboardAppearance = style == .default ? .default : .dark

UITextField.appearance().keyboardAppearance = keyboardAppearance

但以上代码应用在UITextView会Crash,暂不知道什么原因造成的,有同学知道可以告诉下。

所以对于UITextView的keyboardAppearance我们需要通过实例设置

let keyboardAppearance: UIKeyboardAppearance = style == .default ? .default : .dark

textView.keyboardAppearance = keyboardAppearance

文中有何错误还望指教~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值