Skip to content

Instantly share code, notes, and snippets.

@timonus
Last active January 1, 2024 12:08
Show Gist options
  • Save timonus/8b4feb47eccb6dde47ca6320d8fc6b11 to your computer and use it in GitHub Desktop.
Save timonus/8b4feb47eccb6dde47ca6320d8fc6b11 to your computer and use it in GitHub Desktop.
Programmatically create iOS 13 dynamic images
- (UIImage *)dynamicImage
{
UITraitCollection *const baseTraitCollection = /* an existing trait collection */;
UITraitCollection *const lightTraitCollection = [UITraitCollection traitCollectionWithTraitsFromCollections:@[baseTraitCollection, [UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleLight]]];
UITraitCollection *const purelyDarkTraitCollection = [UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark];
UITraitCollection *const darkTraitCollection = [UITraitCollection traitCollectionWithTraitsFromCollections:@[baseTraitCollection, purelyDarkTraitCollection]];
__block UIImage *lightImage;
[lightTraitCollection performAsCurrentTraitCollection:^{
lightImage = /* draw image */;
}];
__block UIImage *darkImage;
[darkTraitCollection performAsCurrentTraitCollection:^{
darkImage = /* draw image */;
}];
[lightImage.imageAsset registerImage:darkImage withTraitCollection:purelyDarkTraitCollection];
return lightImage;
}
@ULazdins
Copy link

@dangalasko , just tried and it doesn't work for me on Xcode 13.1 and iOS 15 device

The code returns dark image for both light and dark modes

Cursor_and_AppDelegate_swift

@dotswift
Copy link

dotswift commented Feb 9, 2022

This is what worked for me on XCode 13.2 / iOS 15 📦

    static func dynamicImage(light: @autoclosure () -> UIImage, dark: @autoclosure () -> UIImage) -> UIImage {

        let imageAsset = UIImageAsset()
        let lightTraitCollection = UITraitCollection(traitsFrom: [.init(userInterfaceStyle: .light)])
        let darkTraitCollection = UITraitCollection(traitsFrom: [.init(userInterfaceStyle: .dark)])
        imageAsset.register(dark(), with: darkTraitCollection)
        imageAsset.register(light(), with: lightTraitCollection)
        return imageAsset.image(with: .current)
    }

@cragod
Copy link

cragod commented Jul 21, 2022

After creating an imageAsset and registering the image with traitCollection more than 65535 times, the image taken by imageAsset.image(with:) will always be wrong.
P.S. Tried all of the above solutions.

@lafezhang
Copy link

After creating an imageAsset and registering the image with traitCollection more than 65535 times, the image taken by imageAsset.image(with:) will always be wrong. P.S. Tried all of the above solutions.

@cragod did you find any solution?

@cragod
Copy link

cragod commented Dec 28, 2023

After creating an imageAsset and registering the image with traitCollection more than 65535 times, the image taken by imageAsset.image(with:) will always be wrong. P.S. Tried all of the above solutions.

@cragod did you find any solution?

not yet, just use the cache to delay its occurrence.

@lafezhang
Copy link

After creating an imageAsset and registering the image with traitCollection more than 65535 times, the image taken by imageAsset.image(with:) will always be wrong. P.S. Tried all of the above solutions.

@cragod did you find any solution?

not yet, just use the cache to delay its occurrence.

The reason has been identified:

  1. When creating UIImageAsset and registering images, Apple will use an auto-increment as the identifier for the image asset and cache it in a global container. And it will also bind the identifier to the imageAsset object. You can inspect the auto-increment identifier using po [[UIImage new] valueForKeyPath:@"imageAsset._unsafe_mutableCatalog._themeStore._maxNameIdentifier"] in the debugger.
  2. However, when retrieving from the cache, Apple will the identifier = {original identifier} & 0xffff. So, the identifier 65536 becomes 1, which causes the unexpected result.
    image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment