在本系列的前几期中,我们介绍了Core Data框架的基础。 是时候我们通过构建一个由Core Data驱动的简单应用程序来运用我们的知识了。
在本教程中,我们还将遇到核心数据框架的另一个明星角色NSFetchedResultsController
类。 我们将要创建的应用程序将管理待办事项列表。 使用该应用程序,我们可以添加,更新和删除待办事项。 您将很快了解到NSFetchedResultsController
类使此操作非常容易。
先决条件
我在本系列的“核心数据”中介绍的内容适用于iOS 7+和OS X 10.10+,但是重点将放在iOS上。 在本系列中,我将使用Xcode 7.1和Swift 2.1。 如果您更喜欢Objective-C,那么我建议阅读我先前关于Core Data framework的系列文章 。
1.项目设置
打开Xcode,从“ 文件”菜单中选择“ 新建”>“项目... ”,然后从“ iOS”>“ 应用程序”类别中选择“ 单视图应用程序”模板。
将项目命名为Done ,将Language设置为Swift ,并将Devices设置为iPhone 。 因为我想向您展示如何从头开始创建Core Data应用程序,所以请确保未选中标记Use Core Data的复选框。 告诉Xcode您要将项目保存到何处,然后单击创建以创建项目。
2.核心数据设置
开放AppDelegate.swift和声明三个懒惰存储属性managedObjectModel
类型的NSManagedObjectModel
, managedObjectContext
类型NSManagedObjectContext
,和persistentStoreCoordinator
类型的NSPersistentStoreCoordinator
。 如果您对此步骤感到困惑,那么建议您重新阅读本系列的第一篇文章 ,其中详细介绍了Core Data堆栈。
// MARK: -
// MARK: Core Data Stack
lazy var managedObjectModel: NSManagedObjectModel = {
}()
lazy var managedObjectContext: NSManagedObjectContext = {
}()
lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
}()
请注意,我还在AppDelegate.swift的顶部添加了Core Data框架的import语句。
import UIKit
import CoreData
请记住,我们使用lazy
关键字延迟设置了Core Data堆栈。 这意味着我们在应用程序需要它们时实例化托管对象上下文,托管对象模型和持久性存储协调器。 以下实现看起来非常熟悉。
lazy var managedObjectModel: NSManagedObjectModel = {
let modelURL = NSBundle.mainBundle().URLForResource("Done", withExtension: "momd")!
return NSManagedObjectModel(contentsOfURL: modelURL)!
}()
lazy var managedObjectContext: NSManagedObjectContext = {
let persistentStoreCoordinator = self.persistentStoreCoordinator
// Initialize Managed Object Context
var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
// Configure Managed Object Context
managedObjectContext.persistentStoreCoordinator = persistentStoreCoordinator
return managedObjectContext
}()
lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
// Initialize Persistent Store Coordinator
let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
// URL Documents Directory
let URLs = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
let applicationDocumentsDirectory = URLs[(URLs.count - 1)]
// URL Persistent Store
let URLPersistentStore = applicationDocumentsDirectory.URLByAppendingPathComponent("Done.sqlite")
do {
// Add Persistent Store to Persistent Store Coordinator
try persistentStoreCoordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: URLPersistentStore, options: nil)
} catch {
// Populate Error
var userInfo = [String: AnyObject]()
userInfo[NSLocalizedDescriptionKey] = "There was an error creating or loading the application's saved data."
userInfo[NSLocalizedFailureReasonErrorKey] = "There was an error creating or loading the application's saved data."
userInfo[NSUnderlyingErrorKey] = error as NSError
let wrappedError = NSError(domain: "com.tutsplus.Done", code: 1001, userInfo: userInfo)
NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)")
abort()
}
return persistentStoreCoordinator
}()
您应该注意三件事。 首先,接下来将创建的数据模型将命名为Done.momd 。 其次,我们将命名后备存储命名为Done ,它将是一个SQLite数据库。 第三,如果后备存储与数据模型不兼容,我们调用abort
,终止应用程序。 就像我在本系列文章前面提到的那样,尽管在开发过程中很好,但是您永远都不要在生产中调用abort
。 我们将在本系列的后面部分重新讨论迁移和不兼容问题。
如果您尝试运行我们的应用程序不会使其崩溃,则不会正确设置Core Data堆栈。 原因很简单,我们尚未创建数据模型。 让我们在下一步中进行处理。
3.创建数据模型
从文件菜单中选择新建>文件... ,然后从iOS>核心数据类别中选择数据模型 。
将文件命名为Done ,仔细检查它是否已添加到Done目标中,并告诉Xcode需要将其保存在何处。
数据模型将非常简单。 创建一个新实体并将其命名为Item 。 向实体添加三个属性, 类型为String的 名称 ,类型为Date的 createdAt和类型为Boolean的 完成 。
将属性标记为必需属性,而不是可选属性,并将done属性的默认值设置为NO
。
设置了核心数据堆栈,并正确配置了数据模型。 是时候熟悉核心数据框架的新类NSFetchedResultsController
类了。
4.管理数据
本文不仅涉及NSFetchedResultsController
类,还涉及NSFetchedResultsController
类在幕后所做的事情。 让我澄清一下我的意思。
如果要在没有NSFetchedResultsController
类的情况下构建应用程序,则需要找到一种方法来保持应用程序的用户界面与Core Data管理的数据同步。 幸运的是,Core Data可以很好地解决此问题。
每当在托管对象上下文中插入 , 更新或删除记录时,托管对象上下文都会通过通知中心发布通知。 受管对象上下文发布三种类型的通知:
-
NSManagedObjectContextObjectsDidChangeNotification
:每当插入,更新或删除托管对象上下文中的记录时,都会发布此通知。 -
NSManagedObjectContextWillSaveNotification
: 在将挂起的更改提交到后备存储之前,此通知由托管对象上下文发布。 -
NSManagedObjectContextDidSaveNotification
: 在将未决更改提交到后备存储后 ,托管对象上下文会立即发布此通知。
这些通知的内容是相同的,也就是说,通知的object
属性是发布该通知的NSManagedObjectContext
实例,并且该通知的userInfo
词典包含已插入 , 更新和删除的记录 。
要点是,它需要大量的样板代码以使获取请求的结果保持最新。 换句话说,如果我们不使用NSFetchedResultsController
类来创建应用程序,则必须实现一种机制来监视托管对象上下文的更改并相应地更新用户界面。 让我们看看NSFetchedResultsController
如何帮助我们完成此任务。
5.设置用户界面
使用NSFetchedResultsController
类非常容易。 NSFetchedResultsController
类的实例接收获取请求并具有委托对象。 NSFetchedResultsController
对象通过监视执行提取请求的托管对象上下文来确保它使获取请求的结果保持最新。
如果NSFetchedResultsController
对象被获取请求的NSManagedObjectContext
对象通知任何更改,它将通知其委托。 您可能想知道与直接监视NSManagedObjectContext
对象的视图控制器有何不同。 NSFetchedResultsController
类的NSFetchedResultsController
在于,它处理从NSManagedObjectContext
对象收到的通知,并仅告诉委托者响应这些更改而需要知道哪些信息来更新用户界面。 NSFetchedResultsControllerDelegate
协议的方法应对此进行澄清。
optional public func controllerWillChangeContent(controller: NSFetchedResultsController)
optional public func controllerDidChangeContent(controller: NSFetchedResultsController)
optional public func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType)
optional public func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?)
optional public func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String) -> String?
上述委托方法的签名揭示了NSFetchedResultsController
类的真正目的。 在iOS上, NSFetchedResultsController
类旨在管理由UITableView
或UICollectionView
显示的数据。 它准确地告诉其委托人哪些记录已更改,如何更新用户界面以及何时执行此操作。
如果您仍然不确定NSFetchedResultsController
类的目的或优点,请不要担心。 一旦实现了NSFetchedResultsControllerDelegate
协议,它将变得更加有意义。 让我们重新访问应用程序,并使用NSFetchedResultsController
类。
步骤1:填充情节提要
打开项目的主故事板Main.storyboard ,选择View Controller Scene ,然后通过从Editor菜单中选择Embed In> Navigation Controller将其嵌入到导航控制器中 。
在视图控制器场景中拖动UITableView
对象 ,在ViewController
类中创建一个插座,然后将其连接到情节提要中。 不要忘记使ViewController
类符合UITableViewDataSource
和UITableViewDelegate
协议。
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
@IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
}
}
选择表视图,打开Connections Inspector ,然后将表视图的dataSource
和delegate
出口连接到View Controller对象。 在选择表视图的情况下,打开“ 属性”检查器并将“ 原型单元”的数量设置为1 。
在继续之前,我们需要为原型单元创建一个UITableViewCell
子类。 创建一个新的Objective-C子类ToDoCell
,并将其超类设置为UITableViewCell
。 创建两个出口, nameLabel
类型UILabel
和doneButton
类型UIButton
。
回到情节ToDoCell
在表视图中选择原型单元,然后在Identity Inspector中将类设置为ToDoCell
。 将UILabel
和UIButton
对象添加到单元格的内容视图,然后在Connections Inspector中连接出口。 选择原型单元后,打开“ 属性”检查器 ,并将原型单元的标识符设置为ToDoCell
。 该标识符将用作单元的重用标识符。 原型单元的布局应类似于以下屏幕截图。
创建一个新的ViewController
子类,并将其命名为AddToDoViewController
。 打开AddToDoViewController.swift ,声明一个UITextField
类型的出口textField
,并使视图控制器符合UITextFieldDelegate
协议。
import UIKit
class AddToDoViewController: UIViewController, UITextFieldDelegate {
@IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
}
在将视图控制器添加到情节提要之前,请将以下两个动作添加到视图控制器的实现文件中。
// MARK: -
// MARK: Actions
@IBAction func cancel(sender: AnyObject) {
}
@IBAction func save(sender: AnyObject) {
}
再打开故事板一次,然后在ViewController
导航栏的右侧添加一个标识符为Add的条形按钮项。 将一个新的视图控制器添加到情节AddToDoViewController
,并将其类设置为Identity Inspector中的 AddToDoViewController
。 选择视图控制器后,从“ 编辑器”菜单中选择“ 嵌入”>“导航控制器 ”。
现在,新的视图控制器应该具有导航栏。 在导航栏中添加两个栏按钮项,一个在左边,其标识为Cancel ,在右边,一个标识为Save 。 将cancel(_:)
操作连接到左栏按钮项,将save(_:)
操作连接到右栏按钮项。
将UITextField
对象添加到视图控制器的视图,并将其放置在导航栏下方20点处。 文本字段应保留在导航栏下方的20点处。 请注意,顶部的布局约束参考顶部的布局指南 。
将文本字段与视图控制器中相应的插座连接,然后将视图控制器设置为文本字段的委托。 最后,将控件从ViewController
的bar按钮项目拖到AddToDoViewController
是其根视图控制器的导航控制器。 将segue类型设置为Present Modally并将segue的标识符设置为Attributes Inspector中的 SegueAddToDoViewController
。 这要花很多钱。有趣的部分还没有到。
步骤2:实施表格视图
在可以旋转应用程序之前,我们需要在ViewController
类中实现UITableViewDataSource
协议。 但是,这是NSFetchedResultsController
类起作用的地方。 为了确保一切正常,请从tableView(_:numberOfRowsInSection:)
方法返回0
。 这将导致表视图为空,但是它将使我们能够运行应用程序而不会崩溃。
// MARK: -
// MARK: Table View Data Source Methods
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0
}
为了使编译器满意,我们还需要实现tableView(_:cellForRowAtIndexPath:)
。 在AddToDoViewController.swift的顶部,为单元重用标识符添加一个常量。
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let ReuseIdentifierToDoCell = "ToDoCell"
...
}
实现tableView(_:cellForRowAtIndexPath:)
非常简单,因为我们还没有对表格视图单元格做任何特别的事情。
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(ReuseIdentifierToDoCell, forIndexPath: indexPath) as! ToDoCell
return cell
}
步骤3:储存并取消
打开AddToDoViewController
类,并实现cancel(_:)
和save(_:)
方法,如下所示。 我们将在本教程的后面部分更新它们的实现。
// MARK: -
// MARK: Actions
@IBAction func cancel(sender: AnyObject) {
dismissViewControllerAnimated(true, completion: nil)
}
@IBAction func save(sender: AnyObject) {
dismissViewControllerAnimated(true, completion: nil)
}
在模拟器中构建并运行该应用程序,以查看是否已正确连接所有内容。 您应该能够点击右上角的添加按钮以调出AddToDoViewController
并通过点击取消或保存按钮将其关闭。
6.添加NSFetchedResultsController
类
NSFetchedResultsController
类是Core Data框架的一部分,用于管理获取请求的结果。 该类旨在与iOS上的UITableView
和UICollectionView
以及OS X上的NSTableView
无缝UICollectionView
。但是,它也可以用于其他目的。
步骤1:奠定基础
但是,在我们开始使用NSFetchedResultsController
类之前, ViewController
类需要访问NSManagedObjectContext
实例,即我们先前在应用程序委托中创建的NSManagedObjectContext
实例。 首先声明一个类型为NSManagedObjectContext!
的属性managedObjectContext
NSManagedObjectContext!
在ViewController
类的头文件中。 请注意,我们还在顶部为Core Data框架添加了import语句。
import UIKit
import CoreData
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let ReuseIdentifierToDoCell = "ToDoCell"
@IBOutlet weak var tableView: UITableView!
var managedObjectContext: NSManagedObjectContext!
...
}
打开Main.storyboard ,选择情节提要的初始视图控制器,一个UINavigationController
实例,然后在Identity Inspector中将其情节提要ID设置为StoryboardIDRootNavigationController
。
在应用程序委托的application(_:didFinishLaunchingWithOptions:)
方法中,我们获得对ViewController
实例(导航控制器的根视图控制器)的引用,并设置其managedObjectContext
属性。 更新后的application(_:didFinishLaunchingWithOptions:)
方法如下所示:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Fetch Main Storyboard
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
// Instantiate Root Navigation Controller
let rootNavigationController = mainStoryboard.instantiateViewControllerWithIdentifier("StoryboardIDRootNavigationController") as! UINavigationController
// Configure View Controller
let viewController = rootNavigationController.topViewController as? ViewController
if let viewController = viewController {
viewController.managedObjectContext = self.managedObjectContext
}
// Configure Window
window?.rootViewController = rootNavigationController
return true
}
为了确保一切正常,将以下打印语句添加到ViewController
类的viewDidLoad()
方法。
override func viewDidLoad() {
super.viewDidLoad()
print(managedObjectContext)
}
步骤2:初始化NSFetchedResultsController
实例
打开ViewController
类的实现文件,并声明NSFetchedResultsController
类型的惰性存储属性。 将属性命名为fetchedResultsController
。 NSFetchedResultsController
实例还具有需要符合NSFetchedResultsControllerDelegate
协议的委托属性。 因为ViewController
实例将充当NSFetchedResultsController
实例的委托,所以我们需要使ViewController
类符合NSFetchedResultsControllerDelegate
协议,如下所示。
import UIKit
import CoreData
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate {
let ReuseIdentifierToDoCell = "ToDoCell"
@IBOutlet weak var tableView: UITableView!
var managedObjectContext: NSManagedObjectContext!
var fetchedResultsController: NSFetchedResultsController = {
}()
...
}
现在该初始化NSFetchedResultsController
实例了。 NSFetchRequest
对象是获取结果控制器的NSFetchRequest
,因为它确定获取结果控制器将管理哪些记录。 在视图控制器的viewDidLoad()
方法中,我们通过将"Item"
传递给initWithEntityName(_:)
方法来初始化获取请求。 到目前为止,这应该已经很熟悉了,在接下来的行中,我们在提取请求中添加了排序描述符,以便根据每个记录的createdAt属性的值对结果进行排序。
lazy var fetchedResultsController: NSFetchedResultsController = {
// Initialize Fetch Request
let fetchRequest = NSFetchRequest(entityName: "Item")
// Add Sort Descriptors
let sortDescriptor = NSSortDescriptor(key: "createdAt", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
// Initialize Fetched Results Controller
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
// Configure Fetched Results Controller
fetchedResultsController.delegate = self
return fetchedResultsController
}()
获取结果控制器的初始化非常简单。 init(fetchRequest:managedObjectContext:sectionNameKeyPath:cacheName:)
方法采用四个参数:
- 提取请求
- 获取结果控制器将监视的管理对象上下文
- 如果您希望将结果分成多个部分,则为一个部分的关键路径
- 缓存名称(如果要启用缓存)
现在,我们为最后两个参数传递nil
。 第一个参数很明显,但是为什么还要传递一个NSManagedObjectContext
对象呢? 传入的托管对象上下文不仅用于执行提取请求,而且提取结果控制器还将监视托管对象上下文中的更改。 当我们开始实现NSFetchedResultsControllerDelegate
协议的委托方法时,这将在几分钟后变得更加清晰。
最后,我们需要告诉获取结果控制器执行传递给我们的获取请求。 我们通过在viewDidLoad()
调用performFetch()
来实现。 请注意,这是一个throwing方法,这意味着我们需要将其包装在do-catch
语句中。 performFetch()
方法类似于NSManagedObjectContext
类的executeFetchRequest(_:)
方法。
override func viewDidLoad() {
super.viewDidLoad()
do {
try self.fetchedResultsController.performFetch()
} catch {
let fetchError = error as NSError
print("\(fetchError), \(fetchError.userInfo)")
}
}
步骤3:实现委托协议
设置好获取的结果控制器并准备使用后,我们需要实现NSFetchedResultsControllerDelegate
协议。 如我们先前所见,协议定义了五种方法,本教程中我们感兴趣三种方法:
-
controllerWillChangeContent(controller: NSFetchedResultsController)
-
controllerDidChangeContent(controller: NSFetchedResultsController)
-
controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?)
前两种方法告诉我们提取结果控制器正在管理的数据何时发生并且确实发生了变化。 这对于批量更新用户界面很重要。 例如,可能同时发生多个更改。 在完成所有更改后,我们将批量更新用户界面,而不是为每次更改都更新用户界面。
在我们的示例中,这归结为controllerWillChangeContent(controller: NSFetchedResultsController)
和controllerDidChangeContent(controller: NSFetchedResultsController)
的以下实现。
// MARK: -
// MARK: Fetched Results Controller Delegate Methods
func controllerWillChangeContent(controller: NSFetchedResultsController) {
tableView.beginUpdates()
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}
controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?)
的实现有些棘手。 该委托方法接受不少于五个参数:
-
NSFetchedResultsController
实例 - 已更改的
NSManagedObject
实例 - 获取的结果控制器中记录的当前索引路径
- 更改的类型,即insert , update或delete
- 更改后 ,获取的结果控制器中记录的新索引路径
请注意,索引路径与我们的表视图无关。 NSIndexPath
只不过是一个包含一个或多个索引的对象,这些索引表示层次结构中的路径,因此也包含类的名称。
在内部,获取的结果控制器管理分层数据结构,并在该数据结构发生更改时通知其委托。 我们需要在表格视图中可视化这些更改。
controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?)
看起来令人生畏,但让我controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?)
一下。
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch (type) {
case .Insert:
if let indexPath = newIndexPath {
tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
break;
case .Delete:
if let indexPath = indexPath {
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
break;
case .Update:
if let indexPath = indexPath {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! ToDoCell
configureCell(cell, atIndexPath: indexPath)
}
break;
case .Move:
if let indexPath = indexPath {
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
if let newIndexPath = newIndexPath {
tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
}
break;
}
}
更改类型的类型为NSFetchedResultsChangeType
。 该枚举具有四个成员值:
-
Insert
-
Delete
-
Move
-
Update
名称很不言自明。 如果类型为Insert
,则告诉表视图在newIndexPath
处插入一行。 同样,如果类型为Delete
,则从表视图中删除indexPath
处的行。
如果记录已更新,则通过调用configureCell(_:atIndexPath:)
(一个接受ToDoCell
对象和NSIndexPath
对象的辅助方法)来更新表视图中的相应行。 我们稍后将实现此方法。
如果更改类型等于Move
,则删除indexPath
处的行,并在newIndexPath
处插入一行,以反映记录在获取的结果控制器的内部数据结构中的更新位置。
步骤4:实现UITableViewDataSource
协议
那不是太困难。 是吗 实现UITableViewDataSource
协议要容易得多,但是您需要注意一些事项。 让我们从numberOfSectionsInTableView(_:)
和tableView(_:numberOfRowsInSection:
。
即使示例应用程序中的表格视图只有一个部分,我们还是要向访存结果控制器询问部分的数目。 我们通过调用其上的sections
来完成此操作,该sections
返回符合NSFetchedResultsSectionInfo
协议的对象数组。
符合NSFetchedResultsSectionInfo
协议的对象需要实现一些方法和属性,包括numberOfObjects
。 这为我们提供了实现UITableViewDataSource
协议的前两个方法所需的内容。
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if let sections = fetchedResultsController.sections {
return sections.count
}
return 0
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let sections = fetchedResultsController.sections {
let sectionInfo = sections[section]
return sectionInfo.numberOfObjects
}
return 0
}
接下来是tableView(_:cellForRowAtIndexPath:)
和configureCell(_:atIndexPath:)
。 tableView(_:cellForRowAtIndexPath:)
很短,因为我们将大多数单元的配置移到configureCell(_:atIndexPath:)
。 我们向表视图询问具有重用标识符ReuseIdentifierToDoCell
的可重用单元格,并将该单元格和索引路径传递给configureCell(_:atIndexPath:)
。
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(ReuseIdentifierToDoCell, forIndexPath: indexPath) as! ToDoCell
// Configure Table View Cell
configureCell(cell, atIndexPath: indexPath)
return cell
}
魔术发生在configureCell(_:atIndexPath:)
。 我们在indexPath
处向获取的结果控制器询问该项目。 获取的结果控制器将NSManagedObject
实例返回给我们。 我们更新nameLabel
和状态doneButton
通过询问记录它的名称和done属性。
func configureCell(cell: ToDoCell, atIndexPath indexPath: NSIndexPath) {
// Fetch Record
let record = fetchedResultsController.objectAtIndexPath(indexPath)
// Update Cell
if let name = record.valueForKey("name") as? String {
cell.nameLabel.text = name
}
if let done = record.valueForKey("done") as? Bool {
cell.doneButton.selected = done
}
}
当我们从列表中删除项目时,我们将在本教程的后面部分重新讨论UITableViewDataSource
协议。 我们首先需要用一些数据填充表格视图。
7.添加记录
让我们通过添加创建待办事项的功能来结束本教程。 打开AddToDoViewController
类,为Core Data框架添加import语句,并声明NSManagedObjectContext!
类型的属性managedObjectContext
NSManagedObjectContext!
。
import UIKit
import CoreData
class AddToDoViewController: UIViewController, UITextFieldDelegate {
@IBOutlet weak var textField: UITextField!
var managedObjectContext: NSManagedObjectContext!
...
}
回到ViewController
类并实现prepareForSegue(_:sender:)
方法。 在此方法中,我们设置AddToDoViewController
实例的managedObjectContext
属性。 如果您以前使用过故事板,那么prepareForSegue(_:sender:)
应该很简单。
// MARK: -
// MARK: Prepare for Segue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SegueAddToDoViewController" {
if let navigationController = segue.destinationViewController as? UINavigationController {
if let viewController = navigationController.topViewController as? AddToDoViewController {
viewController.managedObjectContext = managedObjectContext
}
}
}
}
如果用户在AddToDoViewController
的文本字段中输入文本,然后点击“ 保存”按钮,我们将创建一个新记录,并用数据填充并保存它。 此逻辑进入我们之前创建的save(_:)
方法。
@IBAction func save(sender: AnyObject) {
let name = textField.text
if let isEmpty = name?.isEmpty where isEmpty == false {
// Create Entity
let entity = NSEntityDescription.entityForName("Item", inManagedObjectContext: self.managedObjectContext)
// Initialize Record
let record = NSManagedObject(entity: entity!, insertIntoManagedObjectContext: self.managedObjectContext)
// Populate Record
record.setValue(name, forKey: "name")
record.setValue(NSDate(), forKey: "createdAt")
do {
// Save Record
try record.managedObjectContext?.save()
// Dismiss View Controller
dismissViewControllerAnimated(true, completion: nil)
} catch {
let saveError = error as NSError
print("\(saveError), \(saveError.userInfo)")
// Show Alert View
showAlertWithTitle("Warning", message: "Your to-do could not be saved.", cancelButtonTitle: "OK")
}
} else {
// Show Alert View
showAlertWithTitle("Warning", message: "Your to-do needs a name.", cancelButtonTitle: "OK")
}
}
save(_:)
方法看起来非常令人印象深刻,但其中尚无我们尚未涉及的内容。 我们使用NSEntityDescription
实例和NSManagedObjectContext
实例创建一个新的托管对象。 然后,我们用名称和日期填充被管理对象。
如果成功保存了托管对象上下文,则关闭视图控制器,否则通过调用辅助方法showAlertWithTitle(_:message:cancelButtonTitle:)
显示警报。 如果用户点击保存按钮而不输入任何文本,我们也会显示警报。 在showAlertWithTitle(_:message:cancelButtonTitle:)
,我们创建,配置和展示一个UIAlertController
。
// MARK: -
// MARK: Helper Methods
private func showAlertWithTitle(title: String, message: String, cancelButtonTitle: String) {
// Initialize Alert Controller
let alertController = UIAlertController(title: title, message: message, preferredStyle: .Alert)
// Configure Alert Controller
alertController.addAction(UIAlertAction(title: cancelButtonTitle, style: .Default, handler: nil))
// Present Alert Controller
presentViewController(alertController, animated: true, completion: nil)
}
运行该应用程序并添加一些项目。 我确定您同意NSFetchedResultsController
类使添加项目的过程变得异常简单。 它负责监视托管对象上下文的更改,并根据NSFetchedResultsController
实例通过NSFetchedResultsControllerDelegate
协议告诉我们的内容,更新用户界面( ViewController
类的表视图)。
结论
在下一篇文章中,我们将通过添加删除和更新待办事项的功能来完成应用程序。 了解您在本文中讨论的概念很重要。 核心数据广播受管对象上下文状态更改的方式至关重要,因此请确保在继续之前先了解这一点。
翻译自: https://code.tutsplus.com/tutorials/core-data-and-swift-nsfetchedresultscontroller--cms-25072