本人有若干成套學習視頻, 可試看! 可試看! 可試看, 重要的事情說三遍 包含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];
}