How to rebrand an iOS app efficiently?
Let's say you built an excellent iOS app, it became highly successful and because of that, some people came to you and asked if you could rebrand it so they can use it in their own organisation. If the proposed conditions are convenient (you want to sell your app like that, you are pleased with the money you get, etc.), you'll do it in a reasonable amount of time: just change the logo, app name, a few more things and you're set. You can even submit the new version to the App Store. After you finish rebranding your app for one or two customers, some other customer comes and tells you that he needs at least 100 rebranded versions of your app. What will you do? You managed to rebrand your app for a few customers, but to create another 100 versions?! It's definitely going to take a lot of time which the client probably doesn't have. Fortunately, we can help you speed things up a bit. In this blog post we will tell you how we managed to create over 50 rebranded versions of an app using some ruby scripts, in just 2 days! We will present the "problem statement", provide and explain code snippets and reveal a nice surprise at the end of this post. If it sounds interesting, it certainly is, so let's get to it.
The Launchpad app that you can see in our portfolio was requested by a client of ours and was designed to be used as a learning platform in schools. The "problem" was that every school wanted the app to be branded with their official logo and display a custom name.
Furthermore, some schools were from the United States and some from Denmark, the latter ones needing a custom login mechanism. To distinguish between these versions of the app (english and danish) we used a global flag that we set in a .pch type file. The flag would have the value YES if the current version of the app was danish, or NO if the version was english.
So we received the task of rebranding the app. What does that mean? Firstly, we needed to change the app's name into one that was requested by the client. Afterwards, we needed to change the app icons, splash screens and the school logo from the login screen. Furthermore, because the app needed to be submitted to the App Store, we also had to change the app's bundle identifier. Lastly, we had to set the .pch flag to the appropriate value, depending on the rebranding type (english or danish).
To achieve all this, we created a clever ruby script. We set a code for each rebranding version which will also help us identify the rebranding type. For example, if we needed to rebrand our app for a school in the US named Casper College, we would set "en2" as the code for Casper College ("en" is the default for Launchpad). If we wanted to rebrand the app for a school in Denmark named Skole Skyen, the school code would be "da". To run the script, it's mandatory to provide the school code.
After we obtained all the school codes, we created two hash tables in our script: one for the school name and one for the bundle identifier. Both will use the school codes as keys:
appNames['en'] = 'Launchpad'
appNames['en2'] = 'Casper College'
appNames['en3'] = 'Spartan Cloud'
appNames['da'] = 'SkoleSkyen'
appNames['da2'] = 'Esbjerg'
# and so on…
bundleIdentifiers['en'] = 'com.classlink.launchpad'
bundleIdentifiers['en2'] = 'com.classlink.caspercollege'
bundleIdentifiers['en3'] = 'com.classlink.easternlancaster'
bundleIdentifiers['da'] = 'com.classlink.skoleskyen'
bundleIdentifiers['da2'] = 'com.classlink.esbjergkommune'
# and so on…
We also created an array containing all the available school codes, which is helpful when someone tries to run the script for a school code that does not exist:
availableLanguages = ["en","en2","en3","en4", ...]
Therefore, when a new rebranding needs to be added, we simply create a new school code, add it to the list of available codes and add the app name and bundle identifier in the corresponding hash tables, using the new school code as the key.
What about image resources? How do we know what app icons, logos and splash screens belong to which rebranding version? All the rebranded images are stored in a folder named "localized_resources", in the project's folder. That is the place where the script is searching for the resources that will rebrand the app. To identify each resource to its corresponding school brand, we add the school code to the resource name. Therefore, if we need to find the rebranded images for a school having the school code "en23", we would search for images that have the extension "_en23" in their name.
localizedResourcesPath = ''
# find the path of the rebranded resource for a given imageName
Find.find("Resources/localized_resources/") do |path|
if found
Find.prune # exit loop
end
next if FileTest.directory?(path)
if path.include? imageName
found = true
path.slice! imageName
localizedResourcesPath = path
next
end
end
In the above code snippet, imageName is the rebranded image name, having the school code extension. When we find the localised resource for a given school code, we store its path and then we replace the currently used image with the rebranded one:
# overwrite the resource in the project's 'path', having 'name', with the rebranded resource
FileUtils.copy("#{localizedResourcesPath}#{imageName}", "#{path}#{name}.png")
FileUtils.copy("#{localizedResourcesPath}#{imageName2x}", "#{path}#{name}@2x.png")
But how do we know which are the currently used images? How do we know what actually needs to be rebranded? Well, we already have a folder that contains all the rebranded images. Their name is identical to the name of the currently used resources, but also has the school code extension. Therefore, we will create a set that will contain the name without school code extension of the rebranded images, and search the project's "Resources" folder for images having the same name. When we find such image, it means that this image is currently used in the app and we apply the above described algorithm for replacing it with the rebranded one:
itemSet = Set.new
# get available localized resource names, without language extension (e.g. Icon-76)
Dir.glob('Resources/localized_resources/**/*.png') do |item|
item = item.split('/')[-1]
item.gsub!(/_[^_]+\.png/,'')
itemSet << item
end
Dir.glob('Resources/**/*.png') do |item| # iterate through all the currently used project resources
itemName = item.split('/')[-1].gsub('.png','') # get resource name without extension
if (itemSet.member?(itemName)) # check if there is a localized resource for the currently used resource
path = item.gsub(/\/[^\/]+.png/,'/') # create the path of the currently used resource
localizeResource(path, itemName, language) # localize the currently used resource
end
end
We clarified how we replace the currently used images with the rebranded ones. Where do we get these rebranded images from, though? We said that the rebranded images names contain a school code extension. Does the client provide them with this extension? Actually no. The client only provides us the app logo and the app icons. Therefore, we have to rename all the app icons and logos to add the required school code extension, and we also have to create 8 splash screen images for each school. If we have 50 schools, that is 400 images for splash screens only! Do we do this by hand? Of course we don't. It would take a very long time to do this manually, so we created a script that does all of the above mentioned tasks for us. If you want to find out more about this script, write a comment and tell us! Or send us an email if you want. If there are enough people interested, we'll probably make a blog post about this too. If not…we might do it anyways.
Returning to this post's topic, we discussed about rebranding images, but we didn't say anything about the app name and bundle identifier. To change these, we simply read the app's Info.plist file into a hash, modify the app name and bundle id based on their specific keys (CFBundleName, CFBundleDisplayName and CFBundleIdentifier) and overwrite the modified contents back into the file.
# read the contents of the project's Info.plist
infoDictionary = Plist::parse_xml(plistPath)
# change the app name and bundle identifier with the rebranded value
infoDictionary['CFBundleDisplayName'] = appNames[language]
infoDictionary['CFBundleName'] = appNames[language]
infoDictionary['CFBundleIdentifier'] = bundleIdentifiers[language]
# overwrite the modified Info.plist back (save modifications)
File.open(plistPath, "w") { |io| io.write(infoDictionary.to_plist) }
To change the .pch flag value, we read the file's contents, find the line that defines it (#define DANISHVERSION YES), replace it with the appropriate flag value based on the school code and overwrite the modified contents back to the file. If the school code contains "en", then the app version is english and if it contains "da", the version is danish.
pch = ''
File.open("Launchpad/Launchpad-Prefix.pch", "r") { |file| pch = file.read}
defineValue = "YES" # the current type of branding: english or danish
defineValue = "NO" if language.include?("en")
pch.gsub!(/#define DANISHVERSION.*\n/, "#define DANISHVERSION #{defineValue}\n")
# rewrite the .pch with the applied modifications
File.open("Launchpad/Launchpad-Prefix.pch", "w") { |file| file.write(pch)}
To sum up, we have talked about changing the app name and bundle identifier, changing the rebranding type .pch flag and replacing the image resources with the rebranded ones. We also told you how we managed to create the rebranded images using a ruby script and asked you if you'd like us to publish a blog post with more details about it, so please let us know.
Finally, because we didn't forget about the surprise we promised you, we want to announce that we are joining the open source community! Therefore, you can check out the source code of this rebranding ruby script here, and adapt it for your needs.
Thanks for reading, we hope we'll see you in the next one!