美文网首页
NSFetchedResultsController与 UITa

NSFetchedResultsController与 UITa

作者: 小冰山口 | 来源:发表于2016-10-01 00:24 被阅读0次

    本人有若干成套学习视频, 可试看! 可试看! 可试看, 重要的事情说三遍 包含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.

    相关文章

      网友评论

          本文标题:NSFetchedResultsController与 UITa

          本文链接:https://www.haomeiwen.com/subject/atdryttx.html