บันทึกให้ด้วยสิ
ถ้าเราไม่ทำให้บันทึกค่าเก็บไว้ปกติ ถ้าเรา kill แอพทิ้งแล้วรันใหม่ข้อมูลเราจะหายไป
ขั้นแรกเราต้องทำให้สามารถ encode และ decode คลาส Todo ของเราให้ได้ก่อน เพราะเราจะได้แปลงเป็น data ที่จะบันทึกได้ต่อไป
วิธีการก็แสนจะง่าย ให้เราเพิ่ม conform Codable ที่ Todo และ TodoItem เป็นอันเสร็จ
import Foundation
class Todo: Codable {
private var items = [TodoItem]()
var totalItems: Int {
return items.count
}
func item(at index: Int) -> TodoItem {
return items[index]
}
func add(item: TodoItem) {
items.insert(item, at: 0)
}
func remove(at index: Int) {
items.remove(at: index)
}
func index(of item: TodoItem) -> Int? {
return items.index { $0 === item }
}
}
import Foundation
class TodoItem: Codable {
var title: String
var isDone: Bool
init(title: String, isDone: Bool = false) {
self.title = title
self.isDone = isDone
}
func toggleIsDone() {
self.isDone = !self.isDone
}
}
บันทึกลงไฟล์
เราใช้ FileManager ในการสร้าง url ไปยังไฟล์ที่เรากำหนด
ใช้ PropertyListEncoder ในการ encode ข้อมูลของเรา
func saveTodo() {
do {
let encoder = PropertyListEncoder()
encoder.outputFormat = .xml
let data = try encoder.encode(todo)
let fileManager = FileManager.default
var destinationURL = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
destinationURL.appendPathComponent("todo")
destinationURL.appendPathExtension("plist")
try data.write(to: destinationURL)
} catch {
print("Cannot save todo to file, Error: \(error)")
}
}
เสร็จแล้วให้เพิ่มเรียก saveTodo() ที่ delegate, dataSource method ที่ใช้ในการ Add, Edit และ Delete TodoItem และ todoItemTableViewCellDidTapCheckboxButton
โดย default PropertyListEncoder จะ encode เป็น binary แต่เราจะใช้ xml (บรรทัดที่ 4) ก่อนเพื่อที่จะได้อ่านได้
เราสามารถ print(destinationURL.path) ออกมาดูได้ว่าไฟล์อยู่ที่ไหนและลองเปิดดูได้
อ่านจากไฟล์
เหมือนบันทึกลงไฟล์ แต่คราวนี้ใช้ PropertyListDecoder ในการ decode และถ้าไม่มีไฟล์ให้อ่านเราจะข้ามไป
TodoListViewController.swift
func loadTodo() {
do {
let fileManager = FileManager.default
var destinationURL = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
destinationURL.appendPathComponent("todo")
destinationURL.appendPathExtension("plist")
if fileManager.fileExists(atPath: destinationURL.path) {
let data = try Data(contentsOf: destinationURL)
let decoder = PropertyListDecoder()
todo = try decoder.decode(Todo.self, from: data)
tableView?.reloadData()
}
} catch {
print("Cannot load todo from file, Error: \(error)")
}
}
เพิ่มโค้ดให้อ่านไฟล์ตอนโหลด
TodoListViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
loadTodo()
}
จากนั้นลองรันแอพ เพิ่มรายการ kill แอพ แล้วเปิดดูใหม่
Refactor Code หน่อยยย
จะเห็นว่าโค้ดที่ใช้หา Path ของไฟล์ที่กำหนดมันใช้ซ้ำกัน เราก็สร้างฟังก์ชั่นใหม่มาครอบดีกว่า
TodoListViewController.swift
func loadTodo() {
do {
let fileManager = FileManager.default
let destinationURL = try makeTodoFileURL(fileManager: fileManager)
if fileManager.fileExists(atPath: destinationURL.path) {
let data = try Data(contentsOf: destinationURL)
let decoder = PropertyListDecoder()
todo = try decoder.decode(Todo.self, from: data)
tableView?.reloadData()
}
} catch {
print("Cannot load todo from file, Error: \(error)")
}
}
func saveTodo() {
do {
let destinationURL = try makeTodoFileURL(fileManager: FileManager.default)
let encoder = PropertyListEncoder()
let data = try encoder.encode(todo)
try data.write(to: destinationURL)
} catch {
print("Cannot save todo to file, Error: \(error)")
}
}
func makeTodoFileURL(fileManager: FileManager) throws -> URL {
var destinationURL = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
destinationURL.appendPathComponent("todo")
destinationURL.appendPathExtension("plist")
return destinationURL
}