over 2 years ago

1. 加上 Table View

開啟新專案 TodoExample 選 Tabbed Application,把 View 都設定好

請照著影片上的流程做

影片一

教學影片1 - 設定 View

2. Install Cocoapods & RealmSwift

Cocoapods 是幫我們管理 App 套件的程式,安裝好後就可以很方便的幫 App 加上各種 Plugin,在我們的 Todo App 中因為需要有可以存資料的地方,所以我們要安裝 RealmSwift 這個套件,安裝方式就是在 Cocoapods 產生的 Podfile 裡面加上 pod 'RealmSwift' 然後在 Command Line 打上 pod install 就可以了。

安裝 Cocoapods:

待補
CocoaPods

安裝好 Cocoapods 後要讓 Cocoapods 管理這個專案,首先打開 Command Line 進到目前的資料夾:

wayne@Waynes-MacBook-Pro ~/Downloads/TodoExample $
$ pod init
Podfile
# Uncomment this line to define a global platform for your project

# platform :ios, '8.0'

# Uncomment this line if you're using Swift

# use_frameworks!


target 'TodoExample' do
  use_frameworks!
  pod 'RealmSwift'
end
$ pod install

把 Xcode 關掉,改成開 TodoExample.xcworkspace 而不要開 TodoExample.xcodeproj,因為只有 workspace 的檔案會幫你載入你想要的 framework, 在此例就是幫你載入 Realm 的 framework.

3. 定義 Model

建立一個新的檔案叫做 Task.swift

Task.swift
import RealmSwift

class Task: Object {

    dynamic var name = ""
    dynamic var isCompleted = false

}

教學影片2 - 建立Task

4. 讓 Table View 顯示東西,並且可以新增 Task

記得剛剛我們有兩個 Tabs 嗎? 現在我們要加上一些功能

  1. 點擊新增的按鈕可以跳出新增 Task 的 Popup 頁面,並可以新增 Task
  2. 讓 Table 可以顯示所有未完成的 Tasks

(1) 點擊新增的按鈕可以跳出新增 Task 的 Popup 頁面,並可以新增 Task

首先我們先看第一個 Tab,也就是 Todo List 的 Tab, 這個 Tab 對應的檔案 Xcode 已經幫我們產生了,打開 FirstViewController.swift 會看到現在長這樣:

FirstViewController.swift
import UIKit

class FirstViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!
    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 addNewItem(sender: AnyObject) {
    }
}

記得我們在 Step1 新增 View 的時候有把新增按鈕拖動到這個檔案裡面嗎?就是下面這段:

FirstViewController.swift
@IBAction func addNewItem(sender: AnyObject) {
}

這段 Code 的意思就是每當我們按下新增按鈕的時候,執行這段 Code。
也就是說,裡面要執行這些動作:

  1. 跳出新增的視窗
  2. 可以輸入 Task 的 Name
  3. 可以新增 Task
  4. 可以 Cancel 取消新增

讓我們一行一行打上去變這樣:

首先因為我們要在這個 Controller 裡面使用到 Realm 的功能,所以要在這個檔案的開頭加上

import RealmSwift

然後在 FirstViewController class 裡面定義一個 Realm 的 Object 常數

let realm = try! Realm()

之後定義使用者按了新增的按鈕後應該會發生的動作:

@IBAction func addNewItem(sender: AnyObject) {
    // 定義 alertController, UIAlertController 就是之後會跳出來的 Popup View

    let alertController = UIAlertController(title: "Add Task", message: nil, preferredStyle: UIAlertControllerStyle.Alert)
    
    // 加上一個 Input 的 TextField 框框讓使用者輸入 Task Name

    alertController.addTextFieldWithConfigurationHandler({ (textField) -> Void in
        textField.placeholder = "type task name here.."
    })
    
    
    // 定義這個 Alert Popup 會有哪些動作,此例我們只需要 Cancel 和 Add

    let cancelAction = UIAlertAction(title: "Cancel", style: .Default, handler: { (action: UIAlertAction!) in
        // handler 裡面的 code 代表使用者按了這個 Action 以後要處理的事情。

        print("Canceled")
    })
    
    let addTaskAction = UIAlertAction(title: "Add", style: .Default, handler: { (action: UIAlertAction!) in
        let textField = alertController.textFields![0] as UITextField
        let taskName = textField.text! as String
        
        // 初始化 Task 的 Object

        let task = Task()
        task.name = taskName

        // 將 Task 寫進資料庫

        try! self.realm.write {
            self.realm.add(task)
        }
    })
    
    // 把剛剛定義好的 Action 塞到 Alert 的 Popup 裡面

    alertController.addAction(cancelAction)
    alertController.addAction(addTaskAction)
    
    // 將前面設定好的 Alert View 顯示出來

    presentViewController(alertController, animated: true, completion: nil)
}

整個檔案會變成這樣:

FirstViewController.swift
import UIKit
import RealmSwift

class FirstViewController: UIViewController {

    let realm = try! Realm()
    @IBOutlet weak var tableView: UITableView!
    
    

    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 addNewItem(sender: AnyObject) {
        // 定義 alertController, UIAlertController 就是之後會跳出來的 Popup View

        let alertController = UIAlertController(title: "Add Task", message: nil, preferredStyle: UIAlertControllerStyle.Alert)
        
        // 加上一個 Input 的 TextField 框框讓使用者輸入 Task Name

        alertController.addTextFieldWithConfigurationHandler({ (textField) -> Void in
            textField.placeholder = "type task name here.."
        })
        
        
        // 定義這個 Alert Popup 會有哪些動作,此例我們只需要 Cancel 和 Add

        let cancelAction = UIAlertAction(title: "Cancel", style: .Default, handler: { (action: UIAlertAction!) in
            // handler 裡面的 code 代表使用者按了這個 Action 以後要處理的事情。

            print("Canceled")
        })
        
        let addTaskAction = UIAlertAction(title: "Add", style: .Default, handler: { (action: UIAlertAction!) in
            let textField = alertController.textFields![0] as UITextField
            let taskName = textField.text! as String
            
            // 初始化 Task 的 Object

            let task = Task()
            task.name = taskName

            // 將 Task 寫進資料庫

            try! self.realm.write {
                self.realm.add(task)
            }
        })
        
        
        // 把剛剛定義好的 Action 塞到 Alert 的 Popup 裡面

        alertController.addAction(cancelAction)
        alertController.addAction(addTaskAction)
        
        // 將前面設定好的 Alert View 顯示出來

        presentViewController(alertController, animated: true, completion: nil)
    }
}

(2) 讓 Table 可以顯示所有未完成的 Tasks

請照著教學影片的做法將 Table 設定好

教學影片3 - 設定 Table

影片解釋:

我們使用了 UITableViewDelegateUITableViewDataSource 兩個 Protocal 讓這個 FristViewController 可以有操作 Table 的能力,在 Storyboard 按住 Control 拖拉 Table View 是為了讓 Story Board 中 Table 的 Object 可以跟我們寫在 Controller 中的東西連結起來,建立 Reference,若是少了這個步驟那模擬器就不會顯示任何資訊在 Table 上面。

另外由於 UITableViewDataSource 需要有以下兩個 function, 所以我在影片中快速 Demo 了一下這兩個 Function 的作用,如果想要查在這兩個 protocal 裡面還有哪些可以用的 function 可以按住 Command 然後點擊它,就如同影片中做的一樣,有空可以多看看。

// 告訴 Table View 我們的 Table 有幾格 (幾個 Cells)

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1 // 這個 Table 只有 1 個 cell

}

// 告訴 Table View 每個 Cell 的內容

tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        // 建立 Cell 的物件並設個 Text 測試看看是否成功。

        let cell = UITableViewCell()
        
        cell.textLabel!.text = "Test Cell Here!!"
        
        return cell
}

Table 設定好之後,我們就要想辦法讓真正的資料顯示在 Cell 中

方便起見,我們先定義一個變數 items 來儲存所有新增的 Tasks

var tasks :Results<Task>!

然後在 viewDidLoad() 裡面把這些 Tasks 都拿出來:

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

    
    loadTasks()
}

func loadTasks() {
    tasks = realm.objects(Task).filter("isCompleted == 0")
}

viewDidLoad() 這個 function 的意思就是讓手機在 load 完這個 View 但是還沒顯示前做的事情,所以我們要趁沒顯示前先把資料準備好也是很合理的。

接下來就是把剛剛寫過的兩個 table 相關的 functions 改成以下的樣子:

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // 有多少 Tasks 就有多少 Cell

    return tasks.count
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = UITableViewCell()
    
    // 因為 tasks 是一個 List,所以我們可以從 cell 的順序來取得每一個 Task,按照順序顯示出來

    cell.textLabel!.text = tasks[indexPath.row].name
    
    return cell
}

最後還有一件事情要做,就是在每次 Task 有變動的時候記得要重新 refresh table,目前我們會變動的時候只有新增 Task 的時候,所以要加兩行 Code 在 addNewItem() 的裡面,分別是重新讀取 Tasks 的資料以及 Refresh table

self.loadTasks()
self.tableView.reloadData()

addNewItem 的 function 變成這樣:

@IBAction func addNewItem(sender: AnyObject) {
    // 定義 alertController, UIAlertController 就是之後會跳出來的 Popup View

    let alertController = UIAlertController(title: "Add Task", message: nil, preferredStyle: UIAlertControllerStyle.Alert)
    
    // 加上一個 Input 的 TextField 框框讓使用者輸入 Task Name

    alertController.addTextFieldWithConfigurationHandler({ (textField) -> Void in
        textField.placeholder = "type task name here.."
    })
    
    
    // 定義這個 Alert Popup 會有哪些動作,此例我們只需要 Cancel 和 Add

    let cancelAction = UIAlertAction(title: "Cancel", style: .Default, handler: { (action: UIAlertAction!) in
        // handler 裡面的 code 代表使用者按了這個 Action 以後要處理的事情。

        print("Canceled")
    })
    
    let addTaskAction = UIAlertAction(title: "Add", style: .Default, handler: { (action: UIAlertAction!) in
        let textField = alertController.textFields![0] as UITextField
        let taskName = textField.text! as String
        
        // 初始化 Task 的 Object

        let task = Task()
        task.name = taskName

        // 將 Task 寫進資料庫

        try! self.realm.write {
            self.realm.add(task)
        }
        
        self.loadTasks()
        self.tableView.reloadData()
    })
    
    
    // 把剛剛定義好的 Action 塞到 Alert 的 Popup 裡面

    alertController.addAction(cancelAction)
    alertController.addAction(addTaskAction)
    
    // 將前面設定好的 Alert View 顯示出來

    presentViewController(alertController, animated: true, completion: nil)
}

教學影片4 - 讓 Table 顯示 Data

5. 可以將每個 Task Mark as completed 和 Delete

接下來要做滑動 Cell 就可以把 Task 變成完成和刪除的功能,在 UITableViewDataSource 裡面有提供 editActionsForRowAtIndexPath 的 API 給我們用,所以只要寫以下的 Code 就可以實作:

func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
    let doneAction = UITableViewRowAction(style: .Normal, title: "Mark as completed") { action, index in
        let task = self.tasks[indexPath.row]
        
        try! self.realm.write {
            task.isCompleted = true
        }
        
        self.loadTasks()
        self.tableView.reloadData()
    }
    doneAction.backgroundColor = UIColor.greenColor()
    
    let deleteAction = UITableViewRowAction(style: .Default, title: "Delete") { action, index in
        let task = self.tasks[indexPath.row]
        
        
        try! self.realm.write {
            self.realm.delete(task)
        }
        
        self.loadTasks()
        self.tableView.reloadData()
    }
    
    return [deleteAction, doneAction]
}

6. 完成 Completed 的 Tab

以上就大致完成了這個 Todo App, 剩下的就是把一樣的功能也寫進 SecondViewController 裡面,大致上就把 FirstViewController 的 Code 貼過去,大概會長這樣:

import UIKit
import RealmSwift

class SecondViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    
    let realm = try! Realm()
    
    var tasks :Results<Task>!
    
    @IBOutlet weak var tableView: UITableView!
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        
        loadTasks()
    }

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

    }
    
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return tasks.count
    }
    
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = UITableViewCell()
        
        cell.textLabel!.text = tasks[indexPath.row].name
        
        return cell
    }



    @IBAction func addNewItem(sender: AnyObject) {
        let alertController = UIAlertController(title: "Add Task", message: nil, preferredStyle: UIAlertControllerStyle.Alert)
        
        alertController.addTextFieldWithConfigurationHandler({ (textField) -> Void in
            textField.placeholder = "type task name here.."
        })
        
        let cancelAction = UIAlertAction(title: "Cancel", style: .Default, handler: { (action: UIAlertAction!) in
            print("Canceled")
        })
        
        let addTaskAction = UIAlertAction(title: "Add", style: .Default, handler: { (action: UIAlertAction!) in
            let textField = alertController.textFields![0] as UITextField
            let taskName = textField.text! as String
            
            let task = Task()
            task.name = taskName
            

            try! self.realm.write {
                self.realm.add(task)
            }
        })
        
        
        alertController.addAction(cancelAction)
        alertController.addAction(addTaskAction)
        
        presentViewController(alertController, animated: true, completion: nil)
    }
    
    func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
        let doneAction = UITableViewRowAction(style: .Normal, title: "Mark as uncompleted") { action, index in
            let task = self.tasks[indexPath.row]
            
            try! self.realm.write {
                task.isCompleted = false
            }
            
            self.loadTasks()
            self.tableView.reloadData()
        }
        doneAction.backgroundColor = UIColor.greenColor()
        
        let deleteAction = UITableViewRowAction(style: .Default, title: "Delete") { action, index in
            let task = self.tasks[indexPath.row]
            
            
            try! self.realm.write {
                self.realm.delete(task)
            }
            
            self.loadTasks()
            self.tableView.reloadData()
        }
        
        return [deleteAction, doneAction]
    }
    
    
    func loadTasks() {
        tasks = realm.objects(Task).filter("isCompleted == 1")
    }
}

幾個注意的點

  1. 記得把 table view 跟 delegate 和 datasource reference 起來(storyboard)
  2. 把 mark as completed 改成 mark as uncompleted
  3. Load isCompleted == 1 的 tasks
  4. 新增完 task 不用 reload tasks 跟 table view

做完後其實會有一個小 Bug,就是 mark as completed/uncompleted 後再切換到另外一個 Tab 發現剛剛 Mark 的 Task 並沒有出現在上面,原因是因為我們只有在 viewDidLoad 的時候更新 Tasks,但是切換 Tab 的時候 View 早就 Loaded 好了,所以並不會再度觸發 viewDidLoad 裡面的 loadTasks(),所以其實我們應該要在這兩個 Tab 的 Controller 裡面再加上一個 function:

override func viewDidAppear(animated: Bool) {
    loadTasks()
    tableView.reloadData()
}

這段就會讓我們在切換 Tab 但是 View 還沒顯示出來的時候運行,以上。

最後成品:Github

待續

  1. 整理 Code
  2. Edit Task
  3. 批次刪除 Tasks

參考資料

Realm
CocoaPods
http://www.appcoda.com/realm-database-swift/

← iOS - FB Login - Swift - Tutorial and Example ios swift how to custom table view cell →
 
comments powered by Disqus