To go with dark mode, macOS Mojave introduced a feature called “dynamic wallpapers”. Once enabled, a dynamic wallpaper would cycle between a number of related images1, showing one that was appropriate for the time of day.

Keeping with tradition macOS Catalina includes a new default wallpaper, and while it is a dynamic desktop, it works a bit differently: It only has two images rather than sixteen, and rather than switching between them based on time, the wallpaper is set based on whether your appearance preference is set to light or dark mode. This style is even acknowledged separately in System Preferences as “Automatic” rather than “Dynamic”.

While I was enamoured with Mojave’s dynamic desktops at first, I ended up switching to a regular wallpaper after some time. I don’t use dark mode only at night2, and so I’d often be left with a dark UI and a searing bright wallpaper.

So naturally I was excited to create my own dynamic desktops with this new style, but like with the previous ones, Apple hasn’t said anything about how one would go about doing that.

Mojave’s Dynamic Desktop Format

As it turns out though Mattt at NSHipster had done some digging around into the format for Mojave and that proved to be a good starting place.

Encoded within the heic file for the default dynamic wallpaper for Mojave was a metadata item named “solar”, which detailed the position of the sun in the sky in terms of its altitude and azimuth, for each of the images.

The general format for the solar metadata was as follows:

(
	ap = {
		d // ??
		l // ??
	};
	si = (
		{
			a // altitude
			i // index
			z // azimuth
		},
		...
	)
)

The d and l were bits that Mattt wasn’t able to figure out; more about those in a bit.

And here’s the data in it’s XMP form:

<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
	<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
		<rdf:Description rdf:about="" xmlns:apple_desktop="http://ns.apple.com/namespace/1.0/">
			<apple_desktop:solar>
				<!-- (Base64-Encoded Metadata) -->
			</apple_desktop:solar>
		</rdf:Description>
	</rdf:RDF>
</x:xmpmeta>

Equipped with this information and the companion Playgrounds, I set about trying to figure out Catalina’s dynamic desktop format. It’s worth reading the NSHipster post before proceeding any further since I’m leaning heavily on that.

Catalina’s Dynamic Desktop Format

I hadn’t installed Catalina at this time, so I obtained the wallpaper from here (it’s the Dynamic.heic file).

Reading the metadata, while there wasn’t a solar item to be found, there was one named apr. Here’s the data included with that:

YnBsaXN0MDDSAQIDBFFsUWQQABABCA0PERMAAAAAAAABAQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAFQ==

Here's what I got after putting it through a PropertyListDecoder:

{
	d = 1;
	l = 0;
}

Gone is all the solar positioning data from Mojave’s format, and this is much simpler. Just two keys, with two integer values.

d and l, it turns out, are the indices for the dark and light wallpapers, respectively. Their inclusion in the Mojave format suggests that the “Automatic” style might also be enabled for these wallpapers in the future, however this doesn’t seem to be true as of the first beta for Catalina.

The XMP format is also slightly tweaked, with the apple_desktop:solar tag being replaced with an apple_desktop:apr tag

<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
	<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
		<rdf:Description rdf:about="" xmlns:apple_desktop="http://ns.apple.com/namespace/1.0/">
			<apple_desktop:apr>
				<!-- (Base64-Encoded Metadata) -->
			</apple_desktop:apr>
		</rdf:Description>
	</rdf:RDF>
</x:xmpmeta>

That’s all the information we need to make new wallpapers of our own!

Generating Dynamic Wallpapers

The code below is tweaked from Mattt’s aforementioned Playground.

First we have references to the two light and dark wallpapers. These must be stored in the Playground’s Resources folder:

let lightImage = NSImage(imageLiteralResourceName: "Mojave Day.jpg")
let darkImage = NSImage(imageLiteralResourceName: "Mojave Night.jpg")

Next, a location for where the final image must be stored:

let outputURL = URL(fileURLWithPath: "/Users/harshil/Desktop/output.heic")

Catalina makes some changes to how permissions work for certain folders including the desktop, so you might need to change the location.

We then create a CGImageDestination:

guard
	let imageDestination = CGImageDestinationCreateWithURL(
		outputURL as CFURL,                 // url
		AVFileType.heic as CFString,        // type
		2,                                  // count
		nil)                                // options
else {
	fatalError("Error creating image destination")
}

Then we create a metadata item and populate it as per the XML structure:

let imageMetadata = CGImageMetadataCreateMutable()

guard
	let metadata = try? DynamicDesktopMetadata().base64EncodedMetadata() as CFString,
	let tag = CGImageMetadataTagCreate(
		"http://ns.apple.com/namespace/1.0/" as CFString,        // xmlns
		"apple_desktop" as CFString,                             // prefix
		"apr" as CFString,                                       // name
		.string,                                                 // type
		metadata),                                               // value
	CGImageMetadataSetTagWithPath(imageMetadata, nil, "xmp:apr" as CFString, tag)
else {
	fatalError("Error creating image metadata")
}

We then convert the images to CGImages and write them to file, including the metadata along with the first image:

guard
	let lightCGImage = lightImage.cgImage,
	let darkCGImage = darkImage.cgImage
else {
	fatalError("Error converting images")
}

CGImageDestinationAddImageAndMetadata(imageDestination, lightCGImage, imageMetadata, nil)
CGImageDestinationAddImage(imageDestination, darkCGImage, nil)

And lastly we finalise the conversion:

guard CGImageDestinationFinalize(imageDestination) else {
	fatalError("Error finalizing image")
}

Once this has finished executing, we should have our image at the destination URL, ready for use!

The automatic wallpapers work in Mojave too, although setting them somewhat glitches out the UI in System Preferences.

Even though iOS 13 ships with its own dynamic wallpapers, neither the official Catalina wallpaper nor any that I’ve generated seem to work there. Hopefully that’s just a beta bug.

You can find the full source code for the above on my fork of Mattt’s repo. I have also made some dynamic wallpapers from the wallpapers shipping with iOS 13, which can be downloaded here.


  1. The two wallpapers bundled with Mojave, the eponymous “Mojave” and “Solar Gradients”, include 16 images each.

  2. I switch between themes enough that I even made my first Mac app, Nocturnal, to make it easier to do so.