In a previous article, I reverse-engineered the .car file format used to store the compiled assets of an Asset Catalog. I also demonstrated how to create a tool to manually parse such files. While this tool can extract a lot of information, it is cumbersome to use if you want to quickly see all the assets contained in a car file.
- Easily preview Mermaid diagrams
- Live update when editing
- Capture screenshots
- Create PNG from the Terminal
- Free download on the Mac App Store
In this article, I explain how to leverage the private CoreUI.framework to build a QuickLook plugin allowing us to inspect the content of car files:
The QLCARFiles
QuickLook plugin can be downloaded at the end of this article, as well as the complete source code. A command line tool is also available to dump the assets to disk.
Table of contents:
- Purpose
- Recap about Asset Catalogs and car files
- Getting the asset variations
- The CUINamedLookup base class
- The CUINamedLookup subclasses
- The QLCARFiles QuickLook plugin
- Downloads
Purpose
In order to analyze iOS and macOS apps, I needed a tool to quickly view the content of car files: a QuickLook plugin was exactly what I needed. In fact I wrote this QuickLook plugin several years ago and kept improving it with each macOS and iOS release. If you read some old articles on this blog, you might have seen screenshots of this QuickLook plugin. For example when I analyzed the Facebook.app for iOS [v. 87.0].
With this tool, I also discovered that Slack for iOS was containing several pictures of animals… Slack quickly exterminated all these puppies in their next release:
Recap about Asset Catalogs and car files
An Asset Catalog
is an important piece of any iOS, tvOS, watchOS and macOS application. It lets you organize and manage the different assets used by an app, such as images, sprites, textures, ARKit resources, colors and data.
When a developer builds the app using Xcode, the asset catalogs containing the various assets (images, icons, textures, …) are not simply copied to the app bundle but they are compiled as car files. The resulting car file contains the asset variations (also called renditions):
- @1x, @2x, @3x resolutions
- Dark Mode or Light Mode
- …
If you are interested about all the details of the car file format, you can read: Reverse engineering the .car file format (compiled Asset Catalogs)
Getting the asset variations
The CoreUI.framework has a simple API to load and extract the assets from a car file:
@interface CUICatalog : NSObject
- (id)initWithURL:(NSURL *)url error:(NSError **)error;
- (void)enumerateNamedLookupsUsingBlock:(void (^)(CUINamedLookup *namedLookup))block;
@end
We can create a CUICatalog
object using -[CUICatalog initWithURL:error:]
and then use -[CUICatalog enumerateNamedLookupsUsingBlock:]
to execute a given block for each asset found. Thus the core of an application to dump all the assets consists of just 2 lines of code:
CUICatalog *catalog = [[CUICatalog alloc] initWithURL:[NSURL fileURLWithPath:inCarPath] error:nil];
[catalog enumerateNamedLookupsUsingBlock:^(CUINamedLookup *namedLookup)
{
// Dump the namedLookup
}];
Et voilĂ ! But as you can imagine, there is a little more to do:
in the block, we get a CUINamedLookup object representing the asset. We need to access the actual data (CGImageRef, CGColor, NSData, …).
the QuickLook plugin needs some code to generate the preview/thumbnail of the assets.
for the command line tool to dump the assets to disk, we need to find a proper name for the asset… which is not as easy as it sounds.
The CUINamedLookup base class
The -[CUICatalog enumerateNamedLookupsUsingBlock:]
method executes a given block for each asset found. It has a single parameter, a CUINamedLookup
object, which contains a lot of interesting properties:
@interface CUINamedLookup : NSObject
@property(readonly) NSString *name;
@property(readonly) NSString *renditionName;
@property(readonly) kCoreThemeIdiom idiom;
@property(readonly) uint64_t subtype;
@property(readonly) kCoreThemeUISizeClass sizeClassHorizontal;
@property(readonly) kCoreThemeUISizeClass sizeClassVertical;
@property(readonly) kCoreThemeFeatureSetMetalFamily graphicsClass;
@property(readonly) kCoreThemeMemoryClass memoryClass;
@property(readonly) kCoreThemeDisplayGamut displayGamut;
@property(readonly) NSString *appearance;
@end
Let’s analyze the different properties. They correspond to the attributes you can set in Xcode.
name and renditionName
The name corresponds to the name of the asset as it appears in Xcode. The renditionName is the filename of the original file dragged into Xcode… but not always.
For example a @1x
asset generally looks like:
name: MyPNG
renditionName: Timac.png
and a @2x
asset is:
name: MyPNG
renditionName: Timac@2x.png
However for PDFs stored as pdf, the renditionName uses the hardcoded string CoreStructuredImage
:
name: MyPDF
renditionName: CoreStructuredImage
For images created from a PDF with the Preserve Vector Data
flag, the renditionName is set to a random unique identifier:
name: MyPNG
renditionName: 52A94C15-AF59-4DBB-8EE9-EE1A8FDF1FDB@1x.pdf
Due to these details, I decided not to rely on the renditionName but to generate an appropriate filename from the name.
idiom
The idiom corresponds to the device family and is an enum similar to the well-known UIUserInterfaceIdiom
:
typedef NS_ENUM(NSInteger, kCoreThemeIdiom)
{
kCoreThemeIdiomUniversal = 0,
kCoreThemeIdiomPhone,
kCoreThemeIdiomPad,
kCoreThemeIdiomTV,
kCoreThemeIdiomCar,
kCoreThemeIdiomWatch,
kCoreThemeIdiomMarketing,
kCoreThemeIdiomMax
};
static const char* const kCoreThemeIdiomNames[kCoreThemeIdiomMax] = { "", "phone", "pad", "tv", "car", "watch", "marketing" };
Don’t ask me what is kCoreThemeIdiomMarketing
… It seems that the Apple engineers working on assetutil
are not sure neither: "Marketing idiom? What were you thinking?"
subtype
The subtype is mainly used to differentiate the Apple Watch models. It contains heights like 320
, 340
, 384
or 390
.
sizeClassHorizontal and sizeClassVertical
The size class for the image width and height is similar to UIUserInterfaceSizeClass
:
typedef NS_ENUM(NSInteger, kCoreThemeUISizeClass)
{
kCoreThemeUISizeClassUnspecified = 0,
kCoreThemeUISizeClassCompact,
kCoreThemeUISizeClassRegular,
kCoreThemeUISizeClassMax
};
static const char* const kCoreThemeUISizeClassNames[kCoreThemeUISizeClassMax] = { "", "compact", "regular" };
graphicsClass
This property corresponds to the Metal features:
typedef NS_ENUM(NSInteger, kCoreThemeFeatureSetMetalFamily)
{
kCoreThemeFeatureSetMetalFamilyDefault = 0,
kCoreThemeFeatureSetMetalFamily1v2,
kCoreThemeFeatureSetMetalFamily2v2,
kCoreThemeFeatureSetMetalFamily3v1,
kCoreThemeFeatureSetMetalFamily3v2,
kCoreThemeFeatureSetMetalFamily4v1,
kCoreThemeFeatureSetMetalFamily5v1,
kCoreThemeFeatureSetMetalFamilyMax
};
static const char* const kCoreThemeFeatureSetMetalFamilyNames[kCoreThemeFeatureSetMetalFamilyMax] = { "", "MTL1,2", "MTL2,2", "MTL3,1", "MTL3,2", "MTL4,1", "MTL5,1" };
memoryClass
This property indicates the minimum device memory configuration required:
typedef NS_ENUM(NSInteger, kCoreThemeMemoryClass)
{
kCoreThemeMemoryClassLow = 0,
kCoreThemeMemoryClass1GB,
kCoreThemeMemoryClass2GB,
kCoreThemeMemoryClass4GB,
kCoreThemeMemoryClass3GB,
kCoreThemeMemoryClass6GB,
kCoreThemeMemoryClassMax
};
static const char* const kCoreThemeMemoryClassNames[kCoreThemeMemoryClassMax] = { "", "1GB", "2GB", "4GB", "3GB", "6GB" };
displayGamut
This property indicates the color gamut:
typedef NS_ENUM(NSInteger, kCoreThemeDisplayGamut)
{
kCoreThemeDisplayGamutSRGB = 0,
kCoreThemeDisplayGamutP3,
kCoreThemeDisplayGamutMax
};
static const char* const kCoreThemeDisplayGamutNames[kCoreThemeDisplayGamutMax] = { "sRGB", "P3" };
appearance
The appareance corresponds to the appearance names to support for example Dark Mode on macOS Mojave. Some example of values are:
- NSAppearanceNameSystem
- NSAppearanceNameDarkAqua
- NSAppearanceNameAccessibilitySystem
- NSAppearanceNameAccessibilityDarkAqua
The CUINamedLookup subclasses
For each type of rendition, there is a corresponding CUINamedLookup
subclass:
- CUINamedImage
- CUINamedData
- CUINamedLayerStack
- CUINamedImageAtlas
- CUINamedExternalLink
- CUINamedTexture
- CUINamedColor
- CUINamedModel
- CUINamedRecognitionImage
- CUINamedRecognitionGroup
- CUINamedRecognitionObject
- CUINamedVectorImage
- CUINamedMultisizeImageSet
CUINamedImage has also 2 subclasses:
- CUINamedMultisizeImage
- CUINamedLayerImage
Let’s look at 3 of these subclasses.
CUINamedImage
The CUINamedImage
class is used for images like PNGs and has an API to retrieve the CGImageRef:
@interface CUINamedImage : CUINamedLookup
@property(readonly) CGSize size;
@property(readonly) CGFloat scale;
-(CGImageRef)image;
@end
CUINamedColor
The CUINamedColor
class is used for named color and has an API to retrieve the CGColorRef:
@interface CUINamedColor : CUINamedLookup
@property (readonly, nonatomic) CGColorRef cgColor;
@property (readonly, nonatomic) NSString *systemColorName;
@end
To view the color in the QuickLook plugin, I create a small image filled with the CGColorRef.
CUINamedData
The CUINamedData
class is used for raw data like pdf, video, gif, text files, … It has an API to retrieve the NSData and UTI:
@interface CUINamedData : CUINamedLookup
@property (readonly, copy, nonatomic) NSString * utiType;
@property (readonly, copy, nonatomic) NSData * data;
@end
Depending on the UTI, the QuickLook plugin displays:
- an image (png, pdf, svg, …)
- an animated gif
- the first frame of the video
- a placeholder image for other unsupported data
The QLCARFiles QuickLook plugin
The QLCARFiles
QuickLook plugin lets you visualize the content of a car file. It extracts the assets using the CoreUI.framework, generates a webpage with all the images, and displays this webpage in the QuickLook window. The use of a webpage has several advantages, one of them being the possibility to render multiple images in a scrolling view.
Features
- Display images and their dimensions and file sizes
- Support for png, pdf, gif, svg, video, …
- Display named colors and their hex values
- Support for Light and Dark mode
- Generate the previews as HTML data to allow scrolling
- Display the total number of assets in the window’s title
- Add a light gray background to images that are too white or transparent
- For thumbnails, render the best asset as icon and the number of assets
- The command line tool has a
-r
option to dump all the car files found in a folder
Known limitations
- No support for special car files used by pro applications like Final Cut Pro.
- Assets of the following type are not supported: CUINamedExternalLink, CUINamedTexture, CUINamedModel, CUINamedRecognitionImage, CUINamedRecognitionGroup, CUINamedRecognitionObject, CUINamedVectorImage, CUINamedMultisizeImage, CUINamedLayerImage. So far I never encountered these types of renditions.
Downloads
macOS 10.14 or later is required. Please note that the precompiled binaries are not code signed. To install the precompiled QuickLook plugin, you should:
- Download and unzip
QLCARFiles.qlgenerator.zip
- Remove the quarantine flag by running in the Terminal
xattr -c -r QLCARFiles.qlgenerator
- Move
QLCARFiles.qlgenerator
to~/Library/QuickLook/QLCARFiles.qlgenerator
. Note that you might need to create the folder~/Library/QuickLook/
. - Execute in the Terminal
qlmanage -r
to make sure the QLCARFiles plugin is loaded
Alternatively you can sign the precompiled binaries or recompile the application yourself.
Update 03.09.2021:
Version 1.1 is available and supports newer Xcode versions. You can find the latest up-to-date source code on GitHub: https://github.com/Timac/QLCARFiles