顯示具有 Core Data 標籤的文章。 顯示所有文章
顯示具有 Core Data 標籤的文章。 顯示所有文章

2014年7月11日 星期五

Core Data programmatically using Swift language(Update data)

【說明】

此份筆記是根據ISBN:978-986-201-900-9這本書的第19章節所紀錄的。

專案範例延續著這篇,新增更新資料的動作。

【專案開發步驟】

更新一個託管物件:

在TableView與NavigationController之間在新增一個Segue,從Cell到Controller,並設定Identifier為UpdateRecipe,如下圖所示。

新增Segue
在RecipeStoreTableViewController.swift檔案內加入prepareForSugue:方法,如下所示。

override func prepareForSegue(_segue: UIStoryboardSegue!, sender: AnyObject!) {
        
    if (_segue.identifier == "UpdateRecipe") {
        var selectRecipe: NSManagedObject = recipes.objectAtIndex(tableView.indexPathForSelectedRow().row) as NSManagedObject
        var destViewController: UINavigationController = _segue.destinationViewController as UINavigationController
        var recipeViewController: AddRecipeViewController = destViewController.topViewController as AddRecipeViewController
        recipeViewController.recipe = selectRecipe
    }

}
當使用者按下Cell時會觸發這個方法傳送Segue給目的地的ViewController,首先先確認所觸發的Segue是不是Identifier名為UpdateRecipe,如果是的話進迴圈裡面。建立一個NSManagedObject,名為selectRecipe,儲存使用者所選擇的資料。建立一個UINavigationController,名為destViewController,儲存Segue目的地端的ViewController。建立一個名為recipeViewController的AddRecipeViewController,利用topViewController的屬性得到Navigation的堆疊(Stack)中最上層的ViewController。透過recipeViewController將AddRecipeViewController的recipe設為selectRecipe。

修改AddRecipeViewController.swift,如下所示。

var recipe: NSManagedObject!
新增一個名為recipe的全域變數,資料型態為NSManagedObject。

override func viewDidLoad() {
    super.viewDidLoad()

    if (recipe) {
        nameTextField.text = recipe.valueForKey("name") as String
        imageTextField.text = recipe.valueForKey("image") as String
        prepTimeTextField.text = recipe.valueForKey("prepTime") as String
    }

}
recipe只有在Cell被按下時才會有值,其餘時間只是個空的變數而已,所以我們利用if去判斷當AddRecipeViewController出現的時候TextField需不需要顯示文字,若if成立將會把使用者所點選的Cell的資料顯示在TextField內。

修改AddRecipeViewController.swift內的save函式,如下所示。

@IBAction func save(sender: UIBarButtonItem) {
        
    var context: NSManagedObjectContext = self.managedObjectContext()
        
    if (recipe) {
            
        recipe.setValue(nameTextField.text, forKey: "name")
        recipe.setValue(imageTextField.text, forKey: "image")
        recipe.setValue(prepTimeTextField.text, forKey: "prepTime")
    } else {
            
        var newRecipe: NSManagedObject = NSEntityDescription.insertNewObjectForEntityForName("Recipe", inManagedObjectContext: context) as NSManagedObject
        newRecipe.setValue(nameTextField.text, forKey: "name")
        newRecipe.setValue(imageTextField.text, forKey: "image")
        newRecipe.setValue(prepTimeTextField.text, forKey: "prepTime")
    }
        
    self.dismissViewControllerAnimated(true, completion: nil)

}
差別在於我們要知道NSManagedObject是否要新建立一個,所以用if來做判斷資料是否存在。若是已有的資料,則託管物件就不需要在被建立,用舊的託管物件去設定資料的值即可將舊的覆蓋過去。若是新建立的資料則必須新建一個託管物件,使用新的託管文件將資料儲存。最後讓ViewCotroller離開即可。


【執行結果】

    



【專案範例】

2014年7月10日 星期四

Core Data programmatically using Swift language(Delete data)

【說明】

此份筆記是根據ISBN:978-986-201-900-9這本書的第19章節所紀錄的。

專案範例延續著這篇,新增刪除資料的動作。

刪除一個託管物件是很容易的,只要呼叫deleteObject:方法,並將要刪除的物件作為參數來傳遞,即可刪除資料。


【專案開發步驟】

刪除一個託管物件:

為了要讓UITableView能夠讓使用者使用刪除功能,必須實作UITableViewDataSource協定中的canEditRowAtIndexPath:與commitEditingStyle:方法如下所示。

override func tableView(tableView: UITableView!, canEditRowAtIndexPath indexPath: NSIndexPath!) -> Bool {
    return true

}
決定TableView的Row是否可以被編輯。

override func tableView(tableView: UITableView!, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath!) {
            
    var context: NSManagedObjectContext = managedObjectContext()
        
    if editingStyle == .Delete {
            
        context.deleteObject(recipes.objectAtIndex(indexPath.row) as NSManagedObject)
            
        recipes.removeObjectAtIndex(indexPath.row)
        tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
            
    } else if editingStyle == .Insert {

    }   
}
先取得託管物件本文,因為NSManagedObjectContext提供了deleteObject:方法,讓我們可以刪除特定的物件。利用本文執行deleteObject:方法,將選到的indexPath.row刪除。Core Data被刪除後也要將TableView上的資料刪除。


【執行結果】

    


【專案範例】

2014年7月9日 星期三

Core Data programmatically using Swift language

【說明】

此份筆記是根據ISBN:978-986-201-900-9這本書的第18章節所紀錄的。

Core Data與plist的差異:
plist處理小資料量時比較快速,但是會一次把文件載入記憶體內,效率上不是很優。Core Data擁有table之間的Relationship,與SQLite相似,但是不必下語法。

Core Data就是可以儲存到磁碟的物件圖,不僅僅在磁碟上儲存資料,也把我們需要的資料物件讀取到記憶體中。   - Marcus Zarra, Core Data

Core Data主要任務是負責資料更改的管理,序列化到磁碟,最小化記憶體佔用,以及查詢資料,可以將資料儲存為XML、二進制檔案或是SQLite檔案。

Core Data堆疊(Stack):
託管物件模型(Managed Object Model):描述你在程式裡使用的Schema(綱要)。
永續性儲存協調器(Persistent Store Coordinator):負責處理不同永續性物件儲存區以及將物件儲存到儲存區中。
託管物件本文(Managed Object Context):管理被建立的物件並使用Core Data回傳。


【專案開發步驟】

建立專案:

使用Empty Application模板建立專案,專案名稱設為RecipeStore,並勾選使用Core Data。

因為使用Empty Application建立專案,所以需新增Storyboard來方便我們撰寫程式,並將名稱設為Main,如下圖所示。

新增Storyboard
點選專案,將Main Interface設為Main,如下圖所示。

設定Main Interface
將didFinishLaunchingWithOptions:這個方法更新,如下所示。

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
    
    return true
}

定義託管物件模型:

在資料模型內建立一個Entity,並取名為Recipe,如下圖所示。

建立Entity
在名為Recipe的Entity內建立三個Attributes,分別為name、image、prepTime,並設三個資料型態為String,如下圖所示。

建立Attributes

設計使用者介面:

依照下圖建立好介面。

建立使用者介面
將+的Segue Identifier設為Add,如下圖所示。
設定Segue Identifier
並將Prototype Cell的Identifier設為Cell,如下圖所示。

設定Identifier為Cell
建立一個TableViewController的類別,取名為RecipeStoreTableViewController,並繼承UITableViewController,至Storyboard設定介面與自訂類別的關聯,如下圖所示。

設定頁面與自訂類別的關聯
建立一個ViewController的類別,取名為AddRecipeViewController,並繼承UIViewController,至Storyboard設定介面與自訂類別的關聯,如下圖所示。

設定頁面與自訂類別的關聯
建立AddRecipeViewController頁面上,三個TextField與兩個Bar Button的IBOutlet與IBAction,如下圖所示。

建立物件的關聯

建立託管物件:

在AppRecipeViewController.swift內建立一個func,如下所示。

func managedObjectContext() -> NSManagedObjectContext {
        
    let appDelegate: AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
    let context: NSManagedObjectContext = appDelegate.managedObjectContext
        
    return context

}
透過這個func可以管理物件,經由本文就可以取得或變更託管物件。

在AppRecipeViewController.swift實作save:方法,如下所示。

@IBAction func save(sender: UIBarButtonItem) {
        
    var context: NSManagedObjectContext = self.managedObjectContext()
        
    var newRecipe: NSManagedObject = NSEntityDescription.insertNewObjectForEntityForName("Recipe", inManagedObjectContext: context) as NSManagedObject
    newRecipe.setValue(nameTextField.text, forKey: "name")
    newRecipe.setValue(imageTextField.text, forKey: "image")
    newRecipe.setValue(prepTimeTextField.text, forKey: "prepTime")
        
    self.dismissViewControllerAnimated(true, completion: nil)

}
先取得託管物件本文,然後針對新的食譜建立一個NSManagedObject,並將託管物件跟Entity關聯在一起。NSManagedObject就像是NSDictionary一樣,一旦建立託管物件(newRecipe)後,即可將Entity的屬性當作Key一樣來設定value。最後dismissViewControllerAnimated用來解除目前的ViewController。

在AppRecipeViewController.swift實作cancel:方法,如下所示。

@IBAction func cancel(sender: UIBarButtonItem) {
        
    self.dismissViewControllerAnimated(true, completion: nil)

}
解除目前的ViewController。

取得託管物件:

在RecipeStoreTableViewController.swift內建立一個名為recipes的可變陣列,如下所示。

var recipes: NSMutableArray = []
資料型態為NSMutableArray的陣列變數,名為recipes。

在RecipeStoreTableViewController.swift內建立一個func,取得託管物件文本,與AppRecipeViewController.swift內的func相同。

在RecipeStoreTableViewController.swift內增加viewDidAppear方法,如下所示。

override func viewDidAppear(animated: Bool)  {
    super.viewDidAppear(true)

    var managedObjectContext = self.managedObjectContext()
    let fetchRequest = NSFetchRequest(entityName: "Recipe")
    recipes = NSMutableArray(array: managedObjectContext.executeFetchRequest(fetchRequest, error: nil))
        
    self.tableView.reloadData()

}
為了要取得資料,必須取得本文(Context)。建立一個提取需求(fetch request),並指定要去提取的Entity。利用Recipe entity建立一個NSFetchRequest的實體,並用executeFetchRequest:這個方法取得資料庫的所有食譜。最後讓tableView重新載入。
viewDidAppear方法是在View確實被顯示出來時才會呼叫的,利用這個方法讓新食譜被建立後重新載入資料,但這不是好的方法,因為若使用者不想新建食譜時按下取消也會被呼叫。

在TableView中填入食譜:

在RecipeStoreTableViewController.swift內實作以下方法。

override func numberOfSectionsInTableView(tableView: UITableView!) -> Int {
    return 1

}
回傳有幾個Section在TableView裏面。

override func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
    return recipes.count

}
告訴ViewController要顯示多少個Cell。

override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell? {
        
    var cellIdentifier: String = "Cell"
    var cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as UITableViewCell
        
    if cell == nil {
        cell = UITableViewCell(style: .Default, reuseIdentifier: cellIdentifier)
    }

    var recipe: NSManagedObject = recipes.objectAtIndex(indexPath.row) as NSManagedObject
    
    cell.textLabel.text = recipe.valueForKey("name") as String
    var imageString: String = recipe.valueForKey("image") as String
    var prepTimeString: String = recipe.valueForKey("prepTime") as String
    cell.detailTextLabel.text = "\(imageString) - \(prepTimeString)"
        
    return cell

}
在TableView內顯示資料,可參考這篇。NSManagedObject與NSDictionary非常像,使用valueForKey方法就可以取得屬性值。將食譜名稱作為標題,圖檔名與準備時間以子標題方式呈現。


【執行結果】

    

    


【專案範例】