Script for adding App Icon to Xcode project

Since Unity doesn’t yet have support for setting up an app icon for visionOS, I’ve made a script that will do it for us on post-process. Hope it’s useful for others:

using System;
using System.IO;
using UnityEditor;

public static class XcodePostProcessUtils {
	[Serializable] class XCodeInfo {
		public int version;
		public string author;
	}

	[Serializable] class XCodeImage {
		public string idiom;
		public string filename;
		public string scale;
	}

	[Serializable] class XCodeImageSet {
		public XCodeInfo info;
		public XCodeImage[] images;
	}

	[Serializable] class XCodeImageStackLayer {
		public string filename;
	}

	[Serializable] class XCodeImageStack {
		public XCodeInfo info;
		public XCodeImageStackLayer[] layers;
	}
	
	[Serializable] class XCodeContents {
		public XCodeInfo info;
	}

	public static void SetupVisionOSIcon(
		string buildPath,
		int version,
		string author,
		params Tuple<string, string>[] layers)
	{
		var assetPath = $"{buildPath}/Unity-VisionOS/Images.xcassets/";

		// Delete auto-generated blank App Icon
		var oldIconName = "AppIcon.appiconset";
		var iconPath = $"{assetPath}{oldIconName}/";
		if (Directory.Exists(iconPath)) Directory.Delete(iconPath, true);

		// Create ImageStack
		var newIconName = "AppIcon.solidimagestack";
		var imgStackPath = $"{assetPath}{newIconName}/";
		if (Directory.Exists(imgStackPath)) Directory.Delete(imgStackPath, true);
		Directory.CreateDirectory(imgStackPath);

		var imageStack = new XCodeImageStack {
			info = new XCodeInfo { version = version, author = author },
			layers = new XCodeImageStackLayer[layers.Length]
		};

		// Loop through layers
		for (var i = 0; i < layers.Length; ++i) {
			// Create ImageStackLayer
			var imgStackLayerFileName = $"{layers[i].Item1}.solidimagestacklayer";
			var imgStackLayerPath = $"{imgStackPath}{imgStackLayerFileName}/";
			Directory.CreateDirectory(imgStackLayerPath);
			
			// Add layer to ImageStack layers
			imageStack.layers[i] = new XCodeImageStackLayer {
				filename = imgStackLayerFileName
			};

			// Create Contents.json for ImageStackLayer
			var imgStackLayer = new XCodeContents {
				info = new XCodeInfo { version = version, author = author }
			};
			
			File.WriteAllText(
				imgStackLayerPath + "Contents.json",
				EditorJsonUtility.ToJson(imgStackLayer, prettyPrint: true)
			);
			
			// Create ImageSet for layer
			var filePath = AssetDatabase.GUIDToAssetPath(layers[i].Item2);
			var fileName = Path.GetFileName(filePath);
			
			var imgSetPath = $"{imgStackLayerPath}Content.imageset/";
			Directory.CreateDirectory(imgSetPath);
			
			// Copy Icon into ImageSet
			File.Copy(filePath, imgSetPath + fileName);
			
			var imageSet = new XCodeImageSet {
				info = new XCodeInfo { version = version, author = author },
				images = new [] {
					new XCodeImage {
						idiom = "vision",
						filename = fileName,
						scale = fileName.Contains("@2x") ? "2x" : "1x",
					}
				}
			};
			
			// Create Contents.json for ImageSet
			File.WriteAllText(
				imgSetPath + "Contents.json",
				EditorJsonUtility.ToJson(imageSet, prettyPrint: true)
			);
		}

		// Create Contents.json for ImageStack
		File.WriteAllText(
			imgStackPath + "Contents.json",
			EditorJsonUtility.ToJson(imageStack, prettyPrint: true)
		);
	}
}

Use it like so in a PostProcessBuild method:

const string iconDir = "Assets/Icons/visionOS";

var iconGuidFront = AssetDatabase.FindAssets("AppIcon-1024Front@2x", new[] { iconDir })[0];
var iconGuidMiddle = AssetDatabase.FindAssets("AppIcon-1024Middle@2x", new[] { iconDir })[0];
var iconGuidBack = AssetDatabase.FindAssets("AppIcon-1024Back@2x", new[] { iconDir })[0];

XcodePostProcessUtils.SetupVisionOSIcon(
	buildPath: buildPath,
	version: 1,
	author: "Xcode", 
	Tuple.Create("Front", iconGuidFront),
	Tuple.Create("Middle", iconGuidMiddle),
	Tuple.Create("Back", iconGuidBack)
);
7 Likes

Hi there! Thanks for sharing this workaround! FYI, we’ve added support for visionOS app icons in Player Settings to Unity 2022.3.18, which should ship next week.

2 Likes

thanks for finally including this - looking forward to .18f1