NSFetchedResultsController與 UITableView 的天作之和

本人有若干成套學習視頻, 可試看! 可試看! 可試看, 重要的事情說三遍 包含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的一個屬性,利用懶加載對其進行初始化
初始化的方法中,有三個參數是必須的,
  1. 查詢請求
  1. 管理對象上下文
  2. 分組依據

這三個參數告訴了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 框架來新增數據,具體步驟就再復習一遍

  1. 先用實體描述器初始化對象
  1. 給對象的屬性賦值,值是從 textField 中來的
  2. 將對象保存在數據庫中
    從數據庫中獲取數據的工作就不用這個控制器來做了,而是交給了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的實現邏輯基本相同,修改數據庫的操作也只有三步:

  1. 拿到數據模型對象
    在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];
}
以上就是這個小 demo 的全部內容,感謝大家的支持!

PS. 本人有若干成套學習視頻, 包含Java, 數據結構與算法, iOS, 安卓, python, flutter等等, 如有需要, 聯系微信tsaievan.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容