We started off with a five-week marathon to develop our native iOS app sometime in October. The goal was simple: Develop the app from scratch and aim to get it featured. Problem - We have just 5 weeks before Phocuswright to develop it. Here are some interesting challenges that made us put our coffee/beer down.
Swift vs Objective-C
Taking cue from many blog posts, we had some interesting discussions regarding which language to pick (and for me personally, which language to learn). We went with Swift. Primary reasons that favored Swift:
- Seems like Apple has promising goals for Swift - A quick look at Apple's introduction to Swift in the documentation tells us how much they want to push Swift forward
- The overhead of re-writing the app in Swift after already having it written in Obj-C is hard, more time-consuming and honestly, I don’t think it’s a common occurrence in Startups.
- Swift is a lot more leaner and readable (and much easier to learn than Objective-C)
Our web app is very SVG centric and using the same data model implied that we had to use SVGs in the iOS app not just as static images, but also for creating animations. Problem - iOS doesn't support SVGs
- SVG to PNG - One solution was to convert to all SVGs to PNGs. But this creates a usability issue in two ways:
Let's look at our collections feed design-
Glitchy effect - Sending all PNG links over the feed call and using imageViews which fetches the image data using URL has a glitchy effect (since there can be a state where user sees the screen without the PNGs).
Wait time - On the other hand, we can show the loader for some more time and load all the PNG's before drawing the table view. This adds additional wait time.
SVGKit - This is the only library that a lot of blogs pointed out for SVG usage. But a lot of comments also pointed out that this is resource expensive and buggy.
- Stress testing it on iPhone6 (inflating 1000 svg's on a single view) revealed that it is not buggy.
- This is still not a complete solution since we were unable to create the animations we wanted.
WebView to the rescue - The final solution we came up with was to use UIWebViews with custom HTML code.
- We created a UIWebView extension which accepts the SVG data as a string and inflates the webView with simple HTML code containing just the SVG. This solved the problem of showing SVGs.
- To create animations, we went a step ahead and pumped in JS code to webView. Using JS to Swift function bridges, we were able to enable/disable classes of
<svg>div in webView and thus playing animations.
Example of an animation we created using SVGs -
MVC framework & API Manager
Looking at how lengthy the code is going to be, it only made sense to setup a MVC design. All of our ViewControllers serve as controllers. When they are initialized, they create model objects and views. The model objects are responsible for making any necessary API calls, fetch the data, parse it and return the data to controller through delegates. Controller then uses these models and refreshes the views for rendering.
We wrote an APIManager which handles all the API calls made from the app. Clubbing it with a protocol meant that models are relieved of API handling logic and takes care of only parsing the data returned from the calls. We used Alamofire (a wonderful HTTP networking library) and SwifyJSON (to avoid the messy JSON handling code).
Test vs Prod
We were building backend API to support the app. And some of these API calls are not just data-inbound but also handle bookings from the app. That meant we cannot ping our PROD server during testing and we needed to setup a test environment. We did a multi-fold setup for this.
- Backend server: We deployed a duplicate instance of our backend to www.test-headout.com
- Swift preprocessor macros: Using
#if/#else/#endifmacros, we set the entire app to use our test-server instead of prod when in DEBUG mode. (Since all the network calls flow through APIManager, it is just additional 4 LOC)
- Build schema: We have set our schema so that the build configuration is "Debug" for running and testing and "Release" for archiving.
So the app pings our test server when under testing. And when we archive and upload the app to store, it always pings PROD.
Images: Caching and Animations
I like to call this ImageViews on steroids. Our app is completely image-centric (or heavy). So we can't have the app loading images from S3 every time. Think of the wait time and image sizes! We extended the default UIImageView class and made it powerful.
- Local Caching - All the images loaded from URL's are cached in memory and disk using TMCache library.
- Power of Imgix - We switched to imgix for image processing. It also gave us the flexibility of loading only required portion/size of the image for the current device avoiding excessive data calls. Our UIImageView extension automatically alters the imgix URL's for the current viewport width and height before making the network call.
- Animations - We implemented few basic animations over ImageView extension which can be turned on and off with a single line anywhere in the app. We have added Zoom-in-out functionality and Gyro/Parallax effect and hope to add more soon.
- Blur effects - We wanted blur effects on the background for a lot of our views. The problem was that I was unable to create the same blur effect as the default iOS blur. Simple fix - When we want an image to be blurred, we place the actual image in the background and the blurred image in the foreground. Playing with the alpha of the top blurred image did the magic. This is not the best or desired effect but works very effectively.
Asking user for permissions or rating is always tricky. A very common use case is asking the user to rate/review the app in app store. Though this might increase the number of reviews in the App Store, it also increases the chance of negative reviews. Another example is location permissions. In the off chance that the user didn't give you location permission for the first time, you are (almost) never getting it again. We came up with a schema for all user-permissions.
- Not first - No permission popups will come up when the user opens the app for the first time.
- Context first - Before asking for a permission, give user context about why we need the permission and how it helps him
- 'Yes or No' before 'Yes or No' - Ask user if he wants to give the permission before raising the default system popup. If the user replies 'No' or 'Not now', we deal with it accordingly. If the user says 'Yes', he most likely is going to give us the permission.
- Pros - Lesser or no negative reviews in the app. We can time and ask the same user again for the permissions later
- Cons - Tiresome two layer approach
Apple has added some cool features with iOS9. It didn't make sense to roll out the app without exploring the new changes.
Quick actions - 3D touch adds a new way to interact with mobile other than traditional touch and swipe. We added quick actions to chat/browse/search Headout from the app icon itself. We are yet to experiment with 'Peek and Pop'.
Spotlight Search - This is my personal favourite. If innovated properly, this can be a total game changer for iOS. We have indexed Headout experiences for spotlight search giving users just another way to explore Headout's universe.
Universal links (Deep linking) - Another beautiful concept of using web links for deeplinking- We have deep linked our app with our web app links enabling us to use the same links for marketing. So a user with our app checking out our web links is automatically re-directed to a similar view in our app without losing the context.
Google App indexing - This is Google playing cool. We can now index our apps (both Android and iOS) in a similar way as indexing websites. This opens windows to better SEO and user engagement. And it's super easy to set it up.
Our feed screen in the app is a basic version of our ambitious goal to develop a fun and customized feed for our user. Simply paginating our Feed API would have worked for the basic version. But interesting challenges came up when we added filters. One easy way to do it:
- Enhance API - We can add filter params to our API calls. When applying filters, we can refresh the feed and start pulling in data from paginated API (with filters). Cons - Too much duplicated data and excessive API calls.
Our solution -
- Power the Client - All the filters are applied by the client on the generic paginated API. We made our View controllers intelligent to decide how many pages to fetch to give enough filtered data to user's viewable area. Pros - One API to rule them all (:P) and data re-usability.
Putting it all together
It was a great learning experience on many levels -
- Android vs iOS: I have worked on Android before (and deployed some apps to Play store too). Hence my mind was tuned to design paradigms of Android. Now I have a new feather in my cap and can switch my mindset to either iOS or Android.
- Coffee/ Beer/ Redbull: We pulled off 40-hour work days to push the first version out before the deadline. Such satisfaction, much fun!
And the "best" part - We got featured in the App Store under the Best New Apps section just 3 weeks after the first version's release. Here's to the adrenaline of coding!