2016年3月6日 星期日

iOS筆記:Swift練習(5)

CoreData

提供了一種儲存資料到persistent store的方式, 可以將APP中的物件與資料庫中的表格相對映.

建立託管物件

被綁訂在CoreData的物件稱為託管物件. 它用來呈現APP的資料且存於托管物件內容中

託管物件是NSManagedObject的子類別, 代表一個Entity. 在Swift中, 我們在每一個所定義的property前面加上@NSManaged, 讓他對應Restaurant Entity的屬性(attribute).

import CoreData

class Restaurant:NSManagedObject{
    @NSManaged var name:String
    @NSManaged var type:String
    @NSManaged var location:String
    @NSManaged var phoneNumber:String?
    @NSManaged var image:NSData?
    @NSManaged var isVisited:NSNumber?
    @NSManaged var rating:String?

指定Entity給Restaurant類別
enter image description here

透過CoreData 儲存資料

  1. 從AppDelegate取得託管物件內容.
if let managedObjectContext = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext { ...
        }
  1. 呼叫insertNewObjectForEntityForName來針對Restaurant Entity建立託管物件
restaurant = NSEntityDescription.insertNewObjectForEntityForName("Restaurant", inManagedObjectContext: managedObjectContext) as! Restaurant
  1. 呼叫save方法來告訴context要儲存物件
do {
       try managedObjectContext.save()
   } catch {
       print(error)
       return
       }

透過CoreData讀取資料

最基本的方式就是用託管物件內容所提供的方法來達成:

if let managedObjectContext = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext {

       let fetchResultController = NSFetchedRequest(entityName: "Restaurant")
       do {
           try managedObjectContext.executefetchRequest(fetchRequest) as! [Restaurant]
       } catch {
           print(error)
       }
}

NSFetchedResultsController API

特別設計用來處理Core Data讀取後所回傳的結果, 並提供資料給表格視圖. 會監看託管物件內容中的物件變更, 然後將變更結果報告給其代理.

    1.
import CoreData

class RestaurantTableViewController: UITableViewController, NSFetchedResultsControllerDelegate 
  1. 宣告實體變數來讀取結果控制器
    var fetchResultController:NSFetchedResultsController!

  2. NSSortDescriptor可以讓你指定物件間的排序.
    當讀取結果建立完成後, 先初始化NSFetchedResultsController, 呼叫performFetch()來執行讀取結果. 完成後我們存取fetchedObjects來存取Restaurant物件. fetchedObjects回傳的事AnyObject屬性所以這邊要強制轉型!
let fetchRequest = NSFetchRequest(entityName: "Restaurant")
        let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
        fetchRequest.sortDescriptors = [sortDescriptor]

        if let managedObjectContext = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext {

            fetchResultController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
            fetchResultController.delegate = self

            do {
                try fetchResultController.performFetch()
                restaurants = fetchResultController.fetchedObjects as! [Restaurant]
            } catch {
                print(error)
            }
        }

如果有任何內容變更, 以下方法會被呼叫.

// MARK: - NSFetchedResultsControllerDelegate

    func controllerWillChangeContent(controller: NSFetchedResultsController) {
        tableView.beginUpdates()
    }

    func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {

        switch type {
        case .Insert:
            if let _newIndexPath = newIndexPath {
                tableView.insertRowsAtIndexPaths([_newIndexPath], withRowAnimation: .Fade)
            }
        case .Delete:
            if let _indexPath = indexPath {
                tableView.deleteRowsAtIndexPaths([_indexPath], withRowAnimation: .Fade)
            }
        case .Update:
            if let _indexPath = indexPath {
                tableView.reloadRowsAtIndexPaths([_indexPath], withRowAnimation: .Fade)
            }

        default:
            tableView.reloadData()
        }

        restaurants = controller.fetchedObjects as! [Restaurant]
    }

    func controllerDidChangeContent(controller: NSFetchedResultsController) {
        tableView.endUpdates()
    }

第一個方法是當讀取結果控制器準備開始處理內容變更時呼叫, 我們只是告訴表格視圖 “我們要更新表格了 快準備好”.
第二個方法是當託管物件內容有任何變更時會被呼叫.
第三個方始只是用來告訴表格視圖我們已經完成更新.

刪除CoreData資料

呼叫deleteObject的方法來刪除託管物件即可.

let deleteAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Delete",handler: { (action, indexPath) -> Void in

            // Delete the row from the database
            if let managedObjectContext = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext {

                let restaurantToDelete = self.fetchResultController.objectAtIndexPath(indexPath) as! Restaurant
                managedObjectContext.deleteObject(restaurantToDelete)

                do {
                    try managedObjectContext.save()
                } catch {
                    print(error)
                }
            }
        })

UISearchController

簡化了建立搜尋欄位的方法以及搜尋結果的處理. 甚至提供開發者透過自訂的動畫物件, 能更彈性的改變搜尋欄的動畫.

第二行為建立instance, 傳遞nil的值表示搜尋結果會顯示於你正在搜尋的相同視圖中.另外也可以指定其他ViewController來顯示搜尋結果.
第四行為告訴searchResultsUpdater, 哪一個物件會負責更新搜尋結果

var searchController:UISearchController!

searchController = UISearchController(searchResultsController: nil)
tableView.tableHeaderView = searchController.searchBar
searchController.searchResultsUpdater = self        

內容過濾

UISearchController在預設環境下並沒有提供任何過濾環境的功能, 必須自己負責實作內容的過濾規則.

在Swift中有一個內建方法叫filter, 是用來過濾目前的陣列. 只需要在閉包中提供過濾規則即可.
在下面的程式中, 我們使用rangeOfString來檢查是否有相對應的名稱在搜尋文字內, 若是沒有找到就會回傳nil, 最後再檢查是否有找到搜尋文字, 並回傳相對應的Bool value.

func filterContentForSearchText(searchText: String) {
        searchResults = restaurants.filter({ (restaurant:Restaurant) -> Bool in
            let nameMatch = restaurant.name.rangeOfString(searchText, options: NSStringCompareOptions.CaseInsensitiveSearch)
            return nameMatch != nil
        })
    }

更新搜尋結果

接著要實作如何在畫面上更新與顯示搜尋到的結果.

  1. 使用UISearchResultsUpdating Delegate.
  2. updateSearchResultsForSearchController 為定義在協定中的一個方法. 當使用者選取搜尋欄時或者在裡面做了些變更時會被呼叫.
func updateSearchResultsForSearchController(searchController: UISearchController) {
    if let searchText = searchController.searchBar.text {
        filterContentForSearchText(searchText)
        tableView.reloadData()
    }
}
  1. 當完成到這一步的時候, tableView會同時顯示所有的餐廳資訊以及你的搜尋結果, 所以需要透過以下的方式來變免這種狀況發生.
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if searchController.active {
            return searchResults.count
        } else {
            return restaurants.count
        }
    }

自訂SearchBar外觀

// Customize the appearance of the search bar
        searchController.searchBar.placeholder = "Search restaurants..."
        searchController.searchBar.tintColor = UIColor(red: 100.0/255.0, green: 100.0/255.0, blue: 100.0/255.0, alpha: 1.0)
        searchController.searchBar.barTintColor = UIColor(red: 240.0/255.0, green: 240.0/255.0, blue: 240.0/255.0, alpha: 0.6)

Optional Binding - if let & if var

如果無法確定變數是否為nil就貿然使用! 運算子,當該變數真的為nil時,就會發生Run-Time Error。所以當在執行Optional型別的運算的時候,最好是先判斷是否為nil值再進行運算。

var a:String = "Hello "
var b:String? = "bbb"

if b != nil {
    var c = b!
    print(a+c)
}

上面的寫法其實有些麻煩,所以Swift提供了一個語法 - 使用if let或if var。如下例所示,意思是如果b變數不是nil,則c變數等於b變數,並執行後面的statement。如果b變數是nil,則不執行該段statement。if let或if var就是所謂的Optional Binding語法

var a:String = "Hello "
var b:String? = "bbb"

if let c = b {
    print(a+c)
}

沒有留言:

張貼留言