TIPS and TRICKS Graphics for Universal Apps
To celebrate the release of What it Takes for the iPad we thought we'd share the story of how we ended up converting an app that was originally built for iPhone only into a single universal app that works for all iOS devices. After reading through this you should have picked up some handy tips and tricks whilst also avoiding many of the common pitfalls when developing Universal Apps.
If you are more interested generally about how to develop, please see the first post in this tips and tricks series: TIPS and TRICKS For Outsourcing App Development
We had developed the game in its entirety for iPhone. We then wanted to retrofit iPhone5 support and finally iPad support as a single Universal App.
For each additional screen you want to support, you have essentially three options:
1) Use Auto Resize (autoresizing masks - iOS5 - possibly earlier) - whereby the screen will be extended and the graphical elements repositioned and/or stretched and the UI controls extended automatically by iOS. You can control the layout to some degree by tweaking the struts and springs rules on each element/control. This is fairly restrictive and you can only get the results you want on very simple screens.
2) Use Auto Layout. This requires a minimum deployment version of iOS6 and I have no experience using it but I understand that it's basically an improved version of the above.
3) Create a new layout (XIB) file each new screen size. This is clearly the most developer intensive but offers the most flexibility.
For our game we weren't happy with the limitations that 1) presented. We also wanted to make proper use of the iPhone 5 screen unlike many of the currently popular word games out there who seem to have just done a 'quick hack' which doesn't make any productive use of the new screen space. So, we went with 3) which gave us the most control.
Our main concern whilst adding iPad support was that having multiple copies of each graphical asset (potentially 5 copies of each to support iPhone, iphone retina, iphone5, ipad, ipad retina). This would cause the size of the app to increase significantly. Clearly we did not want it to go over the 50MB limit but ideally we wanted to keep in under 30MB.
When developing for the iPhone initially we only ever included the retina @2x versions of the files. Non-retina iPhone such as the 3GS didnt seem to have a problem scaling down and displaying these images so I thought it would be a good idea to take this idea to the next level - only provide one copy of the images - the iPad retina ones - and let the other devices scale the images down accordingly.
So our plan was to have a single set of iPad retina sized graphics and scale these down for all other devices where a scaled down version was required. Only in cases where graphics were a different shape (e.g. screen backgrounds, due to the screens having different aspect ratios) would multiple files be needed. The naming convention we used was an extension of Apple's recommended one, with our own addition of ~iphone5:
So at most now, we'd only need 3 images rather than 5 (not 6, because there is no non-retina iphone5). For our game we'd mostly only need one copy of each image.
Initially this worked really well and had plenty of benefits. The graphics could be sliced at a single size. If anyone has sliced graphics before they will know it is a tedious process. Having to slice an entire games worth of graphics for potentially 5 different screen sizes would be nightmareish. The build size was also kept to the minimum whilst still providing the very best quality graphics for iPad with Retina. We experienced minimal visual artefacts caused by iOS downscaling the images - it turns out that iOS is pretty good at doing this. This is a good point actually, although the visual artefacts are minimal, you can further reduce the chances of problems by making sure your slice dimensions and positioning of elements within the image are multiples of 4 for the best quality (assuming that the images will be scaled by 0.25 for normal iPhone screens). We do have some slices which have odd dimensions and when dividing it by 2 or 4 you end up with fractional dimensions which has a greater potential to cause graphical issues. However, take a look at the game - it looks crisp and clear on any screen - so don't worry too much about this, you may just need to adjust the odd image if you experience any specific problems.
So it looked great. However, we quickly noticed that the game would crash every so often on the iPad 2 and iPhone 3GS. We immediately knew this was due to the introduction of the larger graphical assets since the game is way stable and had not had a single crash in months of beta testing. These older devices have a limited memory and struggle to handle loading and downscaling huge images. Sure enough, when hooked up to the debugger, each crash was preceeded by a number of memory warnings.
In short, there are three parts to this problem:
1) Large images use huge amounts of memory and scaling them down (probably) causes an even higher memory usage.
2) UIImages's imageNamed: actually caches all images in memory - so the more screens the users navigates through, the more memory gets used.
3) It turns out that iOS is pretty poor at clearing the cache when it receives a memory warning and eventually the game would crash (on older devices).
In order to fix it we knew we were going to have to bite the bullet and provide smaller images for those devices which couldn't handle it. We knew that the 3GS was working well with iphone retina sized graphics so it was just a case of finding a happy medium where graphical memory usage was balanced with overall file size and no crashes occured on and device, even under very heavy load.
Here's what we did:
1) Instead of rescaling and reslicing every image, we opted to use a shell script to automatically reduce the dimensions of every @2x image by half and save it with the same name but without the @2x suffix. Therefore we could continue to slice only at the largest size and had essentially automated the resizing. Apart from adding these new files to the project, we didn't have to do anything else as iOS automatically uses the correct image depending on the screen (using the @2x convention).
2) Further reduced the app size by disabling XCodes automatic PNG optimisation and using ImageOptim to optimise every image.
3) Used imageNamed: sparingly. Where we didnt wan't to explicitly cache the image (e.g. for the clock animation) we used imageWithContentsOfFile: instead. For our game screens this had a remarkable effect. Instead of the memory usage constantly going up as the clock ticks down, it stayed roughly the same throughout the entire round.
We're pleased with the end result - great crisp retina graphics, supporting all devices all in an app size well under 40MB. We've also set ourselves up for further optimisations and have a few more ideas for reducing size and memory usage, whilst improving performance further. I'll save this for another post. Besides, we're pretty happy with this considering that Zynga's Scramble with Friends (iPhone only) is 40MB!
1. The trick is to balance memory usage and app size.
2. Develop and slice all graphics at iPad retina size where a scaled down version will suit for other devices. Where different shape images are needed (e.g. backgrounds) then create them separately and name accordingly - e.g. background@2x~ipad.png
3. Save time and effort and slice all graphics only once - at the largest size.
4. Use a shell script to automatically down-scale all slices by half thereby automatically producing the non-retina version.
5. Use ImageOptim or another image optimiser to further reduce the build size.
6. Understand the differences between imageNamed and imagewithContentsOfFile. Use imageNamed wisely!
Related discussion: http://forums.toucharcade.com/showth...=171528&page=1