smarty_pants
smarty_pants

Reputation: 89

Persistent data in Swift

I am currently experimenting with persistent data in swift, but I am having trouble saving this data and retrieving it back again. Basically I have two text fields, and when the user presses the submit button then the entry will be saved to a UITable, here the user will be able to move the entries in the table around or delete them if they so wish. My main problem is saving and loading this data.

Taskmanager.swift -- Here I have my basic types stored

import Foundation

import UIKit

var taskMgr: TaskManager = TaskManager()

struct task {
    var name = "Name"
    var year = "Year"
}


//setting data
let defaults = UserDefaults.standard
//defaults.synchronize()


//getting data

class TaskManager: NSObject {
    var tasks = [task]()

    func addTask(name: String, year: String){
        tasks.append(task(name: name, year: year))
    }
}

ThirdViewController.swift -- Here I have stored my table and its functions, I also have a rough sketch of save and load data functions.

import Foundation

import UIKit

class ThirdViewController:UIViewController, UITableViewDelegate, UITableViewDataSource {

    @IBOutlet var tableView: UITableView!

    @IBAction func deleteT(_ sender: Any) {

        if(tableView.isEditing == true){
        tableView.setEditing(false, animated: true)
        }else{
        tableView.setEditing(true, animated: true)
        }


    }

    func saveData() {

        let data = NSMutableData()

        let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
        let path = paths[0]
        let file = (path as NSString).appendingPathComponent("Persistent.plist")

        //2
        let archiver = NSKeyedArchiver(forWritingWith: data)
        archiver.encode(G, forKey: "name")
        archiver.endode(year, forKey: "year")
        archiver.finishEncoding()
        data.write(toFile: file, atomically: true)
    }


    func loadData() {
        let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
        let path = paths[0]
        let file = (path as NSString).appendingPathComponent("Persistent.plist")

        // 1
        if FileManager.default.fileExists(atPath: file) {
            if let data = NSData(contentsOfFile: file) {
                let unarchiver = NSKeyedUnarchiver(forReadingWith: data as Data)
                name = unarchiver.decodeObjectForKey("name") as! [String]
                 year = unarchiver.decodeObjectForKey("year") as! [String]


                unarchiver.finishDecoding()
            }
        }
    }





     func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
        return true
    }

     func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
        return false
    }

     func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {


    }

    func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        return true
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.reloadData()
        loadData()
    }

    override func viewWillAppear(_ animated: Bool) {
        self.tableView.reloadData()
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
        return taskMgr.tasks.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{

        let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier: "TableView")

        //Assign the contents of our var "items" to the textLabel of each cell
        cell.textLabel!.text = taskMgr.tasks[indexPath.row].name
        cell.detailTextLabel!.text = taskMgr.tasks[indexPath.row].year
        //cell.editing = tableView(tableView, canMoveRowAtIndexPath: indexPath)

        return cell

    }

    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath){
        if (editingStyle == UITableViewCellEditingStyle.delete){

            taskMgr.tasks.remove(at: indexPath.row)
            tableView.reloadData()
        }
    }
}

FourthViewController.swift -- Here I have my textfields and buttons and how I am adding my entries to the table.

import Foundation

import UIKit

class FourthViewController: UIViewController, UITextFieldDelegate{

    @IBOutlet var addT: UITextField!
    @IBOutlet var addY: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

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


    @IBAction func confTask(_ sender:UIButton){
         if (addT.text == ""){
         }else{
        //add record
        let name: String = addT.text!
        let Year: String = addY.text!
        //taskMgr.addTask(name:name)
        taskMgr.addTask(name:name, year:Year)

        }

        //dismiss keyboard and reset fields

        self.view.endEditing(true)
        addT.text = nil
        addY.text = nil

    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.view.endEditing(true)
    }

    func textFieldShouldReturn(_ textField: UITextField) -> Bool{
        textField.resignFirstResponder()
        return true
    }





}

Upvotes: 4

Views: 1790

Answers (1)

dirtydanee
dirtydanee

Reputation: 6151

I have created some sample code using NSUserDefaults for persisting the tasks. It is a fairly simple example, as long as you are just experimenting, and only want to have less than a 100 elements, it should be fine. Using the code below you should be able to display, remove and save tasks.

However, in the future, i would highly recommend you to read more into Core Data. There are many great tutorials out there, like this one.

I have created a Task object model, and TaskManager for reading, saving and removing tasks.

import Foundation

// Task Data Model
class Task: NSObject, NSCoding {
    let name: String
    let year: String

    required init(name: String, year: String) {
        self.name = name
        self.year = year
    }

    required init(coder decoder: NSCoder) {
        self.name = decoder.decodeObject(forKey: "name") as? String ?? ""
        self.year = decoder.decodeObject(forKey: "year") as? String ?? ""
    }

    func encode(with coder: NSCoder) {
        coder.encode(name, forKey: "name")
        coder.encode(year, forKey: "year")
    }
}

class TaskManager {
    ///  UserDefaults instance
    private let defaults = UserDefaults.standard

    /// Singleton instance, class **should** be accessed by this property
    static let shared = TaskManager()

    /// Indetifier of tasks container in `defaults` 
    private let kTasksIdentifier = "tasks"

    /// Add a new task to your container and syncronize it into `defaults`
    ///
    /// - Parameters:
    ///   - name: Name of the task
    ///   - year: Year of the task
    func save(taskName name: String, year: String) {
        let task = Task(name: name, year: year)

        // Check if there is already saved tasks
        guard let data = defaults.value(forKey: kTasksIdentifier) as? Data, var tasks = NSKeyedUnarchiver.unarchiveObject(with: data) as? [Task] else {
            // If not, save it as the first one
            syncronizeTasks(tasks: [task])
            return
        }

        tasks.append(task)
        syncronizeTasks(tasks: tasks)
    }

    /// Remove a task at an index
    ///
    /// - Parameters:
    ///   - index: The index of the removeable task
    func remove(at index: Int) {

        guard let data = defaults.value(forKey: kTasksIdentifier) as? Data, var tasks = NSKeyedUnarchiver.unarchiveObject(with: data) as? [Task] else {
            fatalError("Unable to retrive tasks from defaults")
        }

        tasks.remove(at: index)
        syncronizeTasks(tasks: tasks)        
    }

    /// Read all tasks elements
    /// If there are tasks in memory, it returns the one from memory
    /// Otherwise reads it from `UserDefaults`
    ///
    /// - Returns: all tasks elements available, return empty array if no elements found
    func readAllTasks() -> [Task] {
        let data = UserDefaults.standard.value(forKey: kTasksIdentifier)
        let allTasks = NSKeyedUnarchiver.unarchiveObject(with: data as! Data) 
        return allTasks as? [Task] ?? [Task]()
    }


    private func syncronizeTasks(tasks: [Task]) {
        let data = NSKeyedArchiver.archivedData(withRootObject: tasks)
        defaults.set(data, forKey: kTasksIdentifier)
        defaults.synchronize()
    }
}

I have modified your already existing ThirdViewController a bit.

import UIKit
import Foundation

class ThirdViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    @IBOutlet var tableView: UITableView!
    /// Your tasks being updated in this collection every time `refreshTasks()` is being called
    private var tasks = [Task]()

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        self.refreshTasks()
        self.tableView.reloadData()
    }

    func refreshTasks() {
        self.tasks = TaskManager.shared.readAllTasks()
    }

    @IBAction func deleteT(_ sender: Any) {
        if(tableView.isEditing == true) {
            tableView.setEditing(false, animated: true)
        } else {
            tableView.setEditing(true, animated: true)
        }
    }

    func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
        return true
    }

    func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
        return false
    }

    func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        return true
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
        return tasks.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{

        let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier: "TableView")

        //Assign the contents of our var "items" to the textLabel of each cell
        cell.textLabel!.text = tasks[indexPath.row].name
        cell.detailTextLabel!.text = tasks[indexPath.row].year
        //cell.editing = tableView(tableView, canMoveRowAtIndexPath: indexPath)

        return cell

    }

    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath){
        if (editingStyle == UITableViewCellEditingStyle.delete) {
            self.tableView.beginUpdates()
            TaskManager.shared.remove(at: indexPath.row)
            refreshTasks()
            self.tableView.deleteRows(at: [indexPath], with: .fade)
            self.tableView.endUpdates()
        }
    }
}

And, just in case, edited your FourthViewController too

import Foundation
import UIKit

class FourthViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet var addT: UITextField!
    @IBOutlet var addY: UITextField!

    /// User has pressed `Submit` button 
    ///
    /// - Parameter sender: the pressed button
    @IBAction func confTask(_ sender: UIButton) {
        // Check if textfields are containing text
        guard let nameText = addT.text, let yearText = addY.text, !nameText.isEmpty, !yearText.isEmpty else {
            print("at least one of the textFields is not filled")
            return
        }

        // Save the tasks 
        TaskManager.shared.save(taskName: nameText, year: yearText)

        //dismiss keyboard and reset fields
        self.view.endEditing(true)
        addT.text = nil
        addY.text = nil
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.view.endEditing(true)
    }

    func textFieldShouldReturn(_ textField: UITextField) -> Bool{
        textField.resignFirstResponder()
        return true
    }
}

Upvotes: 2

Related Questions