Reputation: 89
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
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