iOS 13 introduced UIImage
instances that auto-adopt to the current UIUserInterfaceStyle
(aka light or dark mode). However, there seem to be only methods to construct such images from named or system images ( imageNamed:inBundle:withConfiguration:
or systemImageNamed:withConfiguration:
).
Is there a way to dynamically generate a universal light/dark mode UIImage
from Core Graphics (eg using two CGImage
s or using UIGraphicsImageRenderer)?
I don't see any API for that but maybe I'm wrong.
You do not create a new UIImageAsset
, instead, you refer one from an existing UIImage
's imageAsset
property, to which you add a dark image variant using UIImageAsset.register(_:with:)
method.
// 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)
Or use this UIImage
extension
extension UIImage {
func registerDarkImage(_ image: UIImage) {
if #available(iOS 12.0, *) {
imageAsset?.register(image, with: .init(userInterfaceStyle: .dark))
}
}
}
Did some research on this some days ago (need this functionality too, but did not implement it so far):
UIImageAsset
in coderegister(_:with:)
of UIImageAsset
(supplying userInterfaceStyle.dark /.light) as trait collection parameters 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;
}
}
maybe, that is what you want.
Here's my implementation in 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: darkTC)
return lightImage
}
else {
return light()
}
}
}
This implementation:
displayScale
and userInterfaceLevel
)Assume we have two variants already loaded:
let lightImage = ...
let darkImage = ...
let result = UIImage.dynamicImage(withLight: lightImage, dark: darkImage)
Assume we want a red image, dynamic for light/dark, simply call:
let result = UIImage.dynamicImage(withLight: UIImage.generate(withColor: UIColor.red),
dark: UIImage.generate(withColor: UIColor.red))
where generate
function is as follows:
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()
}
}
I wanted to avoid using the underlying UIImage imageAsset
property that has been suggested above since the documentation calls out that it can be nil.
I found that by creating the asset and registering images against it, you can get the same thing.
Note: It is important to use the
UITraitCollection(traitsFrom: [.current
initialiser with the current set as it contains a lot more information about the display scales and such.
private func createDynamicImage(light: UIImage, dark: UIImage) -> UIImage {
let imageAsset = UIImageAsset()
let lightMode = UITraitCollection(traitsFrom: [.current, .init(userInterfaceStyle: .light)])
imageAsset.register(light, with: lightMode)
let darkMode = UITraitCollection(traitsFrom: [.current, .init(userInterfaceStyle: .dark)])
imageAsset.register(dark, with: darkMode)
return imageAsset.image(with: .current)
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.