上一篇 開始用Swift開發(fā)iOS 10 - 16 介紹靜態(tài)Table Views,UIImagePickerController和NSLayoutConstraint 中添加新建restaurant頁面,但最后數(shù)據(jù)并沒有保存下來脓豪,這一篇使用Core Data方式來持久化保存數(shù)據(jù)禁熏。
數(shù)據(jù)持久化一般是指數(shù)據(jù)庫保存准夷。在Web開發(fā)中糙及,常用Oracle或MySQL等關(guān)系數(shù)據(jù)庫來保存數(shù)據(jù),通過SQL語句查詢尖阔。在iOS中對應(yīng)的數(shù)據(jù)庫是SQLite。Core Data不是數(shù)據(jù)庫榨咐,它是讓開發(fā)者通過面向?qū)ο蠓绞脚c數(shù)據(jù)庫進(jìn)行交互的庫介却。
使用Core Data的例子
新建一個使用Core Data的項目,在AppDelegate
類中會比平常多了一個變量和一方法块茁,另外還多了一個文件CoreDataDemo.xcdatamodeld
齿坷。
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentContainer(name: "CoreDataDemo")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
// MARK: - Core Data Saving support
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
- 變量
persistentContainer
是NSPersistentContainer
的實例,let container = NSPersistentContainer(name: "CoreDataDemo")
對應(yīng)CoreDataDemo.xcdatamodeld
文件数焊,如果是自己添加時名字需要對應(yīng)永淌。 - 當(dāng)數(shù)據(jù)變化(insert/update/delete)時 ,調(diào)用
saveContext
方法保存數(shù)據(jù)佩耳。
向項目中添加Data Model
- 右擊FoodPin文件夾遂蛀,選擇新建Data Model文件,文件名為
FoodPin
蚕愤。
- 選中新生成的
FoodPin.xcdatamodeld
答恶,添加一個Restaurant
Entity饺蚊,然后再在此Entity下添加一些屬性。
選中特定屬性后可在右側(cè)檢查器中設(shè)置相關(guān)特性,比如是否強制需要。
創(chuàng)建Managed Objects
Core Data框架中的 Managed Objects與Entity之間的關(guān)系座舍,有點像代碼中 接口變量 和 UI objects之間的關(guān)系锁摔。xcode可自動生成Managed Objects。
-
選中
Restaurant
Entity粮揉,在檢查器中修改class的name
為RestaurantMO
,Codegen
為Class Definition
。
-
command-R 或 comman-B一下苗缩,表面上沒有什么變化,在project navigator中沒有多出文件声诸。實際上已經(jīng)生成
RestaurantMO
類酱讶,代碼已經(jīng)可以使用了,如果使用command+點擊RestaurantMO
彼乌,就可以看到RestaurantMO
的代碼:
-
修改相關(guān)受影響的代碼
-
RestaurantTableViewController.swift
重新定義restaurants
:
var restaurants:[RestaurantMO] = []
由于CoreData中存儲圖片是二進(jìn)制泻肯,引用時不能用文件名:cell.thumbnailImageView.image = UIImage(data: restaurants[indexPath.row].image as! Data)
if let imageToShare = UIImage(data: self.restaurants[indexPath.row].image as! Data) {
由于
RestaurantMO
的屬性值是可選值,使用時需要解包:
let defaultText = "Just checking in at " + self.restaurants[indexPath.row].name!
-
RestaurantDetailViewController.swift
var restaurant:RestaurantMO!
restaurantImageView.image = UIImage(data: restaurant.image as! Data)
geoCoder.geocodeAddressString(restaurant.location!, completionHandler: { placemarks, error in
-
MapViewController.swift
var restaurant:RestaurantMO!
leftIconView.image = UIImage(data: restaurant.image as! Data)
-
ReviewViewController.swift
var restaurant:RestaurantMO!
restaurantImageView.image = UIImage(data: restaurant.image as! Data) ```
-
現(xiàn)在能成功運行慰照,發(fā)現(xiàn)是沒有數(shù)據(jù)的灶挟。
保存新數(shù)據(jù)到數(shù)據(jù)庫
- 在
AddTableViewController.swift
中引入Core Data:import CoreData
。添加變量var restaurant:RestaurantMO!
毒租。 - 在
AppDelegate
中加入上面例子一個變量和一方法稚铣。
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentContainer(name: "FoodPin")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
// MARK: - Core Data Saving support
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
- 在
AddTableViewController
的save
方法的dismiss
之前插入:
// 1
if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) {
restaurant = RestaurantMO(context: appDelegate.persistentContainer.viewContext)
restaurant.name = nameTextField.text
restaurant.type = typeTextField.text
restaurant.location = locationTextField.text
restaurant.isVisited = isVisited
if let restaurantImage = photoImageView.image {
// 2
if let imageData = UIImagePNGRepresentation(restaurantImage) {
restaurant.image = NSData(data: imageData)
}
}
print("Saving data to context ...")
appDelegate.saveContext()
}
-
UIApplication.shared
這種形式是iOS SDK中比較常用單例模式,就是通過一個類屬性shared
獲取整個app運行過程只需要一個實例的方法墅垮。UIApplication.shared.delegate as? AppDelegate
就獲取了AppDelegate
對象惕医。 - 獲取圖片的二進(jìn)制數(shù)據(jù)對象。
運行噩斟,添加新的restaurant后并沒有在Food Pin中顯示曹锨,實際已經(jīng)添加到數(shù)據(jù)庫中,在RestaurantTableViewController
里沒有向數(shù)據(jù)庫獲取剃允。
通過CoreData獲取數(shù)據(jù)
- 在
RestaurantTableViewController.swift
中添加import CoreData
沛简。實現(xiàn)協(xié)議NSFetchedResultsControllerDelegate
,這個協(xié)議中有方法斥废,任何時候當(dāng)獲取來的數(shù)據(jù)有變化時立即通知代理椒楣。
class RestaurantTableViewController: UITableViewController, NSFetchedResultsControllerDelegate
- 定義一個變量
var fetchResultController: NSFetchedResultsController<RestaurantMO>! - 在
viewDidLoad
中添加
// 1
let fetchRequest: NSFetchRequest<RestaurantMO> = RestaurantMO.fetchRequest()
// 2
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) {
let context = appDelegate.persistentContainer.viewContext
fetchResultController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
fetchResultController.delegate = self
}
do {
// 3
try fetchResultController.performFetch()
if let fetchedObjects = fetchResultController.fetchedObjects {
// 4
restaurants = fetchedObjects
}
} catch {
print(error)
}
- 1 從
RestaurantMO
對象獲得數(shù)據(jù)請求對象NSFetchRequest
。 - 2 通過
NSSortDescriptor
來設(shè)置獲取結(jié)果的排序方式牡肉。 - 3
performFetch
方法執(zhí)行從數(shù)據(jù)庫中獲取數(shù)據(jù)請求捧灰。 - 4 把請求結(jié)果復(fù)制給變量
restaurants
。
-
數(shù)據(jù)庫中數(shù)據(jù)變化,將調(diào)用來自
NSFetchedResultsControllerDelegate
三個方法毛俏,調(diào)用三個方法的時間可以簡單的理解分別為數(shù)據(jù)將要改變炭庙、數(shù)據(jù)正在改變、數(shù)據(jù)改變后:
controllerWillChangeContent(_:)
controller(_:didChange:at:for:newIndexPath:)
controllerDidChangeContent(_:)
方法的實現(xiàn)煌寇,也分別對table view有不同處理:
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type:
NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .insert:
if let newIndexPath = newIndexPath {
tableView.insertRows(at: [newIndexPath], with: .fade)
}
case .delete:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .fade)
}
case .update:
if let indexPath = indexPath {
tableView.reloadRows(at: [indexPath], with: .fade)
} default:
tableView.reloadData()
}
if let fetchedObjects = controller.fetchedObjects {
restaurants = fetchedObjects as! [RestaurantMO]
}
}
func controllerDidChangeContent(_ controller:
NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
現(xiàn)在運行程序焕蹄,添加新的Restaurant就能同步顯示了。
通過CoreData刪除數(shù)據(jù)
更新RestaurantTableViewController
的tableView(_:editActionsForRowAt:_)
方法中的 deleteAction
阀溶。
let deleteAction = UITableViewRowAction(style: .default, title: "Delete", handler: {
(action, indexPath) -> Void in
if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) {
let context = appDelegate.persistentContainer.viewContext
let restaurantToDelete = self.fetchResultController.object(at: indexPath)
context.delete(restaurantToDelete)
appDelegate.saveContext()
}
})
現(xiàn)在刪除一項后腻脏,重新啟動后,數(shù)據(jù)消失银锻。
更新數(shù)據(jù)
更新RestaurantDetailViewController
的中的ActionratingButtonTapped:
:
@IBAction func ratingButtonTapped(segue: UIStoryboardSegue) {
if let rating = segue.identifier {
restaurant.isVisited = true
switch rating {
case "great":
restaurant.rating = "Absolutely love it! Must try."
case "good":
restaurant.rating = "Pretty good."
case "dislike":
restaurant.rating = "I don't like it."
default:
break
}
}
if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) {
appDelegate.saveContext()
}
tableView.reloadData()
}
現(xiàn)在評價一項后永品,重新啟動后評價就會保留。
Exercise:添加新字段
之前新建Restaurant頁面沒有Phone字段击纬,現(xiàn)在添加
- 在SB的New Restaurant添加新Cell鼎姐,在
AddRestaurantController
中添加相關(guān)接口并關(guān)聯(lián)。 - 更新
AddRestaurantController
中的save:
Action相關(guān)代碼掉弛。
代碼
Beginning-iOS-Programming-with-Swift
說明
此文是學(xué)習(xí)appcode網(wǎng)站出的一本書 《Beginning iOS 10 Programming with Swift》 的一篇記錄
系列文章目錄
- 開始用Swift開發(fā)iOS 10 - 1 前言
- 開始用Swift開發(fā)iOS 10 - 2 Hello World症见!第一個Swift APP
- 開始用Swift開發(fā)iOS 10 - 3 介紹Auto Layout
- 開始用Swift開發(fā)iOS 10 - 4 用Stack View設(shè)計UI
- [開始用Swift開發(fā)iOS 10 - 5 原型的介紹]
- 開始用Swift開發(fā)iOS 10 - 6 創(chuàng)建簡單的Table Based App
- 開始用Swift開發(fā)iOS 10 - 7 定制Table Views
- 開始用Swift開發(fā)iOS 10 - 8 Table View和UIAlertController的交互
- 開始用Swift開發(fā)iOS 10 - 9 Table Row的刪除, UITableViewRowAction和UIActivityViewController的使用
- 開始用Swift開發(fā)iOS 10 - 10 Navigation Controller的介紹和Segue
- 開始用Swift開發(fā)iOS 10 - 11 面向?qū)ο缶幊探榻B
- 開始用Swift開發(fā)iOS 10 - 12 豐富Detail View和定制化Navigation Bar
- 開始用Swift開發(fā)iOS 10 - 13 Self Sizing Cells and Dynamic Type
- 開始用Swift開發(fā)iOS 10 - 14 基礎(chǔ)動畫喂走,模糊效果和Unwind Segue
- 開始用Swift開發(fā)iOS 10 - 15 使用地圖
- 開始用Swift開發(fā)iOS 10 - 16 介紹靜態(tài)Table Views殃饿,UIImagePickerController和NSLayoutConstraint