iOS适配深色DarkMode模式

作者: MambaYong | 来源:发表于2021-10-19 13:22 被阅读0次

    iOS在13的版本加入了对深色模式的支持,深色模式下App整体上呈现黑色UI界面,现在许多App都完成了深色模式的适配,但也有少量App未支持深色模式(这些App大多是内嵌较多的H5页面)例如支付宝等,本文主要介绍iOS深色模式的适配。

    System Colors

    Apple为了适配深色模式对UIKit中的UIColor进行了重新定义,例如将.red, .blue.yellow定义为.systemRed.systemBlue.systemYellow,这些新定义的System Colors在深色和浅色模式下表现为不同的颜色,具体的色值可以见苹果的官方文档

    Semantic Colors

    对于一些需要进行文字显示的控件Apple也做了深色模式的适配,Apple新加了Semantic Colors颜色方案,使用Semantic Colors时不需要纠结具体的值,只需要在合适的场景使用,例如当控件是Label时,在没有自定义字体颜色时,可以使用.label类型的的Semantic Colors,在浅色模式下显示黑色字体,在深色模式下显示白色字体;Semantic Colors包括.label.separator.link.systemBackground.systemFill

    对于系统适配的以上深色模式下的颜色可以使用SemanticUI这个App进行查看,该App给出了二种模式下的系统颜色的对比:

    Dynamic Colors

    当然在实际开发中很多情况下我们都是需要自定义颜色的,有幸的是Apple也给出了相应的方案,那就是通过UIColor.init(dynamicProvider: @escaping (UITraitCollection) -> UIColor)这个方法进行创建自定义的semantic color

    使用代码的方式:

    import UIKit
    infix operator |: AdditionPrecedence
    public extension UIColor {
        static func | (lightMode: UIColor, darkMode: UIColor) -> UIColor {
            guard #available(iOS 13.0) else { return lightMode }          
            return UIColor { (traitCollection) -> UIColor in
                return traitCollection.userInterfaceStyle == .light ? lightMode : darkMode
            }
        }
    }
    // 使用
    let dynamicColor = .black | .white
    

    使用Asset Catalog方式

    自动iOS11开始,可以在Asset Catalogs保存自定义颜色,并支持Interface Buildercode二种方式使用,自定义的color目前也支持深色模式。打开Assets.xcassets ,店家左下角的+号按钮新增一个Color Set,在Any Appearence(浅色模式)和Dark Appearence(深色模式)分别添加一种颜色即可。


    当然也支持代码的使用:
    Let view =  Uiview()
    View.backdroundcolor = Color(named: Color)
    

    Border colors

    Border colors在当主题模式发生改变时并不会自动的进行适配,所以需要手动的进行处理,可以通过traitCollectionDidChange(_:)这个方法在进行处理:

    import UIKit
    extension UIColor {
        static let layer = UIColor(light: .init(red: 106 / 255, green: 32 / 255, blue: 119 / 255, alpha: 1),
                                   dark: .init(red: 138 / 255, green: 76 / 255, blue: 146 / 255, alpha: 1))
    }
    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        super.traitCollectionDidChange(previousTraitCollection)
        traitCollection.hasDifferentColorAppearance(comparedTo: traitCollection) {
            layer.backgroundColor = UIColor.layer.cgColor
        }
    }
    

    引起TraitCollection变化有多种情况的,不只是系统显示模式的切换,例如当屏幕方向发生旋转时也会触发上面的方法,所以最好是做一个判断,当TraitCollection的更改影响color的值时才给layer赋值,在Xcode中可以添加一个launch argument:UITraitCollertionChangeLoggingEnabled = YES来检测TraitCollection的改变:

    Dynamic Images

    图片资源同样支持深色模式,需要使用Assets.xcassets,新建一个Assets.xcassets并在Attributes inspector点击Appearances选择Any, Dark,然后分别为Any AppearancesDark Appearances配置响应的图片。


    实际开发中图片资源可能是后台返回的,这时候需要使用image assets
    import UIKit
    public extension UIImageAsset {
        convenience init(lightModeImage: UIImage?, darkModeImage: UIImage?) {
            self.init()
            register(lightModeImage: lightModeImage, darkModeImage: darkModeImage)
        }
        func register(lightModeImage: UIImage?, darkModeImage: UIImage?) {
            register(lightModeImage, for: .light)
            register(darkModeImage, for: .dark)
        }
        func register(_ image: UIImage?, for traitCollection: UITraitCollection) {
            guard let image = image else {
                return
            }
            register(image, with: traitCollection)
        }
        func image() -> UIImage {
            if #available(iOS 13.0, tvOS 13.0, *) {
                return image(with: .current)
            }
            return image(with: .light)
        }
    }
    

    尽量减少图片

    为了适配深色模式,无限的增加图片资源最终会导致包的大小会增加很多,为了减少包的体积,在非必要添加图片的情况下有以下二种方案:

    • 使用tint color
      对于那些像toolbarstab bars的图片,可以用tint color去渲染这些图片以满足深色模式,首先需要让图片以模板的形式渲染:

    或者:

    let iconImage = UIImage()
    let imageView = UIImageView()
    imageView.image = iconImage.withRenderingMode(.alwaysTemplate)
    

    使用:

    public static var tint: UIColor = {
        if #available(iOS 13, *) {
            return UIColor { (UITraitCollection: UITraitCollection) -> UIColor in
                if UITraitCollection.userInterfaceStyle == .dark {
                    return Colors.osloGray
                } else {
                    return Colors.dataRock
                }
            }
        } else {
            return Colors.dataRock
        }
    }()
    imageView.tintColor = Style.Colors.tint
    
    • 反转图片颜色
      反转图片颜色也是一种好的选择,只是不是对所有的图片都合适,可以使用如下代码:
    extension UIImage {
        func invertedColors() -> UIImage? {
            guard let ciImage = CIImage(image: self) ?? ciImage, let filter = CIFilter(name: "CIColorInvert") else { return nil }
            filter.setValue(ciImage, forKey: kCIInputImageKey)
            guard let outputImage = filter.outputImage else { return nil }
            return UIImage(ciImage: outputImage)
        }
    }
    

    自动更新 Dark Mode

    现在支持深色模式的App都会提供浅色和深色模式的选择,同时支持跟谁系统自动切换,当用户选择相应模式后需要用Userdefaults保存用户的选择。

    import UIKit
    public extension UserDefaults {
        var overridedUserInterfaceStyle: UIUserInterfaceStyle {
            get {
                UIUserInterfaceStyle(rawValue: integer(forKey: #function)) ?? .unspecified
            }
            set {
                set(newValue.rawValue, forKey: #function)
            }
        }
    }
    

    当保存了用户选择后同时需要更新当前App的显示:

    public extension UIApplication {
        func override(_ userInterfaceStyle: UIUserInterfaceStyle) {
            // iPad支持多窗口,不支持iPad的话可以删除这段判断
            if #available(iOS 13.0, *), supportsMultipleScenes {
                for connectedScene in connectedScenes {
                    if let scene = connectedScene as? UIWindowScene {
                        scene.windows.override(userInterfaceStyle)
                    }
                }
            }
            else {
                windows.override(userInterfaceStyle)
            }
        }
    }
    public extension UIWindow {
        func override(_ userInterfaceStyle: UIUserInterfaceStyle) {
            if #available(iOS 13.0, tvOS 13.0, *) {
                overrideUserInterfaceStyle = userInterfaceStyle
            }
        }
    }
    public extension Array where Element: UIWindow {
        func override(_ userInterfaceStyle: UIUserInterfaceStyle) {
            for window in self {
                window.override(userInterfaceStyle)
            }
        }
    }
        UIApplication.shared.override(style)
        UserDefaults.standard.overridedUserInterfaceStyle = style
    

    退出或禁用深色模式

    当采用Xcode 11新建项目时会默认开启深色模式的,当没时间或者暂不支持深色模式时可以禁用掉,简单的方法时在Info.plist中添加一个UIUserInterfaceStyle = Light,也支持为某个View或者ViewController单独设置相应的模式,但是这种设置不会影响模态弹出的VC的模式。

    let view = UIView()
    view.overrideUserInterfaceStyle = .dark
    class ViewController: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
            overrideUserInterfaceStyle = .dark
        }
    }
    

    总结:

    本文详细介绍了iOS深色模式的适配,最主要的是通过UITraitCollection来拿到当前的样式来进行相应的颜色上的适配,Apple也提供的新定义的几种系统颜色方案并能自动适配深色模式,深色模式适配代码不多,就是需要将项目所有的界面都要进行适配,工作量巨大。

    相关文章

      网友评论

        本文标题:iOS适配深色DarkMode模式

        本文链接:https://www.haomeiwen.com/subject/hxagoltx.html