Today’s topic solves a problem I recently worked on for a client, and I think it would be interesting to many iOS developers. We all know that when our users don’t like our app or recent update, they jump through hoops to head over to the AppStore to let us know their displeasure by leaving us 1 star ratings and scathing reviews. Unfortunately, I’ve learned it simply comes with the territory of being an iOS Developer.
We rely on our thick skin as we absorb the blow with faux “open mindedness” then try to address the blood bath of reported issues as quickly as possible. The weird part is that when our users are enjoying their experience with our apps, most seem to have amnesia when it comes to the AppStore rating process. For whatever reason, our happy users just can’t seem to remember how to find the AppStore to leave a positive rating and review. But what can we really do about it?
What if we could determine who our good users are and give them a little nudge to leave you that positive rating? That would be huge! Positive reviews and ratings are extremely helpful tools that assist in improving your product as well as encourage other potential users to take the plunge and download your app. We’re talking real value with just a little bit of code! Imagine a world where your good users are able to help you (“the developer”) achieve positive goals like developing a better product. Your users could become advocates to spread the word on just how good your product is! I know it would be epic if every user spent 30 seconds and left a positive rating with a comment on why they love your app! Bad news though. There is no silver bullet to get users to leave you ratings and reviews, but we could try to encourage a few more people to positively rate our app, and that’s what we will cover today.
I’m taking a slightly different approach with this article. Instead of just focusing on a single topic and giving a tutorial on it; I’m going to show you how several components come together to build something useful. To accomplish building our AppRater, we will use the following: iOS Cocoa Touch Frameworks, NSUSerDefaults, NSNotifications, and attributes like the amazing #available. I won’t have time for a deep dive in this post, but I have added a sample project so that you can see the code and to help get you started. My goal is to make you aware of the possibilities and to give you a sampling of their usages in a real project. It’s up to you to explore further, now that you know these gems exist!
What will we cover
- iOS Cocoa Touch Framework
- NSNotifications and NSNotificationCenter
- Swift and Objective C interoperability
- #Available syntax to future proof your code
Building an iOS Cocoa Touch Framework
The first crucial component to our project is making it reusable. We need to make a library that we can drop into multiple projects. As of Xcode 6.0, developers can add their own libraries that can be bundled with their projects. This is extremely cool and will allow you to share your libraries across projects.
iOS developers can now create dynamic frameworks. Frameworks are a collection of code and resources to encapsulate functionality that is valuable across multiple projects. Frameworks work perfectly with extensions, sharing logic that can be used by both the main application and the bundled extensions
For our sample project, we are going to create a Cocoa Touch Framework to hold code for our AppRater library. Using a iOS framework facilitates reuse and helps us isolate functionality that can be used across projects. Once the library is built, you can add the library to your other projects. When you need to make a change, you only need to update the library code, recompile, and re-link to have the new changes. Let’s fire up Xcode and create a new iOS Single View Application project. You can name it whatever you like. This project is going to be a testing mechanism and has no real code, since we are really only focused on the library that we will create.
Once we have created our project, add a library as a new target. Click the project name (AppRaterSample) and click the plus button at the bottom of the targets pane to add a new target to the project. Here we are going to select Cocoa Touch Framework under iOS->Framework & Library. Let’s name the library AppRater. We are going to embed the project into our sample iOS App. This is mostly for testing. Alternatively, you can choose not to embed it in a project, but that makes testing harder.
Once you finish this step, you will have a new folder that we can add source files and resources for your framework. Select the apprater target and change the Deployment Target to 7.0. We want our library to support older versions of iOS going back to and including iOS 7.0.
Let’s create our first Swift file for our AppRater library. Prefix the filename with AP and name the file APAppRater.swift. For this simple library, we will add all of our functionality to this file. Let’s make a Singleton class named APAppRater in this file.
NSUserDefaults for Tracking AppLaunches and Intents
We have created our library scaffolding but don’t have any real content. The next challenge we face is determining how to store persistant data to track app launches. We have a few options like using a sqlite database, CoreData, or simply using flat files. However, we not are going to store a lot of data, so I have choosen to use NSUserDefaults.
NSUserDefaults allows us to persist information like user preferences, to the defaults database built within the OS. We are going to use NSUserDefaults to store the number of times our app has been launched by the user. We could also store the date when the app was first launched (after install) or a wide variety of other useful bits of data. Our end goal is to know how many times a user has launched our app, and by storing launch stats, we can code logic to determine the right time to prompt a user to rate our app. For example, we could determine that a user is engaged if the user launches the app 20 times. Or maybe I just want to wait until the user has used the app at least 5 times over a 30 day period. Storing this type of info will allow us to create logic on when to prompt the user to rate our app and we can store it using NSUserDefaults.
Quick thoughts on NSUserDefaults. I’ve choosen to use constant strings as the keys for each of the key/value pairs. I have defined those at the top of the class. Using a constant string removes some the error when comparing things for equality. Last, I use “if let” optional binding to unwrap the optional values that are returned in each of the “valueForKey” methods. Optional Bindings are extremely useful in Swift, and they are everywhere!
You use optional binding to find out whether an optional contains a value, and if so, to make that value available as a temporary constant or variable. Optional binding can be used with if and while statements to check for a value inside an optional, and to extract that value into a constant or variable, as part of a single action.
- The Swift Programming Language (Swift 2.1)
NSNotifications and NSNotificationCenter
Another typical challenge when working with iOS apps is being notified of and responding to events. In our library, we would like to know when the application has launched and finished the initializing process. Apple sends out a broadcast message when lifecycle events occur, and we want to know about them.
NSNotificationCenter is a centralized hub to send broadcast messages to observers that have registered for a notification. This is actually a huge topic; and you can read more about it here. For our purposes, we will register for a notification to be notified when the app has launched.
Our setup method adds an observer to the NSNotificationCenter to get notified when UIApplicationDidFinishLaunchingNotification is broadcast, and our appDidFinishLaunching selector method is called in response. We want to know when the app has finished it’s initialization and then determine if we need to show the ratings prompt via the logic in the displayRatingsPromptIfRequired() function.
We are using the number of app launches to determine if we should display our ratings alert prompt. Notice the #available syntax in the function. We will come back to that in a moment. The gist is that we have some functionality that is only available iOS 8 or newer, but that our deployment target goes back to iOS 7. We want to ensure that we don’t try to use functionality that will crash our app because it’s unsupported on the phone.
Swift and Objective C interoperability
Swift is still a young language, and the majority of code bases in existence today are written with Objective C. It’s important not to overlook that fact. We should provide ways for Objective C code bases to consume our library. Fortunately for us, the task will be easy. We need to make sure to decorate our classes, methods, and such with the @objc attribute. In addition, we need ensure our functions don’t take or return types that aren’t available to Objective C (like tuples, for example). If you’d like to delve further, you can learn more here.
The @objc attribute makes your Swift API available in Objective-C and the Objective-C runtime. In other words, you can use the @objc attribute before a Swift method, property, subscript, initializer, class, protocol, or enumeration to use it from Objective-C code.
Using Swift with Cocoa and Objective-C (Swift 2.2)
#Available syntax to future proof your code
As I mentioned earlier in the article, we have added some code to our library that might not be supported by iOS versions earlier than 8.0. We need to add protection to ensure our code won’t crash and to make sure we have a fallback plan to execute something different.
The #available syntax can be used to check if an API method is available to use with the current operating system version running on the user’s phone. In our ratings library, we show system alert dialogs to prompt the user to rate the app after a certain number of app launches. Unfortunately for us, Apple changed the way that alerts are displayed in iOS 8, and since we are supporting iOS 7, this presents a problem for us. To handle our problem, I need two different ways of displaying alerts, one for iOS 7.x and one for iOS 8 and above. Using a #available check, I can conditionally handle this problem with ease. The cool additional benefit of attributes in the Swift language is that you can add them to your own code to get the same behavior that Apple has in their APIs.
Using the Library in the Sample App
Ok, we are just about done. We need to use the code in our sample app to test that it works. In the AppDelegate.swift file of our sample test app, we need to import our framework and initialize it. Import apprater to include our library. In the application delegate’s didFinishLaunchingWithOptions function, we can create an instance of the library. Notice that the code has a property named appId to represent the iOS app’s Id on the AppStore. We won’t have a real id for the sample app because it isn’t in the AppStore. However, for a real production app, we would assign the appId a real value so that our library could launch to our app in the AppStore. Then a user could rate our app.
Check out the Code
I understand this is a fairly short article to cover a large project. However, it should be useful as a base from which to build a great tool. I’ve included a link to the sample project for you to explore.I hope you find this post helpful. f you’d like any of the ideas presented in this or other articles of mine covered in more detail, I would be happy to write on them in later posts.
If you find this post helpful, recommend it for others to read. Please visit me at www.gittielabs.com and subscribe to my RSS feed so that you won’t miss a post. I’m also putting together a video course to teach Swift development and could use your input on topics that you feel would be helpful. Thanks for reading!