美文网首页
iOS开发之UI篇(10)—— UITableView

iOS开发之UI篇(10)—— UITableView

作者: 看影成痴 | 来源:发表于2017-12-22 15:26 被阅读69次

    版本
    Xcode 9.1

    一、基本概念

    UITableView : UIScrollView : UIView : UIResponder : NSObject
    

    从继承关系我们可以知道:UITableView可以滚动,具有view的性质,能响应UI事件等等~

    1. UITableView结构:

    UITableView可以有很多个section(本文称为组),每个section分别由一个Header(组头)、一个Fooder(组尾)及若干个cell组成。

    UITableView结构图

    2. UITableView有两种风格:

    typedef NS_ENUM(NSInteger, UITableViewStyle) {
        UITableViewStylePlain,          // regular table view
        UITableViewStyleGrouped         // preferences style table view
    };
    

    主要区别为:

    • 当没有设置Header(或Fooder)时,Plain风格会隐藏掉Header(或Fooder),而Grouped仍会显示一个默认高度值。
    • Header(或Fooder)中的字体样式稍有不同。
    • 以前版本来说,使用Plain布局会排到顶部的状态栏,但是新版就没有这个问题了。

    3. UITableViewCell有四种风格

    typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
        UITableViewCellStyleDefault,    // Simple cell with text label and optional image view (behavior of UITableViewCell in iPhoneOS 2.x)
        UITableViewCellStyleValue1,     // Left aligned label on left and right aligned label on right with blue text (Used in Settings)
        UITableViewCellStyleValue2,     // Right aligned label on left with blue text and left aligned label on right (Used in Phone/Contacts)
        UITableViewCellStyleSubtitle    // Left aligned label on top and left aligned label on bottom with gray text (Used in iPod).
    };
    

    这四种风格分别对应storyboard/XIB里面的设置:

    使用storyboard/XIB设置四种风格

    每种风格对应效果图如下:

    UITableViewCellStyleDefault UITableViewCellStyleValue1 UITableViewCellStyleValue2 UITableViewCellStyleSubtitle

    如果你的UITableViewCell不显示image或者detailTextLabel,那么最好先检查一下注册的时候设置的style是否符合。

    4. 重用cell

    为什么要重用cell?
    由于屏幕显示的cell有限,而当数据量大的时候,如果每个数据创建一个Cell,就会占很大内存。
    重用cell机制是这样的:创建了屏幕内cell所需的个数+1个屏幕之外的cell,在满足显示效果的前提下尽可能降低消耗内存。
    这种机制下默认有一个可变数组NSMutableArray* visiableCells用来保存当前显示的cell;一个可变字典NSMutableDictnery* reusableTableCells用来保存可重复利用的cell(之所以用字典是因为可重用的cell有不止一种样式,我们需要根据它的reuseIdentifier,也就是所谓的重用标示符来查找是否有可重用的该样式的cell)。

    如何重用cell?
    首先要创建一个有重用标识符reuseIdentifier的cell,这些cell会被保存到重用队列中去。当需要实例化cell的时候,首先根据reuseIdentifier去找对应的cell。
    加载cell到重用队列有两种方法:

    // 法1:使用tableView注册一个cell
    [_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"myCell"];
    
    // 法2:在cell的配置代理方法中创建一个cell
    if (cell == nil) {
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"];
    }
    

    根据重用标识符reuseIdentifier实例化一个cell:

    // 实例化一个UITableViewCell
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"myCell" forIndexPath:indexPath];
    

    二、简单用法

    效果图:

    代码比较简单,基本上都有注释,直接上完整代码:

    #define     TABLE_WIDTH      [UIScreen mainScreen].bounds.size.width
    #define     TABLE_HEIGHT     [UIScreen mainScreen].bounds.size.height
    
    #import "ViewController.h"
    
    @interface ViewController () <UITableViewDelegate, UITableViewDataSource>
    
    @property (nonatomic, strong) UITableView *tableView;
    
    @property (nonatomic, strong, nullable) NSArray *digitalArr;
    @property (nonatomic, strong, nullable) NSArray *letterArr;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        // 装载数据源
        self.digitalArr = @[@"0", @"1", @"2"];
        self.letterArr  = @[@"A", @"B", @"C"];
        
        // 添加tableView到self.view
        [self.view addSubview:self.tableView];
    }
    
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    
    #pragma mark - UITableViewDelegate
    
    // cell高度
    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
        
        return 60;
    }
    
    
    // Header高度
    - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
    
        return 30;
    }
    
    
    // Footer高度
    - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
    
        return 30;
    }
    
    
    // 选中了某个cell
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
        
        
    }
    
    
    #pragma mark - UITableViewDataSource
    
    // Section数量
    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
        
        return 2;
    }
    
    
    // 对应Section中cell的个数
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
        
        switch (section) {
            case 0:     // 数字组
                return self.digitalArr.count;
                break;
                
            case 1:     // 字母组
                return self.letterArr.count;
                break;
                
            default:
                break;
        }
        
        return 0;
    }
    
    
    // Header标题
    - (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    
        switch (section) {
    
            case 0:     // 数字组
                return @"数字组头";
                break;
    
            case 1:     // 字母组
                return @"字母组头";
                break;
    
            default:
                break;
        }
    
        return nil;
    }
    
    
    // Footer标题
    - (nullable NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
    
        switch (section) {
    
            case 0:     // 数字组
                return @"数字组尾";
                break;
    
            case 1:     // 字母组
                return @"字母组尾";
                break;
    
            default:
                break;
        }
    
        return nil;
    }
    
    
    // cell
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        
        // 实例化一个UITableViewCell
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"myCell" forIndexPath:indexPath];
        // 由于已经注册过重用cell,所以这里不必判断是否存在
    //    if (cell == nil) {
    //        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"];
    //    }
        
        // 设置cell相关属性
        switch (indexPath.section) {
                
            case 0:     // 数字组
                if (indexPath.row < self.digitalArr.count) {
                    cell.textLabel.text = self.digitalArr[indexPath.row];
                }
                break;
                
            case 1:     // 字母组
                if (indexPath.row < self.letterArr.count) {
                    cell.textLabel.text = self.letterArr[indexPath.row];
                }
                break;
                
            default:
                break;
        }
        
        // 返回cell
        return cell;
    }
    
    
    #pragma mark - 懒加载
    
    - (UITableView *)tableView {
        
        if (_tableView == nil) {
            // 实例化一个UITableView
            _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, TABLE_WIDTH, TABLE_HEIGHT)
                                                      style:UITableViewStylePlain];
            // 注册一个cell
            [_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"myCell"];
            // 设置代理
            _tableView.delegate = self;
            _tableView.dataSource = self;
        }
        
        return _tableView;
    }
    
    
    @end
    

    三、cell的增、减、排序

    当然,这里要说的操作并不是先改变数据源然后reloadData,而是通过对cell的操作,使整个过程具有动画效果、显得顺畅。

    1. 添加

    添加

    我们会新增两个代理,第一个是返回编辑操作的类型。有插入类型和删除类型。
    先定义一个全局变量_isDelete用于标记是点击添加还是删除按钮:

    - (IBAction)btnAdd:(UIButton *)sender {
        
        _isDelete = NO;     // 编辑类型:添加
        // 启动编辑(参数一:tableView是否正在编辑;参数二:是否动画)
        [self.tableView setEditing:!self.tableView.isEditing animated:YES];
    }
    

    调用方法setEditing: animated:后,接着来到代理方法:

    // 返回操作类型(添加/删除)
    - (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
        
        NSLog(@"返回操作类型");
        if (_isDelete) {
            return UITableViewCellEditingStyleDelete;
        }
        return UITableViewCellEditingStyleInsert;
    }
    

    当点击左边绿色“+”号按钮后,调用此代理方法:

    // 提交编辑操作
    - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
        
        NSLog(@"提交编辑操作");
        if (editingStyle == UITableViewCellEditingStyleInsert) {
            switch (indexPath.section) {
                    
                case 0:
                    [self.digitalArr insertObject:@"我是新插入的数字" atIndex:indexPath.row];
                    break;
                    
                case 1:
                    [self.letterArr insertObject:@"我是新插入的字母" atIndex:indexPath.row];
                    break;
                    
                default:
                    break;
            }
            // 与reloadData不同,此方法带动画效果.(参数二为动画类型)
            [tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft];
        }
    }
    

    2. 删除

    与添加类似,假如我们设置_isDelete = YES,那么代理返回的是UITableViewCellEditingStyleDelete类型,这时删除是这样子的:

    删除

    你也不喜欢这种做法对不对?那我们换个做法,直接左滑删除某个cell或者点编辑多选/全选然后批量删除cell。

    滑动删除
    其实,只要你返回UITableViewCellEditingStyleDelete类型,并实现代理方法tableView : commitEditingStyle: forRowAtIndexPath:,然后就可以向左滑动调出删除按钮了。但是这样一来点击添加按钮就不能调出绿色“+”号按钮了,要解决这个问题,需要修改之前的返回类型的代理方法:

    // 返回操作类型(添加/删除)
    - (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
        
        if (!tableView.isEditing) {
            return UITableViewCellEditingStyleDelete;
        }
        
        return UITableViewCellEditingStyleInsert;
    }
    
    // 提交编辑操作
    - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
        
        NSLog(@"提交编辑操作");
        if (editingStyle == UITableViewCellEditingStyleInsert) {
            switch (indexPath.section) {
                    
                case 0:
                    [self.digitalArr insertObject:@"我是新插入的数字" atIndex:indexPath.row];
                    break;
                    
                case 1:
                    [self.letterArr insertObject:@"我是新插入的字母" atIndex:indexPath.row];
                    break;
                    
                default:
                    break;
            }
            // 与reloadData不同,此方法带动画效果.(参数二为动画类型)
            [tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft];
            
        }else if (editingStyle == UITableViewCellEditingStyleDelete) {
            
            switch (indexPath.section) {
                    
                case 0:
                    if (indexPath.row < self.digitalArr.count) {
                        [self.digitalArr removeObjectAtIndex:indexPath.row];
                    }
                    break;
                    
                case 1:
                    if (indexPath.row < self.letterArr.count) {
                        [self.letterArr removeObjectAtIndex:indexPath.row];
                    }
                    break;
                    
                default:
                    break;
            }
            // 与reloadData不同,此方法带动画效果.(参数二为动画类型)
            [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft];
        }
    }
    
    左滑单个删除

    批量删除
    改的地方比较多,直接上完整代码了。要注意的是,当section中没有cell时该section也应该删掉,本代码就不演示了。

    #define     TABLE_WIDTH      [UIScreen mainScreen].bounds.size.width
    #define     TABLE_HEIGHT     [UIScreen mainScreen].bounds.size.height
    
    #import "ViewController.h"
    
    @interface ViewController () <UITableViewDelegate, UITableViewDataSource> {
        
        BOOL    _isDelete;      // 用于区别是添加还是删除操作
        NSMutableArray *_deleteArr;     // 用于储存将要删除的cell的indexPath
    }
    
    @property (weak, nonatomic) IBOutlet UIButton *editBtn;
    @property (weak, nonatomic) IBOutlet UIButton *addBtn;
    @property (weak, nonatomic) IBOutlet UIView *bottomView;
    @property (weak, nonatomic) IBOutlet UIButton *allBtn;
    @property (weak, nonatomic) IBOutlet UIButton *deleteBtn;
    
    @property (nonatomic, strong) UITableView *tableView;
    
    @property (nonatomic, strong, nullable) NSMutableArray *digitalArr;
    @property (nonatomic, strong, nullable) NSMutableArray *letterArr;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        _isDelete = YES;     // 编辑类型:删除
        self.bottomView.hidden = YES;       //隐藏底部view
        self.deleteBtn.hidden = YES;        //隐藏删除按钮
        
        // 装载数据源
        self.digitalArr = [@[@"0", @"1", @"2"] mutableCopy];
        self.letterArr  = [@[@"A", @"B", @"C"] mutableCopy];
        
        // 添加tableView到self.view
        [self.view addSubview:self.tableView];
    }
    
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    
    #pragma mark - UITableViewDelegate
    
    // cell高度
    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
        
        return 60;
    }
    
    
    // Header高度
    - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
    
        return 30;
    }
    
    
    // Footer高度
    - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
    
        return 30;
    }
    
    
    // 选中了某个cell
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
        
        // 判断是否是多选操作
        if (self.tableView.editing) {
            if (_deleteArr == nil) {
                _deleteArr = [NSMutableArray new];
            }
            // 将选中cell的indexPath添加到删除数组deleteArr
            [_deleteArr addObject:indexPath];
            // 显示删除按钮
            self.deleteBtn.hidden = NO;
            return;
        }
    }
    
    
    // 取消选中时 将存放在deleteArr中的数据移除
    - (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath {
        
        // 判断是否是多选操作
        if (self.tableView.editing) {
            //将取消选中的cell的indexPath从数组deleteArr中移除
            [_deleteArr removeObject:indexPath];
            //隐藏删除按钮
            if ([_deleteArr count] == 0) {
                _deleteBtn.hidden = YES;
            }
        }
    }
    
    
    // 返回操作类型(添加/删除)
    - (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
        
        NSLog(@"返回操作类型");
        if (!tableView.isEditing) {
            return UITableViewCellEditingStyleDelete;
        }
        
        return UITableViewCellEditingStyleInsert;
    }
    
    
    // 提交编辑操作
    - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
        
        NSLog(@"提交编辑操作");
        if (editingStyle == UITableViewCellEditingStyleInsert) {
            switch (indexPath.section) {
                    
                case 0:
                    [self.digitalArr insertObject:@"我是新插入的数字" atIndex:indexPath.row];
                    break;
                    
                case 1:
                    [self.letterArr insertObject:@"我是新插入的字母" atIndex:indexPath.row];
                    break;
                    
                default:
                    break;
            }
            // 与reloadData不同,此方法带动画效果.(参数二为动画类型)
            [tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft];
            
        }else if (editingStyle == UITableViewCellEditingStyleDelete) {
            
            switch (indexPath.section) {
                    
                case 0:
                    if (indexPath.row < self.digitalArr.count) {
                        [self.digitalArr removeObjectAtIndex:indexPath.row];
                    }
                    break;
                    
                case 1:
                    if (indexPath.row < self.letterArr.count) {
                        [self.letterArr removeObjectAtIndex:indexPath.row];
                    }
                    break;
                    
                default:
                    break;
            }
            // 与reloadData不同,此方法带动画效果.(参数二为动画类型)
            [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft];
        }
    }
    
    
    #pragma mark - UITableViewDataSource
    
    // Section数量
    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
        
        return 2;
    }
    
    
    // 对应Section中cell的个数
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
        
        switch (section) {
            case 0:     // 数字组
                return self.digitalArr.count;
                break;
                
            case 1:     // 字母组
                return self.letterArr.count;
                break;
                
            default:
                break;
        }
        
        return 0;
    }
    
    
    // Header标题
    - (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    
        switch (section) {
    
            case 0:     // 数字组
                return @"数字组头";
                break;
    
            case 1:     // 字母组
                return @"字母组头";
                break;
    
            default:
                break;
        }
    
        return nil;
    }
    
    
    // Footer标题
    - (nullable NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
    
        switch (section) {
    
            case 0:     // 数字组
                return @"数字组尾";
                break;
    
            case 1:     // 字母组
                return @"字母组尾";
                break;
    
            default:
                break;
        }
    
        return nil;
    }
    
    
    // cell
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        
        // 实例化一个UITableViewCell
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"myCell" forIndexPath:indexPath];
        // 由于已经注册过重用cell,所以这里不必判断是否存在
    //    if (cell == nil) {
    //        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"];
    //    }
        
        // 设置cell相关属性
        switch (indexPath.section) {
                
            case 0:     // 数字组
                if (indexPath.row < self.digitalArr.count) {
                    cell.textLabel.text = self.digitalArr[indexPath.row];
                }
                break;
                
            case 1:     // 字母组
                if (indexPath.row < self.letterArr.count) {
                    cell.textLabel.text = self.letterArr[indexPath.row];
                }
                break;
                
            default:
                break;
        }
        
        // 返回cell
        return cell;
    }
    
    
    #pragma mark - UI事件
    
    - (IBAction)btnEdit:(UIButton *)sender {
        
        // 如果当前有别人的编辑操作,取消别人的编辑操作,并退出
        if (self.tableView.isEditing && !sender.selected) {
            // 启动编辑(参数一:tableView是否正在编辑;参数二:是否动画)
            [self.tableView setEditing:!self.tableView.isEditing animated:YES];
            return;
        }
        
        // 编辑时显示底部view、不显示添加按钮
        sender.selected = !sender.selected;
        if (sender.selected) {
            self.addBtn.hidden = YES;
            self.deleteBtn.hidden = YES;
            self.deleteBtn.selected = NO;
            self.allBtn.selected = NO;
            self.bottomView.hidden = NO;
        }else {
            // 将要隐藏底部view时,还原各种btn
            self.addBtn.hidden = NO;
            self.deleteBtn.hidden = YES;
            self.deleteBtn.selected = NO;
            self.allBtn.selected = NO;
            self.bottomView.hidden = YES;
        }
    
        // 支持同时选中多行
        self.tableView.allowsMultipleSelectionDuringEditing = YES;
        // 启动编辑(参数一:tableView是否正在编辑;参数二:是否动画)
        [self.tableView setEditing:!self.tableView.isEditing animated:YES];
        
        
        
    }
    
    
    - (IBAction)btnAdd:(UIButton *)sender {
        
        // 如果当前有别人的编辑操作,取消别人的编辑操作,并退出
        if (self.tableView.isEditing && !sender.selected) {
            // 启动编辑(参数一:tableView是否正在编辑;参数二:是否动画)
            [self.tableView setEditing:!self.tableView.isEditing animated:YES];
            return;
        }
        
        // 编辑时显示底部view、不显示添加按钮
        sender.selected = !sender.selected;
        if (sender.selected) {
            self.editBtn.hidden = YES;
        }else {
            self.editBtn.hidden = NO;
        }
        
        _isDelete = NO;     // 编辑类型:添加
        // 取消支持同时选中多行
        self.tableView.allowsMultipleSelectionDuringEditing = NO;
        // 启动编辑(参数一:tableView是否正在编辑;参数二:是否动画)
        [self.tableView setEditing:!self.tableView.isEditing animated:YES];
    }
    
    - (IBAction)btnAll:(UIButton *)sender {
        
        sender.selected = !sender.selected;
        
        if (sender.selected) {
            // 遍历选中数字cell
            for (int i=0; i<self.digitalArr.count; i++) {
                NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
                // 选中TableView中的cell
                [self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionTop];
                // 将选中的cell的indexPath存入数组deleteArr
                if (_deleteArr == nil) {
                    _deleteArr = [NSMutableArray new];
                }
                [_deleteArr addObject:indexPath];
            }
            // 遍历选中字母cell
            for (int i=0; i<self.letterArr.count; i++) {
                NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:1];
                // 选中TableView中的cell
                [self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionTop];
                // 将选中的cell的indexPath存入数组deleteArr
                if (_deleteArr == nil) {
                    _deleteArr = [NSMutableArray new];
                }
                [_deleteArr addObject:indexPath];
            }
            // 显示删除按钮
            self.deleteBtn.hidden = NO;
            
        }else {
            // 遍历取消选中数字cell
            for (int i=0; i<self.digitalArr.count; i++) {
                NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
                //取消选中TableView中的cell
                [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
            }
            // 遍历取消选中字母cell
            for (int i=0; i<self.letterArr.count; i++) {
                NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:1];
                //取消选中TableView中的cell
                [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
            }
            // 移除数组deleteArr中所有元素(indexPath)
            if (_deleteArr) {
                [_deleteArr removeAllObjects];
            }
            //隐藏删除按钮
            self.deleteBtn.hidden = YES;
        }
    }
    
    
    - (IBAction)btnDelete:(UIButton *)sender {
        
        if (self.tableView.isEditing && _deleteArr) {
            // 此处运用i--是为了防止removeObjectAtIndex出错
            for (NSInteger i=_deleteArr.count-1; i>=0; i--) {
                NSIndexPath *indexPath = _deleteArr[i];
                // 数据源同步删除
                switch (indexPath.section) {
                        
                    case 0:     // 数字
                        if (indexPath.row < self.digitalArr.count) {
                            [self.digitalArr removeObjectAtIndex:indexPath.row];
                        }
                        break;
                        
                    case 1:     // 字母
                        if (indexPath.row < self.letterArr.count) {
                            [self.letterArr removeObjectAtIndex:indexPath.row];
                        }
                        break;
                        
                    default:
                        break;
                }
                // 与reloadData不同,此方法带动画效果.(参数二为动画类型)
                [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft];
            }
            [_deleteArr removeAllObjects];
        }
    }
    
    
    #pragma mark - 懒加载
    
    - (UITableView *)tableView {
        
        if (_tableView == nil) {
            // 实例化一个UITableView
            _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 64, TABLE_WIDTH, TABLE_HEIGHT-64-46)
                                                      style:UITableViewStylePlain];
            // 注册一个cell
            [_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"myCell"];
            // 设置代理
            _tableView.delegate = self;
            _tableView.dataSource = self;
        }
        
        return _tableView;
    }
    
    
    @end
    
    批量删除

    3. 排序

    和增加、删除相似,要实现排序功能只需实现下面这个协议即可:

    /**
     移动cell
    
     @param tableView 正在操作的tableView对象
     @param sourceIndexPath 移动前的indexPath
     @param destinationIndexPath 移动后的indexPath
     */
    - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath {
        
        // 需要改变数据源的排序(略)
    }
    
    排序

    四、几种方法创建cell

    常用cell的创建方法有两种,一种是使用系统cell,另一种是自定义cell。其中自定义cell又可以分为三种:纯代码创建cell,XIB创建cell,storyboard创建cell。

    1. 使用系统cell
    2. 自定义cell之纯代码
    3. 自定义cell之XIB
    4. 自定义cell之storyboard

    1. 使用系统cell

    其实我们上面例子中使用的就是系统cell:通过注册一个UITableViewCell,然后在tableView: cellForRowAtIndexPath:方法里返回一个已经注册到重用队列里的UITableViewCell。

    // 注册一个cell
    [_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"myCell"];
    
    // 在tableView: cellForRowAtIndexPath:方法里实例化一个UITableViewCell,并返回
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"myCell" forIndexPath:indexPath];
    return cell;
    

    2. 自定义cell之纯代码

    首先,我们先新建一个继承自UITableViewCell的类MyTableViewCell:

    新建一个MyTableViewCell

    然后倒入头文件#import "MyTableViewCell.h",接下来将上面的系统cell改为我们新建的自定义cell就好了:

    // 注册一个cell
    [_tableView registerClass:[MyTableViewCell class] forCellReuseIdentifier:@"myCell"];
    
    // 在tableView: cellForRowAtIndexPath:方法里实例化一个MyTableViewCell,并返回
    MyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"myCell" forIndexPath:indexPath];
    return cell;
    

    我们可以重写一下cell,来验证是否是使用自定义cell。因为cell继承自UIView,因此我们可以加入如下方法:

    - (void)drawRect:(CGRect)rect {
        
        self.backgroundColor = [UIColor redColor];
    }
    

    运行结果:

    纯代码创建cell

    3. 自定义cell之XIB

    和纯代码创建类似,只需要在创建的时候勾选创建XIB文件:

    使用XIB创建cell

    然后我们就会看到XIB里面cell已经自动关联到类MyXibTableViewCell了:

    XIB的类名

    当然,如果我们创建的时候不勾选Also create XIB file选项,也是可以后续单独创建XIB的,只是创建好的XIB要手动关联到我们自定义cell的类名。
    接下来,我们为XIB的cell起一个标识符:

    XIB中cell的标识符

    最后,我们在注册cell的时候,使用这个标识符找到XIB中的cell,从而创建使用。

    // 注册一个cell
    [_tableView registerNib:[UINib nibWithNibName:@"MyXibTableViewCell" bundle:nil] forCellReuseIdentifier:@"MyXibTableViewCell"];
    

    在tableView: cellForRowAtIndexPath:方法里实例化一个MyXibTableViewCell,并返回:

    // 返回MyXibTableViewCell
    - (MyXibTableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        
        // 实例化一个MyXibTableViewCell
        MyXibTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyXibTableViewCell"];
        
        // 设置cell相关属性
        
        // 返回cell
        return cell;
    }
    

    为了验证是否使用XIB的cell,设置cell的背景颜色:

    设置XIB中cell的背景颜色

    运行结果:

    XIB创建cell

    4. 自定义cell之storyboard

    先在Main.storyboard里面创建一个tableview和一个cell:

    Main.storyboard

    设置tableview的dalegate和datasource:

    设置dalegate和datasource

    设置类别:

    class

    设置id:

    id

    最后,我们可以省掉注册cell这一步(因为系统会自动帮我们注册,噢耶~),直接在tableView: cellForRowAtIndexPath:方法里实返回MyStoryboardTableViewCell

    // cell
    - (MyStoryboardTableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        
        // 实例化一个MyStoryboardTableViewCell
        MyStoryboardTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyStoryboardTableViewCell"];
        
        // 设置cell相关属性
        if (indexPath.row < self.digitalArr.count) {
            cell.textLabel.text = self.digitalArr[indexPath.row];
        }
        
        // 返回cell
        return cell;
    }
    

    设置storyboard里cell的背景颜色:

    设置背景颜色

    运行结果:

    storyboard创建cell

    五、自定义Header/Footer

    如同自定义cell,上面的代码中返回cell的时候返回我们自己创建的cell就好了。对于Header/Footer也一样,我们首先要分别实现两个代理方法:一个返回高度,一个返回内容(UIView类型)。

    // Header高度
    - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
    
        return 100;
    }
    
    
    // Footer高度
    - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
    
        return 30;
    }
    
    
    // 自定义Header
    - (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    
        UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, TABLE_WIDTH, 100)];
        
        UIImageView *imageView = [[UIImageView alloc] initWithFrame:view.bounds];
        imageView.image = [UIImage imageNamed:@"mas091"];
        [view addSubview:imageView];
        
        return view;
    }
    
    
    // 自定义Footer
    - (nullable UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
    
        UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, TABLE_WIDTH, 30)];
        view.backgroundColor = [UIColor blackColor];
        
        return view;
    }
    

    效果图:

    自定义Header/Footer

    相关文章

      网友评论

          本文标题:iOS开发之UI篇(10)—— UITableView

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