Blog post
Every now and again while working on iOS apps as a development agency the need arises to implement a settings screen. This can happen for two reasons:
1. The client specifically requests it. They want a simple screen where the users can enable or disable accessibility and convenience preferences and show acknowledgements.
2. The QA team wants to be able to see the app version, build number, user and analytics data or switch servers and environments while testing.
Most of the time the reason is the former of the two, and usually that settings screen, because of it not being so important design-wise, ends up looking something like this:
This is, of course, just a simplified example, and sometimes the settings can be complex enough that building a separate module with UI and business logic for them is inevitable. However, for simpler cases, there is a much neater and often overlooked feature of the iOS SDK which solves this problem without writing a single line of code!
That’s right! This whole functionality can be implemented without polluting your beautiful app and amazing code with a plain old boring UITableView.
Introducing SettingsBundle
Apple introduced this SDK feature almost a decade ago and the official documentation even states that this is “generally the preferred mechanism to display application preferences”. The feature itself is pretty straightforward and simple, those were simpler times after all, and it hasn’t changed a lot over the years. Nevertheless, even simple things can become powerful when used the right way.
The only thing is, a lot of iOS developers have no idea this even exists! But that’s what we’re here for!
To get the general idea of how the settings bundle works, we’re going to create the same settings that were shown in Picture 1 using this mechanism.
First, let’s create the bundle and add it to our app:
On the project navigator we can see the result of this action: a bundle folder that contains a property list file and a localization folder you can use to translate the strings you use in settings into the languages your app supports. If we go ahead and open the property list we will find a simple example Apple provides for us:
This property list file will now be compiled along with our app, and its contents will automatically be added to the Settings page of our app in the phone settings. So, without a single line of code, to see how this behaves in action all we have to do is run the app once then go to the Settings app of the iPhone or iOS Simulator we are running the app in. This is what we’ll see there:
As you can see, just by modifying the property list file, Apple generates the whole settings page of your app inside the Settings app of the phone. Now you might be wondering: “Ok, great! The user can now modify all these settings, but how can I get these values in my code and adjust the behavior of the app according to them?”. Well, here’s the cool part, all the elements you see here have an associated key and a default value that you can define in the plist file. iOS then automatically adds this key to the UserDefaults storage of your app, so you can easily access these values anywhere you need them.
At this point all we have to do is follow Apple’s documentation and create the elements we want to have in our settings page. Here’s the modified plist file with the same elements as the settings screen from Picture 1:
Now let’s take a look at how this would work in practice:
Just a couple of notes about this demo before we get to the really awesome stuff.
To navigate the user to the app settings page we are just opening the URL defined by Apple as UIApplication.openSettingsURLString
Since the settings are read from UserDefaults, if real-time updates are needed, we need to listen to any changes that the user makes by having our UIViewController observe UserDefaults.didChangeNotification and update our UI accordingly
When big apps attack
When working on complex solutions that are deployed on multiple environments or even markets from a single codebase, put together from a large number of small components and features, it can become quite a challenge to effectively perform quality assurance.
Which app version has this feature? Did the backend deploy this on staging environment? Why am I seeing this feature on market X, wasn’t it supposed to be deployed only to market Y?
These are only some of the questions and issues that arise every day with these types of projects. A part of our everyday jobs at Undabot is constantly striving to find innovative solutions to these problems.
Helping QA
Now that we’ve seen how the settings bundle works in general it’s time to use it to make our jobs easier and reduce the amount of confusion from the aforementioned questions.
Using the knowledge we’ve acquired until now about the settings bundle, it becomes trivial to support showing our QA team the app version and build number. We can even show an app token in the settings, so they can easily attach it in the bug report, or even make the request themselves using Postman and see if the response from the API corresponds to what they’re actually seeing in the app.
Daring to go even further, using the multi-value settings type in the property list, we’re even able to provide them the ability to switch API environments on the fly. They can have only one build installed and use the same one to test features on test, staging and production environments! Here’s an example of the value you can provide to the QA team with just a few simple tweaks:
At last but not least, I would also like to point out another cool thing we can achieve with settings:
Feature flags
As we saw earlier, using the switch element in settings is really easy, so controlling a whole feature with it becomes trivial. This is especially valuable since it allows the QA team to test the app as a set of features that are turned on or off, instead of a big heap of code. Here is a simple Swifty way to neatly organize your feature flags in one place:
Swifty feature flags
As you see from the code, using feature flags this way becomes super simple:
1. Upon app startup call: FeatureFlag.allCases.forEach { $0.type.initialize() } This will initialize all feature flags to the values you wish to provide by default.
2. To check if a feature is on or off use:
if FeatureFlag.feature1.isOn {
// do something
}
3. To add a new feature flag just add a new case in the FeatureFlag enum, define a new struct with the key and initial value, map it to the new enum in the type property and you’re good to go!
Organizing features this way also makes it easier from the programmer’s point of view, since every feature is a moving block that can be turned off at any time the client requests it, or something goes wrong with the APIs the feature uses.
Conclusion
To summarize, settings bundles are a very useful but highly unused mechanism of the iOS ecosystem. They can be used to display settings to your end users or simply to help yourself and your QA team test the app and reduce the confusion around different environments and builds.
With this article, my goal was to provide you with enough information to start using settings bundles for all your development needs, but if any clarification is needed feel free to ask in the comments. Happy coding, and stay safe!