如何从 Core Graphics 生成动态 light/dark 模式的 UIImage?

How to generate a dynamic light/dark mode UIImage from Core Graphics?

iOS 13 引入了 UIImage 个自动适应当前 UIUserInterfaceStyle 的实例(又名亮或暗模式)。但是,似乎只有从命名或系统图像(imageNamed:inBundle:withConfiguration:systemImageNamed:withConfiguration:)构建此类图像的方法。

有没有办法从 Core Graphics 动态生成通用 light/dark 模式 UIImage(例如,使用两个 CGImage 或使用 UIGraphicsImageRenderer)?

我没有看到任何 API 但也许我错了。

前几天研究了一下这个(也需要这个功能,但至今没有实现):

  1. 在代码中创建一个 UIImageAsset
  2. 使用 register(_:with:) of UIImageAsset(提供 userInterfaceStyle .dark / .light)作为特征收集参数注册两个 UIImages https://developer.apple.com/documentation/uikit/uiimageasset/1624974-register
+ (UIImage*)dynamicImageWithNormalImage:(UIImage*)normalImage darkImage:(UIImage*)darkImage{
    if (normalImage == nil || darkImage == nil) {
        return normalImage ? : darkImage;
    }
    if (@available(iOS 13.0, *)) {
        UIImageAsset* imageAseset = [[UIImageAsset alloc]init];
    
        // 注册 lightImage
        UITraitCollection* lightImageTrateCollection = [UITraitCollection traitCollectionWithTraitsFromCollections:
        @[[UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleLight],
          [UITraitCollection traitCollectionWithDisplayScale:normalImage.scale]]];
        [imageAseset registerImage:normalImage withTraitCollection:lightImageTrateCollection];
    
        // 注册 darkImage
        UITraitCollection* darkImageTrateCollection = [UITraitCollection traitCollectionWithTraitsFromCollections:
        @[[UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark],
          [UITraitCollection traitCollectionWithDisplayScale:darkImage.scale]]];
        [imageAseset registerImage:darkImage withTraitCollection:darkImageTrateCollection];
    
        return [imageAseset imageWithTraitCollection:[UITraitCollection currentTraitCollection]];
    }
    else {
        return normalImage;
   }
}

也许,这就是你想要的。

您没有创建新的 UIImageAsset,而是从现有 UIImageimageAsset [=23] 中引用一个=],您可以使用 UIImageAsset.register(_:with:) 方法向其中添加暗图像变体。


// Prepare a UIImage for light mode.
let lightImage: UIImage!

// Prepare a UIImage for dark mode.
let darkImage: UIImage!

// Register your dark mode image to the light mode image's image asset.
lightImage?.imageAsset?.register(darkImage, with: .init(userInterfaceStyle: .dark))

// Now your light mode image actually becomes a dynamic image. Use it.
someImageView.image = lightImage
someButton.setImage(lightImage, for: .normal)

或使用此 UIImage 扩展程序


extension UIImage {
    func registerDarkImage(_ image: UIImage) {
        if #available(iOS 12.0, *) {
            imageAsset?.register(image, with: .init(userInterfaceStyle: .dark))
        }
    }
}

这是我在 Swift 5

中的实现
extension UIImage {
    
    static func dynamicImage(withLight light: @autoclosure () -> UIImage,
                             dark: @autoclosure () -> UIImage) -> UIImage {
        
        if #available(iOS 13.0, *) {
            
            let lightTC = UITraitCollection(traitsFrom: [.current, .init(userInterfaceStyle: .light)])
            let darkTC = UITraitCollection(traitsFrom: [.current, .init(userInterfaceStyle: .dark)])
            
            var lightImage = UIImage()
            var darkImage = UIImage()
            
            lightTC.performAsCurrent {
                lightImage = light()
            }
            darkTC.performAsCurrent {
                darkImage = dark()
            }
            
            lightImage.imageAsset?.register(darkImage, with: UITraitCollection(userInterfaceStyle: .dark))
            return lightImage
        }
        else {
            return light()
        }
    }
}

这个实现:

  • 在评估每个图像时将当前特征与风格相结合(以便包括displayScaleuserInterfaceLevel
  • 在正确的特征集合中执行 auto-closures(以确保正确生成以编程方式生成的图像)​​
  • 但是注册了没有当前特征的深色图像,只指定了深色界面风格(所以,即使另一个特征属性被修改为userInterfaceLevelhorizontalSizeClass,使用当且仅当界面样式为深色时,深色图像将不受影响并仍然使用)

示例 1

假设我们已经加载了两个变体:

let lightImage = ...
let darkImage = ...
let result = UIImage.dynamicImage(withLight: lightImage, dark: darkImage)

示例 2

假设我们想要一个红色图像,动态 light/dark,只需调用:

let result = UIImage.dynamicImage(withLight: UIImage.generate(withColor: UIColor.red),
                                       dark: UIImage.generate(withColor: UIColor.red))

其中generate函数如下:

extension UIImage {
    
    static func generate(withColor color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) -> UIImage {
        let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
        
        UIGraphicsBeginImageContext(rect.size)
        let context = UIGraphicsGetCurrentContext()
        context?.setFillColor(color.cgColor)
        context?.fill(rect)
        
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image ?? UIImage()
    }
}

结果:

Tested on Xcode 13 iOS 14.0 and up

我想避免使用上面建议的底层 UIImage imageAsset 属性,因为文档指出它可以为 nil。

我发现通过手动创建资产并使用尽可能少的特征集合针对它注册图像,您可以获得动态图像。

private func createDynamicImage(light: UIImage, dark: UIImage) -> UIImage {
    let imageAsset = UIImageAsset()
    
    let lightMode = UITraitCollection(traitsFrom: [.init(userInterfaceStyle: .light)])
    imageAsset.register(light, with: lightMode)
    
    let darkMode = UITraitCollection(traitsFrom: [.init(userInterfaceStyle: .dark)])
    imageAsset.register(dark, with: darkMode)
    
    return imageAsset.image(with: .current)
}

下面是使用两张图片的插图,一张取自资产目录,另一张是手动绘制的。两者都设置了亮暗模式的两种变体: