# Save data

## บันทึกให้ด้วยสิ

ถ้าเราไม่ทำให้บันทึกค่าเก็บไว้ปกติ ถ้าเรา kill แอพทิ้งแล้วรันใหม่ข้อมูลเราจะหายไป

ขั้นแรกเราต้องทำให้สามารถ encode และ decode คลาส Todo ของเราให้ได้ก่อน เพราะเราจะได้แปลงเป็น data ที่จะบันทึกได้ต่อไป

วิธีการก็แสนจะง่าย ให้เราเพิ่ม conform Codable ที่ Todo และ TodoItem เป็นอันเสร็จ

{% code title="Todo.swift" %}

```swift
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 }
    }
}
```

{% endcode %}

{% code title="TodoItem.swift" %}

```swift
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
    }
}
```

{% endcode %}

## บันทึกลงไฟล์

เราใช้ **FileManager** ในการสร้าง url ไปยังไฟล์ที่เรากำหนด\
ใช้ **PropertyListEncoder** ในการ encode ข้อมูลของเรา

{% code title="TodoListViewController" %}

```swift
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)")
    }
}
```

{% endcode %}

เสร็จแล้วให้เพิ่มเรียก saveTodo() ที่ delegate, dataSource method ที่ใช้ในการ Add, Edit และ Delete TodoItem และ todoItemTableViewCellDidTapCheckboxButton

{% hint style="info" %}
โดย default **PropertyListEncoder** จะ encode เป็น binary แต่เราจะใช้ xml (**บรรทัดที่ 4**) ก่อนเพื่อที่จะได้อ่านได้
{% endhint %}

{% hint style="info" %}
เราสามารถ print(destinationURL.path) ออกมาดูได้ว่าไฟล์อยู่ที่ไหนและลองเปิดดูได้
{% endhint %}

## อ่านจากไฟล์

เหมือนบันทึกลงไฟล์ แต่คราวนี้ใช้ **PropertyListDecoder** ในการ decode และถ้าไม่มีไฟล์ให้อ่านเราจะข้ามไป

{% code title="TodoListViewController.swift" %}

```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)")
    }
}
```

{% endcode %}

เพิ่มโค้ดให้อ่านไฟล์ตอนโหลด

{% code title="TodoListViewController.swift" %}

```swift
override func viewDidLoad() {
    super.viewDidLoad()
    loadTodo()
}
```

{% endcode %}

จากนั้นลองรันแอพ เพิ่มรายการ kill แอพ แล้วเปิดดูใหม่

![](https://1168329629-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LGk7Utff0rVWsqK4eYO%2F-LHMwK5plNbwcxEeYvps%2F-LHNJ5maz-ZqvmkHvo1y%2FsaveData.gif?alt=media\&token=2e51e10d-0218-4dc5-b059-0c2a22efd198)

{% hint style="info" %}
รายละเอียดเพิ่มเติมเกี่ยวกับเรื่องนี้ได้ที่\
[Archives and Serialization](https://developer.apple.com/documentation/foundation/archives_and_serialization)
{% endhint %}

## Refactor Code หน่อยยย

จะเห็นว่าโค้ดที่ใช้หา Path ของไฟล์ที่กำหนดมันใช้ซ้ำกัน เราก็สร้างฟังก์ชั่นใหม่มาครอบดีกว่า

{% code title="TodoListViewController.swift" %}

```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
}
```

{% endcode %}
