UserDefaults with NSCoding and Codable to save any kind of Data… Part – 1 ūüėé

¬†UserDefaults¬† is one of the good and handy storage you can use.¬†You can use¬†UserDefaults¬†to store any basic data type for as long as the app is installed. For example, you can use basic types as¬†Bool,¬†Float,¬†Double,¬†Int,¬†String, or¬†URL, but you can also write more complex types such as arrays, dictionaries and¬†Date¬†‚Äď and even¬†Data¬†values.

Advantage: After writing data to UserDefaults, when you run the app the UserDefaults data gets automatically loaded. Its very easy to use it.

Disadvantage:  Its a bad idea to use UserDefaults to save lot of data as it slows down your app loading and increases app load time.

You can use it making an instance of the UserDefaults like below:

let defaults = UserDefaults.standard

Now you can save your data like below:

defaults.set(26, forKey: "Age")

defaults.set(true, forKey: “is_iOS_Best”)

defaults.set(CGFloat.pi, forKey: “Pi”)

defaults.set(Date(), forKey: "CurrentDate")
let myArray = ["iOS", "macOS"]
defaults.set(myArray, forKey: "SavedArray")
let myDict = ["Name": "Sagar", "Hobby": "Coding"]
defaults.set(dict, forKey: "SavedDict")

You can get the stored values/data back using below methods:

  • integer(forKey:)¬†returns an integer if the key existed, or 0 if not.
  • bool(forKey:)¬†returns a boolean if the key existed, or false if not.
  • float(forKey:)¬†returns a float if the key existed, or 0.0 if not.
  • double(forKey:)¬†returns a double if the key existed, or 0.0 if not.
  • object(forKey:)¬†returns¬†Any?¬†so you need to conditionally typecast it to your data type.You can save a complex kind of data too in UserDefaults using NSCoding and Codable protocols.
    NSCoding is available for both Objective-C and Swift developers but Codable is only for Swift developers.We will see the usage of first one : NSCoding


     NSCoding

    Your class must confirm to NSCoding before using it, like you use other protocols : UITableViewDataSource and UITableViewDataDelegate.

    Then you can implement it using below method which changes the object graph to Data, so that it becomes able to save in UserDefaults.

    archivedData(withRootObject:) method of NSKeyedArchiver

    There are many Apple’s own classes that support NSCoding like:¬†UIColor,¬†UIImage,¬†UIView,¬†UILabel,¬†UIImageView,¬†UITableView,¬†SKSpriteNode
    but your custom classes don’t support it by default.

    You can also refer HackingWithSwift blog for free and good coding tutorials.

    Let us have a look at below example for its use.

    For example:

    Consider your custom class is MySelf in which you are using name and image properties.


    import UIKit

    class MySelf: NSObject, NSCoding { //Here NSObject is required to use NSCoding.

         func encode(with aCoder: NSCoder)  {    // this method is required and used to encode data

             aCoder.encode(name, forKey: name)

             aCoder.encode(image, forKey: image)

         }

        required init?(coder aDecoder: NSCoder) {   // this method is required and used to decode data

             name = aDecoder.decodeObject(forKey: name) as! String

            image = aDecoder.decodeObject(forKey: image) as! String

       }


    var
    name: String

       var image: String


    init
    (name: String, image: String) {

             self.name = name

            self.image = image

       }

    }

     


    Now in your ViewController class you can use it like below


     

    import UIKit

    class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UICollectionViewDataSource {

        var people = [MySelf]()          // array of MySelf type data

        @IBOutlet weak var myCollectionView: UICollectionView!

        override func viewDidLoad() {

              super.viewDidLoad()

              let defaults = UserDefaults.standard

              if let savedPeople = defaults.object(forKey: people) as? Data {

                      // Get saved values using below method
    people
    = NSKeyedUnarchiver.unarchiveObject(with: savedPeople) as! [MySelf]
    }

          }

        @IBAction func barButton_addPersonTapped(_ sender: Any) {

               addPerson()    

        }

        func addPerson() {      // Show ImagePickerController

               let imagePicker = UIImagePickerController()

              imagePicker.allowsEditing = true

             imagePicker.delegate = self

             present(imagePicker, animated: true) {

                 //  completion block

              }

          }

        func getDocumentsDirectory() -> URL {   // // Get DocumentsDirectory

              let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)

              let documentsDirectory = paths[0]

              return documentsDirectory

        }

    // UICollectionViewController Delegates


    func
    collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

         return people.count

    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

          let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PersonCollectionViewCell, for: indexPath) as! PersonCollectionViewCell

         let aPerson = people[indexPath.item]

         cell.name.text = aPerson.name

         let path = getDocumentsDirectory().appendingPathComponent(aPerson.image)

         cell.imageView.image = UIImage(contentsOfFile: path.path)

         cell.imageView.layer.cornerRadius = 3

         cell.imageView.layer.borderWidth = 2

         cell.imageView.layer.borderColor = UIColor(displayP3Red: 0, green: 0, blue: 0, alpha: 0.3).cgColor

         cell.layer.cornerRadius = 7

        return cell

     }

    // UIImagePickerController Delegates

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {

         guard let image = info[UIImagePickerControllerEditedImage] as? UIImage else { return }

         let imageName = UUID().uuidString

         let imagePath = getDocumentsDirectory().appendingPathComponent(imageName)

         if let jpegData = UIImageJPEGRepresentation(image, 80) {

               try? jpegData.write(to: imagePath)  // Write your image jpeg data to imagePath

         }

         let aPerson = MySelf(name: Img, image: imageName)

         people.append(aPerson)

         myCollectionView.reloadData()

         dismiss(animated: true) {

               //

         }

         self.save()  // Save newly added Image with name.

      }


    func
    save() {  // Save image and name using archivedData method of  NSKeyedArchiver

            let saveData = NSKeyedArchiver.archivedData(withRootObject: people)

            let defaults = UserDefaults.standard

          defaults.set(saveData, forKey: people)

       }

    }


    Your custom collectionViewCell class: PersonCollectionViewCell 


     

    import UIKit

    class PersonCollectionViewCell: UICollectionViewCell {

           @IBOutlet weak var imageView: UIImageView!

           @IBOutlet weak var name: UILabel!

    }


    Have fun… ūüôā

    I will show the usage of Codable in my next post.

    Let me know if you have any queries or problems implementing this feature. Enjoy … ūüôā