Make App Pie

Training for Developers and Artists

How to Use NSUserDefaults in Swift.

NSUserDefaults Getters and setters

While Property Lists can be ways of storing data, there is a better way of handling small amounts of data for preferences and settings that might change over time and user. While built on a property list, NSUSerDefaults Makes for a more convenient way for such data. IN this lesson, we’ll introduce NSUserDefaults and a few more classes which will be helpful is storing several types of data we weren’t transferring with a basic property list.

Make a New Project

We’ll write an app that changes the background color using sliders. Our app will save our settings for color, a selected segment and the date we saved the configuration. Open Xcode and make a new project named SwiftUserDefaultsDemo, using a single view template and Swift as the language. Open the storyboard and roughly set it up like this:

2016-02-24_05-57-11

The background is Light Gray(#AAAAAA). For the segmented control I added two more segments, and I set the tint to Black(#000000). I added the three sliders and used a Red(#FF0000), Green(#00FF00), and Blue(#0000FF) thumb to indicate their function.
Just to keep this clean, I’ll use stack views. You can skip this if you want a messier app. If you are not familiar with the beauty that is stack views, you can check out this tutorial. Start by selecting the Dark Text Label and the switch. Click the stack view button at the bottom of the storyboard.

stackButton

We will use the default values of horizontal stack view, with Fill for Alignment and Distribution. Select the switch. Go to the size inspector and change the Content Huggingfor both Horizontal and Vertical to Required(1000)

2016-02-24_06-10-27

Marquee-select all the controls on the storyboard. Click the stack view button again. In the attributes inspector, set the stack view attributes to this:

2016-02-24_06-19-01

Pin the stack view using the auto layout pin button pinMenuButton 10 top, 10 left and 10 right. Select the Update Frames with Items of new constraints at the bottom of the popup then click Add 3 constraints.

Finally add a button on the bottom of the storyboard. Make the background of the button a Dark Gray(#555555) and the text White(#FFFFFF). Set the text to Save. Using auto layout, Pin the button 10 left, 10 right and 20 down. Set the Height to 60 points. Select the Update Frames with Items of new constraints at the bottom of the popup then click Add 4 Constraints. Your final layout should look like this:

2016-02-24_06-56-40

Set Up Outlets and Actions

Go to the ViewController.swift file. Add the following properties, outlets and actions:

//MARK: Outlets and properties
    
    var dateUsed = NSDate()
    @IBOutlet weak var dateLabel: UILabel!
    @IBOutlet weak var segmentedControl: UISegmentedControl!
    @IBOutlet weak var redSlider: UISlider!
    @IBOutlet weak var greenSlider: UISlider!
    @IBOutlet weak var blueSlider: UISlider!
    @IBOutlet weak var textColorSwitch: UISwitch!
    @IBOutlet weak var rgbLabel: UILabel!
    @IBOutlet weak var darkTextLabel: UILabel!
    
    //MARK: - Actions
    @IBAction func textColorSwitch(sender: UISwitch) {
    }
    
    @IBAction func colorSliderChange(sender: UISlider) {
    }
    
    @IBAction func saveButton(sender: UIButton) {
    }

Go to the storyboard and open up the assistant editor. You can close the navigator and the attributes inspector to give yourself more room. From the circle next to each outlet, drag to the appropriate control on the storyboard. Do the same for the textColorSwitch and the saveButton actions. For the colorSliderChange action, drag three times, once to each of the three sliders. We’ll control all three sliders with the same code.

Code the Demo App

Close the assistant editor. Open the navigator and head over to the ViewController.swift file. Code the textColorSwitch and the colorSliderChange like this:

@IBAction func textColorSwitch(sender: UISwitch) {
        updateColor(background: true)
    }
    
    @IBAction func colorSliderChange(sender: UISlider) {
        updateColor(background: true)
    }

Next we’ll code this new function updateColor. Add the following under the actions:

//MARK: - Instance Methods
func updateColor( background background:Bool){
    //update background color
    if background{
    view.backgroundColor = UIColor(
        red: CGFloat(redSlider.value),
        green: CGFloat(greenSlider.value),
        blue: CGFloat(blueSlider.value),
        alpha: 1.0)
    }
    //update the text color
    var textColor = UIColor()
    if textColorSwitch.on{
        textColor = UIColor.blackColor()
    } else {
        textColor = UIColor.whiteColor()
    }
    dateLabel.textColor = textColor
    darkTextLabel.textColor = textColor
    rgbLabel.textColor = textColor
    segmentedControl.tintColor = textColor
}

This is just a quick way of updating our background and text colors depending on the values for our controls. There’s a Bool parameter background for something we’ll test later. We now have enough code to check our switch and sliders. Build and run. You should get this:

2016-02-24_06-55-18

Move the sliders around, and the background color changes.

2016-02-24_06-55-41

Slide the sliders to the left to make a dark background. Change the text switch.

2016-02-24_06-56-05

Our sliders and switch work. We also should update the RGB label and date label. At the bottom of the updateColor action, add this:

//update label for RGB color
        rgbLabel.text = String(
            format: "RGB: %1.2f, %1.2f %1.2f",
            redSlider.value,
            greenSlider.value,
            blueSlider.value)

In viewDidLoad, add this:

override func viewDidLoad() {
        super.viewDidLoad()
        dateLabel.text = formattedDate(dateUsed)
    }

This adds another function, a date formatter to our code. Add this to your code:

func formattedDate(date:NSDate) -> String{
        let formatter = NSDateFormatter()
        formatter.timeStyle = .ShortStyle
        formatter.dateStyle = .MediumStyle
        return formatter.stringFromDate(date)
    }

This gives us a medium style date and a short style time with a default locale. If you are not familiar with date formatters, you might want to check out my post on them. We’re ready to build and run again. Move the sliders a bit and we get this:

2016-02-24_07-19-17

Saving Preferences with NSUserDefaults

Up to this point our app starts with the gray background. We’ll use NSUserDefaults to save our preferences for a background and other settings. NSUserDefaults is essentially a wrapper around a property list that has easier to use methods than a straight property list. We make a property list object, then use methods to save or retrieve the type of data we have.
Add this to our properties for the ViewController class:

let defaults = NSUserDefaults.standardUserDefaults()

This uses the class method standardUserDefaults. This does one of two things: Creates the standard defaults file and configures it if it does not exist, or opens it if it does. For most cases you’ll use this to make your defaults.
To add a default setting to the NSUserDefaults, we use a method for the type we are setting. In the saveButton action, add the following:

 @IBAction func saveButton(sender: UIButton) {
        defaults.setBool(textColorSwitch.on, forKey: "DarkText")
}

This adds the textColorSwitch‘s Bool value on to a key named DarkText. Note for demonstration purposes I’m using a string literal for the key. It’s a good practice to use string constants instead to prevent several bugs due to misspellings of keys.
In earlier versions of iOS, you would then use the synchronize method to save this. In most cases where you are coding in Swift, this is unnecessary. According to Apple, synchronize is ultimately counter-productive, which is why we won’t use it here. synchronize is now a scheduled event, and will happen eventually. You’ll therefore see the notable exception: If you are exiting an app and wanting to save your preferences before closing, you would need a synchronize.

We can also add entries for Int and Float. Change saveButton
to this:

@IBAction func saveButton(sender: UIButton) {
    defaults.setBool(textColorSwitch.on, forKey: "DarkText")
    defaults.setInteger(segmentedControl.selectedSegmentIndex, forKey: "SegmentIndex")
    defaults.setFloat(redSlider.value, forKey: "Red")
    defaults.setFloat(greenSlider.value, forKey: "Green")
    defaults.setFloat(blueSlider.value, forKey: "Blue")

We’ve added the positions of the sliders and the segment index of our segmented control. Except for the name of the method, there’s little difference in their use from setBool. This is why NSUserDefaults is so much better for defaults than setting up your own property list. Much of the hard work is done for you. There are several methods for setting and getting data into NSUserDefaults. Here’s a table of them:

NSUserDefaults Getters and setters

The setObject method and NSDate

Most of the simple data types we already talked about such as Bool and Float are on the chart above. A few types such as String and NSDate don’t have their own setter, but use setObject. These types are that same types we encountered in property lists. I’ll refer to them as property list objects. The property list objects NSData, NSDate, NSNumber, String, arrays, and dictionaries all go into user defaults using setObject.

In our app, we will use a date example. Add another line to our setters:

defaults.setObject(NSDate(), forKey: "Date")

While the current app grabs the date and time we started the app, this will remember the last time we saved the app.

Saving Colors and Other Objects with NSData.

We’ve already stored enough information to make a color with the float values we stored. This may get cumbersome with more than one color. It makes more sense to store the color directly. Like UIColor, we might want to store objects in our user preferences that are not property list objects. The trick here is the class NSData. You can convert most objects to NSData. We’ll convert the background color to NSData and then store it in the preferences. Add this to our saveButton action:

//Storing the color and any other non-data object
let color = view.backgroundColor!
let colorData = NSKeyedArchiver.archivedDataWithRootObject(color)
defaults.setObject(colorData, forKey: "Color")

The first line gets our color form the view’s background. The second line uses a class method of NSKeyedArchiver to archive the object. What’s going on under the hood is beyond the scope of this tutorial, but essentially it makes an easily readable binary object. This is an NSData object, which we can use setObject to make it a user preference.

A word of caution about images. You can encode images as NSData, but don’t do it. They take up a lot of space and take a long time to encode and decode. A better approach for images is to store the image with a URL and then store the image’s URL in the user preferences with the setURL:forKey: method.

This is all we need to save our settings for this app.

Getting the User Defaults

Of course saving preferences is not enough, we have to read them back as well. Make a new function readDefaults:

func readDefaults(){
}

Let’s set the switch for text color first. Add the following code:

textColorSwitch.on = defaults.boolForKey("DarkText")

We’re reading a property list, and the property list is set up as a dictionary. We use a key DarkText to get a Bool value. All these getters are immutable. It makes little sense to keep them around as variables. We assign that value directly to the switch’s on property. We can do the same for the segmented control. Add this line:

segmentedControl.selectedSegmentIndex = defaults.integerForKey("SegmentIndex")

This does the same as the previous line using integerForKey for the SegmentIndex key to get our selected segment index. We can set the sliders using the floatForkey method. Add this:

redSlider.value = defaults.floatForKey("Red")
greenSlider.value = defaults.floatForKey("Green")
blueSlider.value = defaults.floatForKey("Blue")

If you look at the chart above, you’ll notice a note about what happens for these methods when the key is not found. It returns a specific value of false for the Bool and 0 for the numbers. While in our app that is not tragic, it could be a problem in other apps. You will need some more code or some debugging to determine if it a value of 0 or false or it is a missing key. Later on we’ll see what effect this has on our app.

That is not a problem with objects using setObject. Its counterparts including objectForKey return an optional value, where nil is a not found object. Some people don’t use the simpler values mentioned about and use setObject for numbers and Bool values just to have the optional value. To use setObject is slightly different to detect the nil. We’ve got a NSDate object in our prefrences as an example. We read it like this:

if let dateObject = defaults.objectForKey("Date") {
    let date = dateObject as! NSDate
    dateLabel.text = "Previous save: " + formattedDate(date)
}

We optionally chain the defaults.objectForKey("Date") to check if it exists. If it does, the code downcasts the returned result to a date. Once we have a date we change the date label. If not, we leave the default setting of the label we set in viewDidLoad.

For our color object, things get a bit more complicated since there is no default like the date. We’ll need an else to control for that situation. Add this to our code:

if let colorObject = defaults.objectForKey("Color") {
     let colorData = colorObject as! NSData
     let color = NSKeyedUnarchiver.unarchiveObjectWithData(colorData) as? UIColor
     view.backgroundColor = color
     updateColor(background: false)
} else{
    updateColor(background: true)
}

I broke the steps out here. Like the NSDate, we optionally chain to find the key. Once found, we downcast the value of colorObject to NSData. That’s the same as the date getter in structure. The next step is different though. The code converts the NSData object back to a color. With that color, we update the background color. Since we don’t need the slider values to update the color for us, we call updateColor to not update the background with a false parameter.

Our finished function looks like this

func readDefaults(){
    textColorSwitch.on = defaults.boolForKey("DarkText")
    segmentedControl.selectedSegmentIndex = defaults.integerForKey("SegmentIndex")
    redSlider.value = defaults.floatForKey("Red")
    greenSlider.value = defaults.floatForKey("Green")
    blueSlider.value = defaults.floatForKey("Blue")
        
    if let dateObject = defaults.objectForKey("Date") {
        let date = dateObject as! NSDate
        dateLabel.text = "Previous save: " + formattedDate(date)
    }
        
    if let colorObject = defaults.objectForKey("Color") {
        let colorData = colorObject as! NSData
        let color = NSKeyedUnarchiver.unarchiveObjectWithData(colorData) as? UIColor
        view.backgroundColor = color
        updateColor(background: false)
    } else{
        updateColor(background: true)
    }
}

Add the function to viewDidload like this

override func viewDidLoad() {
    super.viewDidLoad()
    dateLabel.text = formattedDate(dateUsed)
    readDefaults()
}

Build and run. We don’t get the gray background with black lettering:

2016-02-26_07-38-05

Since the property list for NSUserDefaults hasn’t been created yet, the values for the keys were not found. Our UIColor wasn’t there and attempted to set the color from the three float values of the sliders. They weren’t there, and thus were 0.0,0.0,0.0, which is black. The dark text switch was false, and the index of our segments was 0, again because values were not found.

Change the color to orange by sliding the red all the way to the left and the green to 3/4 left. Click the Fourth segment in the segmented control and change the text to dark text.

2016-02-26_07-46-08

Click the Save Button. Double-Click the home button. If you are using the simulator that is Command-Shift-HH.

2016-02-26_07-49-31

Kill the app by dragging the app up. Then click the icon to start the app again. We get an orange screen and all of our other settings change the app, including the previous save date at the top

2016-02-26_07-53-56

Dealing With the Missing Keys

That first use of the app will run into some problems due to no values for the preferences. You might want to detect that case and set some of the preferences yourself. For example, we can change this in readDefaults:

 } else{
        updateColor(background: true)
}

to this:

} else{
            if blueSlider.value == 0 &&
               redSlider.value == 0 &&
                greenSlider.value == 0
            { // very probable no data in defaults
                blueSlider.value = 0.8
                greenSlider.value = 0.8
                redSlider.value = 0.8
                textColorSwitch.on = true
            }
            updateColor(background: true)
        }

We make an assumption if we don’t find the UIColor and the sliders are all zero, there is no preferences set, and so we set them programmatically to light gray with dark text. In the simulator click on the icon for the app, and delete it. Deleting the app deletes our preferences, as the alert tells you. Build and run the app again. This time we get a gray background.

2016-02-26_08-08-30

In most apps, It’s a good idea to have handlers available for missing data. There are three possibilities: there is missing data, the key is wrong, or the data type is wrong. For all three, you need to take some action.

Beyond the Settings View

Our app works for a single page. It’s more likely those defaults are for several pages. Close the app and in Xcode go to the storyboard. Click on the view controller and in the menu, select Editor>embed in>Navigation Controller. We get an navigation controller and a navigation bar in the view controller obscuring our stack view and a warning error. Click the resolver button resolver buttonAt the bottom of the storyboard and select Update All frames. The stack view will move to the correct place.
Add a bar button item to the right side of the navigation bar. Title the button Next. Drag a view controller to the story board. Connect the Next bar button to the new view controller by control dragging from the button to the view controller. Select a Show segue from the menu. You’ll end up with a storyboard that looks like this:

2016-02-26_08-38-19

Press Command-N to bring up the new file dialog box. Make a new Cocoa Touch Class named TwoViewController subclassing UIViewController with Swift as the language. In the code that appears for TwoViewController, Chge viewDidLoad to this:

override func viewDidLoad() {
    let defaults = NSUserDefaults.standardUserDefaults()
    super.viewDidLoad()
    if let colorObject = defaults.objectForKey("Color") {
        let colorData = colorObject as! NSData
        let color = NSKeyedUnarchiver.unarchiveObjectWithData(colorData) as? UIColor
        view.backgroundColor = color
    } else{
        view.backgroundColor = UIColor.lightGrayColor()
    }
}

We’ll set the background color from the defaults. If we don’t have a color, then we’ll set the background color to light gray.
Connect up this code by going back to the storyboard. Click our new view controller on the storyboard. In the identity inspector, set the view controller class to TwoViewController. Build and run. If you havent hit Save on the app since the last run you should have the light gray settings screen. Click Next and you get another light gray settings screen, since we dont have a key yet.

2016-02-26_08-53-59

Click back and change the color of the background with the sliders.

2016-02-26_08-54-34

Tap the Save button, then tap the next button.

2016-02-26_08-54-59

Now we have our background read from the defaults.

A Few Last Thoughts on NSUserDefaults.

There’s a lot more one could cover on NSUserDefaults. I need to add a few more suggestions about the topic. The first and most important is that NSUserDefaults and any property list is not secure. Don’t store sensitive information like passwords, financial data or health data here. It’s in XML. With not much work and access to a phone, it’s easily read.
Secondly, with NSData you might want to run more checks on the data than I did in our example. I didn’t check here to make sure colorData is a UIColor, which is something you might want to do.
With objects like UIColor, you may be adding a lot of code to a lot of view controllers to archive and unarchive them. It’s a good idea to extend NSUserDeafaults instead and make a function call to the extention in your view controllers.
Finally, I used a save method and required the user to press Save to save the defaults. There are times you may not want that, there are times you do. You could automatically save by placing the saving method call in viewWillDisappear or even in the actions for the controls themselves (thought that might be overkill). In an automatic save situation, the save will reflect on all other view controllers as they read the new defaults. Be careful. There are many cases both with automatic saves and manual saves the view controllers besides of settings won’t know they need to make changes. In our app you can change the color and hit Next and it will show the last color saved, not the color selected. A tab bar controller wont get the message on all its view controllers if the setting controller changes something. This is where notifications comes in. In our next persistence lesson, we’ll look at notifications while exploring a settings page outside your app in Apple’s Settings app.

The Whole Code

ViewController.swift

//
//  ViewController.swift
//  SwiftUserDefaultsDemo
//
//  Created by Steven Lipton on 2/24/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import UIKit

class ViewController: UIViewController {
    //MARK: Outlets and properties
    
    var dateUsed = NSDate()
    let defaults = NSUserDefaults.standardUserDefaults()
    
    @IBOutlet weak var dateLabel: UILabel!
    @IBOutlet weak var segmentedControl: UISegmentedControl!
    @IBOutlet weak var redSlider: UISlider!
    @IBOutlet weak var greenSlider: UISlider!
    @IBOutlet weak var blueSlider: UISlider!
    @IBOutlet weak var textColorSwitch: UISwitch!
    @IBOutlet weak var rgbLabel: UILabel!
    @IBOutlet weak var darkTextLabel: UILabel!
    
    
    //MARK: - Actions
    @IBAction func textColorSwitch(sender: UISwitch) {
        updateColor(background: true)
    }
    
    @IBAction func colorSliderChange(sender: UISlider) {
        updateColor(background: true)
    }
    
    @IBAction func saveButton(sender: UIButton) {
        //Storing simple types
        defaults.setBool(textColorSwitch.on, forKey: "DarkText")
        defaults.setInteger(segmentedControl.selectedSegmentIndex, forKey: "SegmentIndex")
        defaults.setFloat(redSlider.value, forKey: "Red")
        defaults.setFloat(greenSlider.value, forKey: "Green")
        defaults.setFloat(blueSlider.value, forKey: "Blue")
       
        
        //Storing a data object -- NSDate, Array, Dictionary, NSData, NSString, NSNumber
        defaults.setObject(NSDate(), forKey: "Date")
        
        //Storing the color and any other non-data object
        let color = view.backgroundColor!
        let colorData = NSKeyedArchiver.archivedDataWithRootObject(color)
        defaults.setObject(colorData, forKey: "Color")
    }
    //MARK: - Instance Methods
    func updateColor( background background:Bool){
        if background{
            view.backgroundColor = UIColor(
                red: CGFloat(redSlider.value),
                green: CGFloat(greenSlider.value),
                blue: CGFloat(blueSlider.value),
                alpha: 1.0)
        }
        var textColor = UIColor()
        if textColorSwitch.on{
            textColor = UIColor.blackColor()
        }else{
            textColor = UIColor.whiteColor()
        }
        dateLabel.textColor = textColor
        darkTextLabel.textColor = textColor
        rgbLabel.textColor = textColor
        segmentedControl.tintColor = textColor
        
        //update label for RGB color
        rgbLabel.text = String(
            format: "RGB: %1.2f, %1.2f %1.2f",
            redSlider.value,
            greenSlider.value,
            blueSlider.value)
        }
    func formattedDate(date:NSDate) -> String{
        let formatter = NSDateFormatter()
        formatter.timeStyle = .ShortStyle
        formatter.dateStyle = .MediumStyle
        return formatter.stringFromDate(date)
    }
    func readDefaults(){
        textColorSwitch.on = defaults.boolForKey("DarkText")
        segmentedControl.selectedSegmentIndex = defaults.integerForKey("SegmentIndex")
        redSlider.value = defaults.floatForKey("Red")
        greenSlider.value = defaults.floatForKey("Green")
        blueSlider.value = defaults.floatForKey("Blue")
        
        if let dateObject = defaults.objectForKey("Date") {
            let date = dateObject as! NSDate
            dateLabel.text = "Previous save: " + formattedDate(date)
        }
        
        if let colorObject = defaults.objectForKey("Color") {
            let colorData = colorObject as! NSData
            let color = NSKeyedUnarchiver.unarchiveObjectWithData(colorData) as? UIColor
            view.backgroundColor = color
            updateColor(background: false)
        } else{
            if blueSlider.value == 0 &&
                redSlider.value == 0 &&
                greenSlider.value == 0
            { // very probable no data in prefrences
                blueSlider.value = 0.8
                greenSlider.value = 0.8
                redSlider.value = 0.8
                textColorSwitch.on = true
            }
            updateColor(background: true)
        }

        
    }


    override func viewDidLoad() {
        super.viewDidLoad()
        dateLabel.text = formattedDate(dateUsed)
        readDefaults()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


}


TwoViewController.swift

//
//  TwoViewController.swift
//  SwiftUserDefaultsDemo
//
//  Created by Steven Lipton on 2/26/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import UIKit

class TwoViewController: UIViewController {
    
    override func viewDidLoad() {
        let defaults = NSUserDefaults.standardUserDefaults()
        super.viewDidLoad()
        if let colorObject = defaults.objectForKey("Color") {
            let colorData = colorObject as! NSData
            let color = NSKeyedUnarchiver.unarchiveObjectWithData(colorData) as? UIColor
            view.backgroundColor = color
        } else{
           view.backgroundColor = UIColor.lightGrayColor()
        }

    }

}

5 responses to “How to Use NSUserDefaults in Swift.”

  1. […] I’ll assume you know how to use property lists and NSUserDefaults in this lesson. If you are not familiar with them you might want to check out my recent posts on Property Lists and NSUserDefaults […]

  2. 1. To avoid missing keys for Bool, Int, Float, Double and String, store their values as strings. When the app launches, a “if let = userDefaults.stringForKey()” on the key will check if the value is nil. If the key exists, convert its string value and process accordingly; else save the default value converted to a string using setObject. When the preference changes, convert its value to a string and save to userDefaults using setObject.

    2. If you use synchronize in xCode 7.2 and 7.3, the compiler will state it is deprecated. It is no longer needed.

    1. A better strategy for dealing with missing keys for primitive types is to utilize the registerDefaults method of NSUserDefaults to register default values that should be returned if the key does not exist.

      These values are not saved to disk and must be registered with each launch of the application.

      Apple’s Documentation of the method:
      https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSUserDefaults_Class/#//apple_ref/occ/instm/NSUserDefaults/registerDefaults:

  3. Steven, have you written anything about a pattern for updating a view when a user default is changed by a user? Example, user is on screen A which displays a weight in pounds. The user goes to a settings screen and changes the units to kilograms. When the settings view is closed, screen A should know the units are now kg, convert the weight and change the label to ‘kg’. I’m writing an app that does this, but the code smells. I have a variable that remembers what the units were before leaving screen A, then when returning, I check the current setting and if they’re different I perform the conversions, etc. It’s just a mess.

    If you don’t have anything written, could you point me in the right direction and I’ll go do the research?

    Thanks!

    1. Best way is to trigger something in the application delegate method applicationDidBecomeActive when the app goes active. Send that to the view controller that it needs to re-read the settings bundle. You can also look into key value notifications. I find them a bit messy though.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: