本人有若干成套学习视频, 可试看! 可试看! 可试看, 重要的事情说三遍 包含Java
, 数据结构与算法
, iOS
, 安卓
, python
, flutter
等等, 如有需要, 联系微信tsaievan
.
NSFetchedResultsController与 UITableView可以说是天设一对,地造一双
有了NSFetchedResultsController,我们可以将NSFetchedResultsController中的数据作为 tableView 的数据源
今天,我们用NSFetchedResultsController与 UITableView做了一个通讯录,效果如下:
Demo的大致效果完成了大致的基本功能, 还有比如说,限制电话号码的格式之类的没有做,但这不是今天的重点
我们可以看出,在这个小 demo 中,只有三个控制器
- contactListVC (联系人列表控制器)
- contactAddVC(新增联系人控制器)
- contactEditVC(编辑联系人控制器)
其中,只有contactListVC (联系人列表控制器)是有 tableView的,所以我们的FetchedResultsController主要是配合这个 controller 来将数据显示在 tableView 上
ContactListVC (联系人列表控制器)的内容
- 要想使用NSFetchedResultsController,我们可以将NSFetchedResultsController作为 Controller的一个属性,利用懒加载对其进行初始化
初始化的方法中,有三个参数是必须的,
- 查询请求
- 管理对象上下文
- 分组依据
这三个参数告诉了FetchedResultsController这个控制器,从哪里,以什么样的方式去获取数据
#pragma mark *** 属性懒加载 ***
- (NSFetchedResultsController *)fetchedResultsController
{
if (!_fetchedResultsController) {
/**
fetchResultController 的初始化方法
* 参数1 :fetchRequest 查询请求
* 参数2 :管理对象上下文
* 参数3 :分组依据
* 参数4 :缓存名
*/
/* 先初始化一个查询请求 */
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Contact"];
/* 给查询请求设置排序器,参数是排序依据,如果不设置,程序会崩溃 */
request.sortDescriptors = @[ [NSSortDescriptor sortDescriptorWithKey:@"namePinYin" ascending:YES] ];
_fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:request managedObjectContext:kYFCoreDataManager.managedObjectContext sectionNameKeyPath:@"sectionName" cacheName:nil];
/* 设置 fetchedResultsController 的代理 */
_fetchedResultsController.delegate = self;
/* 执行查询 */
[_fetchedResultsController performFetch:nil];
/* 执行查询之后刷新 tableView */
[self.tableView reloadData];
}
return _fetchedResultsController;
}
- 其次,我们设定视图控制器为FetchedResultsController的代理
其中FetchedResultsController的代理方法是
-(void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(nullable NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(nullable NSIndexPath *)newIndexPath
只要是 controller 中的数据元素发生变化,就一定会走这个方法,
- 改变的类型主要有四种方式:
- 增加数据
- 删除数据
- 移动数据
- 改变数据
在这个方法中,对 tableView进行适时的刷新,无论是新增数据,还是删除数据,都能立即更新到 tableView 上
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(nullable NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(nullable NSIndexPath *)newIndexPath
{
/* 在这个方法中,你需要判断新增加的数据是组还是行 */
/* 先获取到当前tableView 的组数,以及数据库中的组数 */
NSInteger tableViewSections = self.tableView.numberOfSections;
NSInteger fetchSections = self.fetchedResultsController.sections.count;
/* switch 语句来判断 fetchController 中的 object 改变类型 */
/*
* 一共有四种类型
* NSFetchedResultsChangeInsert 插入数据
* NSFetchedResultsChangeDelete 删除数据
* NSFetchedResultsChangeMove 移动数据
* NSFetchedResultsChangeUpdate 更新数据
*/
switch (type) {
/* 新增的本质就是 tableView 新加数据 */
case NSFetchedResultsChangeInsert:
[self.tableView beginUpdates];
/* 如果 tableViewSections 和 fetchSections 的数量不一致,则表明增加的是组*/
if (tableViewSections != fetchSections) {
/* 注意,是在新的 newIndexPath中插入组 */
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:newIndexPath.section] withRowAnimation:UITableViewRowAnimationTop];
}
/* else 表示相等,表明新增的是组 */
else
{
/* 也是在 newIndexPath 中插入行 */
[self.tableView insertRowsAtIndexPaths:@[ newIndexPath ] withRowAnimation:UITableViewRowAnimationTop];
}
[self.tableView endUpdates];
break;
/* 删除的本质就是 tableView 删除数据 */
case NSFetchedResultsChangeDelete:
[self.tableView beginUpdates];
/* 判断逻辑和新增数据一样,如果组数不想等,则表明新增的是组 */
if (tableViewSections != fetchSections) {
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:newIndexPath.section] withRowAnimation:UITableViewRowAnimationTop];
}
/* 如果组数相等,则新增的是行 */
else
{
[self.tableView deleteRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationTop];
}
[self.tableView endUpdates];
break;
/* 移动(如果数据的排序发生变化,则走移动) */
case NSFetchedResultsChangeMove:
/* 直接刷新数据 */
[self.tableView reloadData];
break;
/* 更新(如果数据的排序未发生变化,只是其他发生变化,则走更新) */
case NSFetchedResultsChangeUpdate:
[self.tableView beginUpdates];
/* 只刷新改变的那一行代码,因此是旧的 indexPath */
[self.tableView reloadRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationTop];
[self.tableView endUpdates];
break;
default:
break;
}
}
此时, tableView的数据源方法就可以直接从fetchedResultsController拿数据就好了
#pragma mark *** Table view data source数据源方法 ***
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
/* 在你使用了 fetchResultsController 之后,返回的组的数量就是从 fetchResultsController 中来获取 */
return self.fetchedResultsController.sections.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
/* self.fetchedResultsController.sections的返回值是NSArray<id<NSFetchedResultsSectionInfo>,那么用 section 去取数组中的元素就可以得到这个数组中的元素 */
/* 这表明这个 id 类型的对象是遵循NSFetchedResultsSectionInfo协议的 */
id <NSFetchedResultsSectionInfo> infos = self.fetchedResultsController.sections[section];
/* 因而这个 id 类型的对象有 numberOfObjects 这个方法 */
return [infos numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ContactListVCCell" forIndexPath:indexPath];
Contact *contact = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = contact.name;
cell.detailTextLabel.text = contact.phoneNum;
return cell;
}
ContactAddVC(新增联系人控制器)的代码内容
主要的代码就是利用 CoreData 框架来新增数据,具体步骤就再复习一遍
- 先用实体描述器初始化对象
- 给对象的属性赋值,值是从 textField 中来的
- 将对象保存在数据库中
从数据库中获取数据的工作就不用这个控制器来做了,而是交给了fetchResultsController来做,只要数据库的对象发生了变化,就会走fetchResultsController的代理,来实现刷新 tableView 的功能
代码如下:
#pragma mark *** 处理按钮的点击事件 ***
// -------- 点击右边的保存按钮触发的事件 --------
- (IBAction)rightBarButtonItemClickAction:(UIBarButtonItem *)sender {
// -------- 数据保存(数据插入的三个步骤) --------
/* 1. 实体描述器初始化模型对象 */
Contact *contact = [NSEntityDescription insertNewObjectForEntityForName:@"Contact" inManagedObjectContext:kYFCoreDataManager.managedObjectContext];
/* 2. 给模型对象的属性赋值 */
contact.name = self.nameTextField.text;
contact.phoneNum = self.phoneNumTextField.text;
contact.namePinYin = [CommonTool getPinYinFromString:contact.name];
// 先取拼音,再取首字母,再大写
contact.sectionName = [[contact.namePinYin substringToIndex:1] uppercaseString];
[kYFCoreDataManager save];
[self.navigationController popViewControllerAnimated:YES];
}
ContactEditVC(编辑联系人控制器)的代码内容
和ContactAddVC的实现逻辑基本相同,修改数据库的操作也只有三步:
- 拿到数据模型对象
在ContactListVC 中,跳转界面的时候把 contac模型对象传给目标控制器
#pragma mark *** 跳转界面执行的方法 ***
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:@"ContactEditVC"]) {
ContactEditVC *contactEditVC = (ContactEditVC *)[segue destinationViewController];
contactEditVC.contact = [self.fetchedResultsController objectAtIndexPath:self.tableView.indexPathForSelectedRow];
}
}
2.直接对模型对象属性进行修改
3.保存
#pragma mark *** 视图生命周期 ***
- (void)viewDidLoad {
[super viewDidLoad];
self.nameTextField.text = self.contact.name;
self.phoneNumTextField.text = self.contact.phoneNum;
}
#pragma mark *** 点击 nacvigationBarButtonItem 触发的对象 ***
- (IBAction)rightBarButtonItemClickAction:(UIBarButtonItem *)sender {
// -------- 相当于在数据库中修改数据 --------
/* 直接对模型对象的属性进行修改 */
self.contact.name = self.nameTextField.text;
self.contact.phoneNum = self.phoneNumTextField.text;
self.contact.sectionName = [[self.contact.namePinYin substringToIndex:1] uppercaseString];
self.contact.namePinYin = [CommonTool getPinYinFromString:self.contact.name];
/* 修改结束后进行保存 */
[kYFCoreDataManager save];
/* 然后 pop 掉当前控制器,返回到上一层控制器 */
[self.navigationController popViewControllerAnimated:YES];
}
网友评论