Matt Coneybeare

MC

Using Xcode Asset Catalogs With Multiple Targets

| Comments

A few months ago, I released an App called NYC Bikes that quickly became the most popular non-official Citi Bike app. It resonates with busy New Yorkers because it takes far less time to get the same amount of information than the official app, and it actually works.

Expanding to other cities where Bike Shares exist was a no brainer. Just this last week I released the second of these bike share apps: Chicago Bikes.

The apps share the same UI, 99% of the same code base, with only API endpoints, colors, strings and images swapped out. When building the base framework that would allow me to create new apps for new cities, I found it particularly easy to use Xcode’s new image asset catalog feature. This allowed me to place all shared images in one asset catalog, and the local images in another that would only apply to that particular city, separated by targets.

Then building the apps would pull in the right assets for that target from the xcassets file that pertained to it. Unfortunately, Xcode had other plans.

Despite me checking and double checking that the target memberships were accurate, despite cleaning my build again and again, despite wiping derived data folders and machine restarts, Xcode would persistently copy the wrong images over from another xcassets bundle. I didn’t do any deep-dive debugging on the characteristics of the erroneous behavior, but it appeared that it took images from the first xcassets it found alphabetically. In my case, all other targets copied their resources from my Chicago Bikes.xcassets file because it was the first one.

Googling around for solutions, I ended up trying all permutations of separating the bundles in Xcode folders, Finder folders, renaming bundles and other shenanigans, all to no avail. It appeared I would have to abandon the asset catalog all together and manually manage my images… like it was 2012 or something.

Alas, while sipping on a tasty Bulleit Rye Old Fashioned, I came up with a brilliant idea to hack together a fix! Here is how to hack it yourself:

Xcode didn’t like it when I had multiple localized xcassets bundles in the project, so I ended up making just one. Create a Local Images.xcassets file and add it to the project, then ensure it belongs to all your targets:

Organizer All Targets

Then, for each target, add a Run Script Build Phase. It should run near the beginning of your build process, and should definitely be run before any compilation or copy bundle files steps.

The basic premise here is that we are going to swap out the correct xcassets file before the files get copied over with the binary. Enter the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
echo "Handling Assets"

xcassetsFile="${PROJECT_DIR}/Bikes App/Images/Local Images.xcassets"
appSpecificXcassetsFile="${PROJECT_DIR}/../Artwork/${PRODUCT_NAME}/Local Images.xcassets"

if [ -d "$appSpecificXcassetsFile" ] ; then

    diff "$xcassetsFile" "$appSpecificXcassetsFile" > /dev/null
    if [[ $? != 0 ]] ; then

        echo "Assets file has changed"

        echo "Removing existing xcassets folder: $xcassetsFile"
        rm -rf "$xcassetsFile"

        echo "Copying local xcassets folder: $appSpecificXcassetsFile"
        cp -r "$appSpecificXcassetsFile" "$xcassetsFile"

        touch "$xcassetsFile"

    else

        echo "Assets file has not changed"

    fi

fi

In plain English, this basically checks to see if the location of the target-specific xcassets file (really a directory) exists. If it does, then check to see if it is different than the one already added to Xcode. If it is different, remove the xcassets that Xcode knows about and replace it with the one for this target.

For my setup, xcassetsFile (the one Xcode knows about) is at the root of the Images directory. The appSpecificXcassetsFile assets catalog is located within a folder outside the project directory. This is essential otherwise Xcode will find the wrong images. The App Specific directory it located up a level (notice the ${PROJECT_DIR}/../), and corresponds to the target’s PRODUCT_NAME. You will have to edit the locations of the xcassets files as they pertain to your project, and perhaps some variables here and there, but this will get you started. In the end, it should look something like this:

Run Script

One downside of this method is that if you are making edits to the xcassets file within Xcode, you will need to copy those changes back to the app-specific ones to prevent them from being overwritten on the next build. I am no longer tweaking these files on a regular basis so it works great for my workflow. Enjoy.

Lastly, I run a small software company called Urban Apps. It pays the bills so I can take the time to write helpful posts like this. If you found this posting helpful at all, I would really appreciate it if you would check out my Apps on the iTunes App Store.

Comments

My name is Matt Coneybeare, I design and develop for iOS (iPhone, iPad and iPod Touch), Mac OS X and the Web out of New York. In 2008 I started a software company called Urban Apps that has made some pretty popular apps such as Ambiance and Hourly News. My current Stack Overflow reputation is about 21k.

I was a Rockstar a decade ago, but then went back to school and collected a Bachelor's Degree in Computer Science from U.C. Berkeley. Now I am settled down with my beautiful wife Di and our three doggies Hamachi, Foxy and Millie. While coding, I walk at least 16 miles/day on my Treadmill Desk. When not at my desk, I love exploring New York City as a Yelp Elite.

Contact information

Name
Matt Coneybeare
Email
Website
Twitter
App.net
Instagram
GitHub
Wizpert
Google+