The NSUserDefaults Class allows you to save settings of your app so that when the user closes the app or perhaps reboots their iOS device, the settings you set are retained. A quick example could be a maps app that lets you set it to show distances in Miles or Kilometers.
Before I continue, I want to point out that if you want to share settings across devices then you need to look at storing preferences in iCloud.
In todays tutorial I want to show a quick demonstration of how NSUserDefaults works and how you create the object as well as save data. I will also show you how to retrieve the data so that your app can load up with the settings the user selected.
Various types of data can be stored in NSUserDefaults which includes NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. Typically you would wrap a UIImage in to NSData to store in NSUserDefaults. Of course, you can also use other options to store data such as CoreData, but NSUserDefaults is a quick and simple way to store small amounts of data with just a few lines of code.
Lets begin by creating a new Single View application in Xcode. We then need to add a few things to the storyboard. Although you can follow this tutorial step by step, perhaps you could also experiment with your own ideas. The storyboard below has two views. The standard View Controller on the left that is set up when you create a Single View App. The second is a Table View Controller. I chose a Table View Controller because this is how I typically see settings views arranged. Of course, it isn’t essential that you use a Table View Controller for a settings view.
A quick run down of what we have… The left View Controller is the main menu of the app. It looks a little sparse and perhaps you can add to that view what you learn in this tutorial by showing the represented data. At the bottom we have a Toolbar along with a settings gear which is commonly seen on iOS apps. Tap on the gear icon and a Modal Segue with a Flip Horizontal transition takes us to our Table View Controller. In there, we have a static cells table set in to 3 groups (we will change that to 4 groups later) with the first group having 3 cells which allows you to enable and disable social networking settings which is followed by a distance unit setting which is followed by a units for temperature.
Next we need to create a custom class for our Table View Controller. I called mine SettingsView. If you are unsure how to do this and how to assign the custom class to the Table View Controller then take a look at the instructions here.
Before we get to working with the NSUserDefaults class we need to set up more of our interface and connect it up to the View Controller classes so that we have something to work with. This works best if we enable the assistant editor and click on the Storyboard. Doing this will bring up the correct header file associated with the selected view.
Click on the Table View Controller and then select the SettingsView.m file. We need to start connecting up the IBActions. Before we do that, it might be a good time to comment out several methods in your SettingsView.m file. The reason for this is that we are using Static Cells which means those warnings don’t apply to us and therefore, we just comment out those methods plus other methods which are not needed for this example.
You just need to keep the following methods:
– (id)initWithStyle:(UITableViewStyle)style
– (void)viewDidLoad
– (void)didReceiveMemoryWarning
Lets start connecting up the switches, segmented controls and buttons. When finished, you will have something like you see below (please note that I added a Done button in another group… this is so we can dismiss the view and go back to the main view).
On line 32 of SettingsView we create an IBAction by CTRL+dragging from one of the switches in the “Share Settings” group. We call the method shareSettingsSwitched and specify the type as a UISwitch and leave the event and arguments as standard. We particularly need the sender because instead of creating three separate methods for the three switches, we will just connect them all up to the same method and use tags to distinguish which switch was toggled. To do this, CTRL+drag from the second switch and connect it to the same block of code that you created for the first method. Do the same for the third switch. Sender is also useful for some queries later on in the code.
You then need to inspect the UISwitches and set the top one to tag “0”, the second needs to be tagged with a 1 and the third tagged with a 2.
Next up is the distances segment which uses a UISegmented Control. We will also use sender here so we can query what segment is selected.
I did the same for the units (temperatureSegment method) and then created the donePressed method which will simply be used to dismiss the view.
Also, we need to set up some IBOutlets for the controls in the header file so that when the settings page loads, it displays all the defaults we have set. IBOutlets are created in the same way that IBActions are created. You need to CRTL+drag from the switch, segment or button to the header file. I defined my IBOutlet properties as follows:
@property (weak, nonatomic) IBOutlet UISwitch *facebookSwitch;
@property (weak, nonatomic) IBOutlet UISwitch *twitterSwitch;
@property (weak, nonatomic) IBOutlet UISwitch *instagramSwitch;
@property (weak, nonatomic) IBOutlet UISegmentedControl *distancesSwitch;
@property (weak, nonatomic) IBOutlet UISegmentedControl *temperatureSwitch;
We will add some code to viewDidLoad which will make these display correctly in SettingsView later on in the tutorial.
Creating and Using an NSUserDefaults Object
Now that we have some very basic code in place, we now need to start storing the switch settings in NSUserDefaults. The class documentation shows that we need to get an instance of SharedUserDefaults. To do this we use the following line of code which creates a pointer and then uses the sharedUserDefaults class method to either get or create the shared user defaults.
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
Now that we have the standardDefaults pointer, we can now start saving data in NSUserDefaults. To do that, we use the following line of code:
[standardDefaults setObject:@"Off" forKey:@"facebookKey"];
Lets now add some code to our sharedSettingsSwitched method as follows:
- (IBAction)shareSettingsSwitched:(UISwitch *)sender {
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
if (sender.tag == 0) {
if (sender.on == 0) {
[standardDefaults setObject:@"Off" forKey:@"facebookKey"];
} else if (sender.on == 1) {
[standardDefaults setObject:@"On" forKey:@"facebookKey"];
}
} else if (sender.tag == 1) {
if (sender.on == 0) {
[standardDefaults setObject:@"Off" forKey:@"twitterKey"];
} else if (sender.on == 1) {
[standardDefaults setObject:@"On" forKey:@"twitterKey"];
}
} else if (sender.tag == 2) {
if (sender.on == 0) {
[standardDefaults setObject:@"Off" forKey:@"instagramKey"];
} else if (sender.on == 1) {
[standardDefaults setObject:@"On" forKey:@"instagramKey"];
}
}
[standardDefaults synchronize];
}
A quick run down of what is going on with this code (feel free to optimise it or change it as you see it best working… options could include separating out the setObject lines of code in to a separate method rather than duplicating the code three times in the method).
Line 2 get the standardUserDefaults or creates it if needed.
Line 3 checks which switch was used. Tag 0 indicates the Facebook switch in this case.
Line 4 checks the status change of the button. This is taken from the sender argument and the .on @property is checked. If the switch is off, it runs line 5 but if on, it runs the code on line 7. Here we create a new key called facebookKey and store either an On or Off NSString.
Jumping down to the bottom of the method, we implement [standardDefaults syncronize]; which saves the changes made as they are made.
Next we will take a look at the remaining methods and how we can set the states on those. For this, we will implement the following code:
- (IBAction)distanceSetting:(UISegmentedControl *)sender {
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
if (sender.selectedSegmentIndex == 0) {
[standardDefaults setObject:@"KM" forKey:@"distanceSettingKey"];
} else if (sender.selectedSegmentIndex == 1) {
[standardDefaults setObject:@"Miles" forKey:@"distanceSettingKey"];
}
[standardDefaults synchronize];
}
- (IBAction)temperatureSetting:(UISegmentedControl *)sender {
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
if (sender.selectedSegmentIndex == 0) {
[standardDefaults setObject:@"F" forKey:@"temperatureSettingKey"];
} else if (sender.selectedSegmentIndex == 1) {
[standardDefaults setObject:@"C" forKey:@"temperatureSettingKey"];
}
[standardDefaults synchronize];
}
- (IBAction)cancelPressed:(UIButton *)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (IBAction)donePressed:(UIButton *)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
On line 2 we get an instance of the standardUserDefaults.
Line 3 checks which segment was tapped. Depending on which one we either store a KM or Miles as an NSString in the distanceSettingKey.
On lines 11 – 19 we do a similar thing to the distanceSettingKey but instead we store the values in to the temperatureSettingKey.
On lines 21 – 27 we dismiss the view controller.
Retrieving Data from NSUserDefaults
Now that the app is saving the settings data correctly, we now need to make sure that the switches and segments are all correct when the Table View is loaded. To do this we need to retrieve the data we stored when the view loads. A good place to put this is in the viewDidLoad method.
As with all programming, there are countless ways that this can be achieved. The way I opted at first was to use an NSString to store the value stored in the key and then do some conditional checking with an IF statement and then set the button as needed.
The line of code to retrieve an NSString key is below which is followed by the conditional statement to choose which state to put the button in.
NSString *facebookSetting = [standardDefaults stringForKey:@"facebookKey"];
if ([facebookSetting isEqualToString:@"On"]) {
self.facebookSwitch.on = YES;
} else {
self.facebookSwitch.on = NO;
}
On line 1 we retrieve the facebookKey from the standardDefaults and store that in an NSString called facebookSetting.
On line 2 we check if facebookSetting isEqualToString @”On” and if so, we set the IBOutlet property called facebookSwitch.on to YES. If not, we set .on to NO.
We do the same for the other keys.
To simplify this we could get rid of the NSString and instead just nest it within the if statement condition as follows:
if ([[standardDefaults stringForKey:@"facebookKey"] isEqualToString:@"On"]) {
self.facebookSwitch.on = YES;
} else {
self.facebookSwitch.on = NO;
}
The full section of code for this part is as follows:
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
if ([[standardDefaults stringForKey:@"facebookKey"] isEqualToString:@"On"]) {
self.facebookSwitch.on = YES;
} else {
self.facebookSwitch.on = NO;
}
if ([[standardDefaults stringForKey:@"twitterKey"] isEqualToString:@"On"]) {
self.twitterSwitch.on = YES;
} else {
self.twitterSwitch.on = NO;
}
if ([[standardDefaults stringForKey:@"instagramKey"] isEqualToString:@"On"]) {
self.instagramSwitch.on = YES;
} else {
self.instagramSwitch.on = NO;
}
if ([[standardDefaults stringForKey:@"distanceSettingKey"] isEqualToString:@"Miles"]) {
self.distancesSwitch.selectedSegmentIndex = 1;
} else {
self.distancesSwitch.selectedSegmentIndex = 0;
}
if ([[standardDefaults stringForKey:@"temperatureSettingKey"] isEqualToString:@"C"]) {
self.temperatureSwitch.selectedSegmentIndex = 1;
} else {
self.temperatureSwitch.selectedSegmentIndex = 0;
}
To cut down on the lines of code used you could simplify the if/else statements like this:
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
self.facebookSwitch.on = ([[standardDefaults stringForKey:@"facebookKey"] isEqualToString:@"On"]) ? (YES) : (NO);
self.twitterSwitch.on = ([[standardDefaults stringForKey:@"twitterKey"] isEqualToString:@"On"]) ? (YES) : (NO);
self.instagramSwitch.on = ([[standardDefaults stringForKey:@"instagramKey"] isEqualToString:@"On"]) ? (YES) : (NO);
self.distancesSwitch.selectedSegmentIndex = ([[standardDefaults stringForKey:@"distanceSettingKey"] isEqualToString:@"Miles"]) ? (1) : (0);
self.temperatureSwitch.selectedSegmentIndex = ([[standardDefaults stringForKey:@"temperatureSettingKey"] isEqualToString:@"C"]) ? (1) : (0);
You can now go ahead and load the app. Click the settings button, make changes, close the app, open the app, click done etc… and the settings will always be retained.
How you use this depends on your app. An example could be a maps app where you calculate distances in Miles or KM. If you use KM then the part of your app that displays the distance could perform a check on the standardUserDefaults and adapt as needed automatically.
This concludes the main part of this tutorial. The sharedUserDefaults can be accessed from any view by simply using the class method on NSUserDefaults and storing it in to an NSUserDefaults object.
We could go further by retrieving the information on the main view and displaying “Facebook integration is ON etc…”. Go ahead and experiment with the code and also try storing other types of data in the NSUserDefaults. Feel free to ask any questions in the comments below.
Full Header Code of SettingsView:
#import
@interface SettingsView : UITableViewController
@property (weak, nonatomic) IBOutlet UISwitch *facebookSwitch;
@property (weak, nonatomic) IBOutlet UISwitch *twitterSwitch;
@property (weak, nonatomic) IBOutlet UISwitch *instagramSwitch;
@property (weak, nonatomic) IBOutlet UISegmentedControl *distancesSwitch;
@property (weak, nonatomic) IBOutlet UISegmentedControl *temperatureSwitch;
@end
Full Implementation Code of Settings View:
#import "SettingsView.h"
@interface SettingsView ()
@end
@implementation SettingsView
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
self.facebookSwitch.on = ([[standardDefaults stringForKey:@"facebookKey"] isEqualToString:@"On"]) ? (YES) : (NO);
self.twitterSwitch.on = ([[standardDefaults stringForKey:@"twitterKey"] isEqualToString:@"On"]) ? (YES) : (NO);
self.instagramSwitch.on = ([[standardDefaults stringForKey:@"instagramKey"] isEqualToString:@"On"]) ? (YES) : (NO);
self.distancesSwitch.selectedSegmentIndex = ([[standardDefaults stringForKey:@"distanceSettingKey"] isEqualToString:@"Miles"]) ? (1) : (0);
self.temperatureSwitch.selectedSegmentIndex = ([[standardDefaults stringForKey:@"temperatureSettingKey"] isEqualToString:@"C"]) ? (1) : (0);
}
- (IBAction)shareSettingsSwitched:(UISwitch *)sender {
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
if (sender.tag == 0) {
if (sender.on == 0) {
[standardDefaults setObject:@"Off" forKey:@"facebookKey"];
} else if (sender.on == 1) {
[standardDefaults setObject:@"On" forKey:@"facebookKey"];
}
} else if (sender.tag == 1) {
if (sender.on == 0) {
[standardDefaults setObject:@"Off" forKey:@"twitterKey"];
} else if (sender.on == 1) {
[standardDefaults setObject:@"On" forKey:@"twitterKey"];
}
} else if (sender.tag == 2) {
if (sender.on == 0) {
[standardDefaults setObject:@"Off" forKey:@"instagramKey"];
} else if (sender.on == 1) {
[standardDefaults setObject:@"On" forKey:@"instagramKey"];
}
}
[standardDefaults synchronize];
}
- (IBAction)distanceSetting:(UISegmentedControl *)sender {
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
if (sender.selectedSegmentIndex == 0) {
[standardDefaults setObject:@"KM" forKey:@"distanceSettingKey"];
} else if (sender.selectedSegmentIndex == 1) {
[standardDefaults setObject:@"Miles" forKey:@"distanceSettingKey"];
}
[standardDefaults synchronize];
}
- (IBAction)temperatureSetting:(UISegmentedControl *)sender {
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
if (sender.selectedSegmentIndex == 0) {
[standardDefaults setObject:@"F" forKey:@"temperatureSettingKey"];
} else if (sender.selectedSegmentIndex == 1) {
[standardDefaults setObject:@"C" forKey:@"temperatureSettingKey"];
}
[standardDefaults synchronize];
}
- (IBAction)donePressed:(UIButton *)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
PatriciaW43 says
I’m a novice xCode programmer and found this tutorial very helpful BUT my app crashes when I get to:
[standardDefaults synchronize]
In addition when I set breakpoints the only one that gets executed is for tag 0.
Can you suggest what I am doing wrong?
Matthew says
Could you post some code so I can take a closer look?
deanwang says
would you please to send your source code to me? so that I can complier it and study it. thanks for your nice support in advance. my mail: [email protected]. Thanks!