Today I am releasing a new app called "Love Laugh Read Children's Books" (it is free to download) and I would like to share with you a little bit about its inception but mostly some of the interesting technical challenges I solved while developing it.
Recently, my good friend self-published her first children's book called "Braving Bedtime". It is an awesome children's book to help kids conquer their nighttime fears. I helped her with some of the more technical aspects of image editing and book formatting. I really enjoyed that process and the book was so good that I asked her if she would be on board for developing an app version of it. To best show her my vision for the app, I threw together a quick demo app and she loved it. So then I was left to solve some interesting technical problems and that is what I want to share with you here.
I am not going to explain the nitty gritty details of implementation, but instead discuss the high level problem solving process I went through.
Let me first quickly lay out what the app needed to do. I wanted to make the app more than just an e-book. To do that, I decided to make the pages animated and also provide a mode where the app would read to the user. My friend is also working on additional books so I wanted to develop the app as platform for all of her future books. Lastly, and most importantly for our discussion, designing the book was a very collaborative process and I knew that would be even more necessary while designing the animations and audio for the app. I didn't want to go through dozens of rounds of edits. Instead, I needed to be able to sit down and configure the animations quickly and easily with her sitting next to me and providing feedback. This required a good amount of forethought and development work upfront but it paid off big time.
Designing Pages in Interface Builder
Interface Builder has its faults and annoyances, but it seemed like the best option short of developing a completely custom desktop application or website for editing books for the platform. (For those of you newer to iOS and Mac development, Interface Builder is the app now integrated into Xcode for editing storyboards and interface files). It gave me a big head-start in being able to arrange objects quickly and easily for immediate feedback, but Interface Builder is not designed for editing animations. That was the big challenge and I think I came up with a good solution.
The feature I could leverage the best to make the editing experience easier was IBInspectable properties on types. These allow defining custom fields on types to be edited directly in Interface Builder. These properties, on several key types, allowed me to do all of my page design directly in the graphical interface.
The visual layout of the pages was very straightforward. Each page is its own view controller interface file and simply consists of images and text like any standard view. Every file uses the same
PageViewController class. To support animations was a little tricker. I designed a type called
LinearViewAnimation that I could add to each page as simple objects alongside the view in the interface file. I could make a connection from it to the views I wanted the animation to act on. It had several different inspectable properties:
- Duration – How long the animation should take.
- Delay – How long to wait to start the animation.
- Translation – X and Y values for how far to move the views.
- Zoom – X and Y values for how much to zoom the views (separate X and Y values allowed for distortions).
- Rotation – How much to rotate the views.
- Alpha – How to change the opacity of the views.
- IsRepeated – Whether the animation should be repeated or not when done.
- IsAutoReversed – If the animation should be played backwards at then end.
A separate object can be dropped in for each distinct animation and they are automatically picked up and setup by the
PageViewController class while loading the nib file. That eliminates the need for me to connect them explicitly to the view controller which is especially good so that I can't have the error of forgetting to do so.
The one subtly is that these properties should be set to define how to move the view from its current position to its starting position. That means I am actually designing the animations in reverse. This allows me to design each page for its end state instead of its starting state. This is much better because most pages build in and then stay put. I didn't want a bunch of pages to look blank when opened.
This type did leave me with one major question though: what should I do if I want to animate the same view in multiple ways with different timings? For example, let's look at the following page:
The sheets and hands have to animate up while at the same time the hands need to be shaking back and forth slightly.
I considered making my own complicated animation implementation which seemed daunting until I had a very simple epiphany, nested views! I have one container view do the animation up and then inside that a child view that does the repeating animation side to side. The final xib file looks like this:
I love when solutions end up being very simple!
The only other major thing I needed to be able to design in interface builder were certain timed sound effects. Most pages have a single audio file that is played with my friend reading the book, but there are a few pages with special sound effects that need to be played at a very specific time. For this, I created a type called "TimedAudio". It has only two properties:
- Delay – How long to wait to start playing the audio
- Id – The id of the audio file
I wish I could have embedded the file directly into the object but I had to settle for an id system. The
PageViewController looks for an audio file with that id and play it after the specified delay.
Just like with the
TimeAudio objects can be added directly to the xib file and are discovered automatically by the
Quickly Iterating on Designs
For me it was too much to have to recompile and rerun the app every time I made a change to the animation or audio properties. We did a lot of timing tweaks down to the hundredth of a second and that would have been way too long of an experimentation cycle. Instead, I developed a system where I could update the nib file while the app was running and dynamically reload it at will.
For this I developed a second target that I called "page-tester". Instead of presenting each book in sequence, it provides a table view of all the pages so I can jump around the book quickly. Each page also has a reload button so that I can ask it to reload from disk and play again. However, that is not the whole story!
Normally we embed our nib files directly into our bundle, but this is not conducive to updating them on the fly. For this particular target, I added them as a subfolder to the binary. That way, I can update the files individuallly using a script while the app is running in the simulator. The script recompiles the xib files into nib files and places them in the correct folder for the simulator. That way, the update cycle became: make the change, save the file, run the script, and tap the reload button. This was so much faster! Sadly I could only do this on the computer as I couldn't access the file system on the device from my script, but it was still way better than recompiling each time.
Ultimately, both of these design features allowed me to sit down with my friend and quickly tweak designs without her having to watch me code or wait for compiling. It was much closer to sitting down and mocking something up in Photoshop except in this case we were directly designing the final product!
On Demand Resources
The last technical challenge of note for me was the use of On Demand Resources. Essentially On Demand Resources allows us to download resources from Apple as they are needed instead of embedding them directly in our app binary. This was important because each book contains a lot of images and audio files and the app will eventually have several books. More than that, I wanted to use App Thinning so each device downloads images at the smallest possible resolution to save time downloading and space on disk.
All of the image assets came from the Photoshop files we used to create the book version. I had to do some image manipulation and even some drawing to cut out different parts of images to animate them, but I didn't try to manually size them to be appropriate for the iOS devices. Instead, I created a third target in my project called "image-resizer". This target has all of the full resolution images used in all of the pages. When run it does the following:
- Loads each of the pages.
- Lays them out for the logical size of a normal iPad (1024x768).
- Looks up each image and calculates the appropriate size in pixels for each iOS device: iPhone @1x, @2x, and @3x as well as iPad @1x and @2x. Each of these were defined by the largest version of that device (iPhone Plus and 12.9 inch iPad Pro).
- Exports the image in each of those sizes.
This took 20 or 30 seconds for the whole book so I did throw up a progress bar to ease my mind. This allowed me freedom to adjust the size of things when developing without it being a lot of work to adjust the sizes of all the assets. I even configured the app to export the images with the correct naming scheme to allow very easily dragging and dropping them all into an assets file in Xcode. I would definitely call that a major win!
Those were the most interesting technical problems I ran into. The rest was still fun and interesting to develop, but it was more mundane iOS development. If you have any questions or want more implementation details don't hesitate to contact me or reach out to me on Twitter or Linked In.
But before I go, I want to quickly talk about release planning. It has been a while since I have released my own app and the App Store has changed a lot since then. I want to use this opportunity to learn what works well when it comes releasing an app these days.
In preparation, my friend and I contacted several blogs, Youtube channels, websites, and even some child psychologists to see if they were interested in the app and might be willing to tell more people about it. I also reached out to Apple to try to get it featured. Lastly, we did some readings of the physical book at book stores and schools where we also showed off a pre-release version of the app and gave away promo codes to build an email list so we will have some attention right on launch day.
We also planned our launch day with social media posts and contacting review sites that wanted our app to already be published before they would look at it. This post is also shamelessly part of my launch plan. I really hope you found some value in it but it was also a way to get the word out even more. If you know anyone who would be interested in the app (or the book) I would really appreciate you letting them know! You can find out more about the book and where to buy it on the website (that I built in Swift) at LoveLaughRead.com.
I'm sure I will have future blog posts talking about how the release went and I am also planning to play with some different types of advertisements and cross-selling between various versions of the book and future books. Stay tuned to find out what I learn!