美文网首页iOS tableview
UITableView表视图学习笔记(摘自传智播客)

UITableView表视图学习笔记(摘自传智播客)

作者: 寻心_0a46 | 来源:发表于2019-03-04 21:54 被阅读0次

    UITableView

    UITableView是作为IOS中显示数据列表最常用的一个控件,继承UIScrollView,支持垂直滚动。拥有两种内置的样式,UITableViewStylePlain普通样式与UITableViewStyleGrouped分组样式。

    注:如果UITableView的style设置为UITableViewStyleGrouped样式,那么组头和组尾视图没有悬停效果。相反,如果UITableView的style设置为UITableViewStylePlain样式,那么组头和组尾视图会有悬停效果。

    普通样式: 屏幕快照 2019-02-28 下午7.51.21.png 分组样式: 屏幕快照 2019-02-28 下午7.51.35.png

    常用属性

    @property (nonatomic, readonly) UITableViewRowActionStyle style;

    属性描述 : 设置表格样式。此属性的值在创建时设置,以后无法更改。

    @property (nonatomic, readonly) UITableViewRowActionStyle style;
    

    @property (nonatomic, weak, nullable) id <UITableViewDataSource> dataSource;

    属性描述 :设置充当表视图的数据源的对象。数据源对象必须采用UITableViewDataSource协议。

    @property (nonatomic, weak, nullable) id <UITableViewDataSource> dataSource;
    

    @property (nonatomic, weak, nullable) id <UITableViewDelegate> delegate;

    属性描述 : 设置充当表视图代理的对象。代理对象必须采用UITableViewDelegate协议。

    @property (nonatomic, weak, nullable) id <UITableViewDelegate> delegate;
    

    @property (nonatomic, getter=isEditing) BOOL editing;

    属性描述 : 确定表视图是否处于编辑模式的布尔值。

    当此属性的值为“是”时,表格视图处于编辑模式:表格的单元格可能在每个单元格的左侧显示一个插入或删除控件,在右侧显示一个重新排序控件,具体取决于单元格的配置方式。点击控件将导致表视图调用数据源方法table view:committedingstyle:forRowAtIndexPath:。默认值为NO。

    @property (nonatomic, getter=isEditing) BOOL editing;   
    

    @property (nonatomic) UITableViewCellSeparatorStyle separatorStyle API_UNAVAILABLE(tvOS);

    属性描述 :设置分割表视图单元格的样式。这个属性的值是UITableViewCell中描述的一个分隔符样式的常量。UITableView使用这个属性来设置tableView中委托返回的cell上的分隔符样式。

    @property (nonatomic) UITableViewCellSeparatorStyle separatorStyle API_UNAVAILABLE(tvOS);
    

    @property (nonatomic, strong, nullable) UIColor *separatorColor UI_APPEARANCE_SELECTOR API_UNAVAILABLE(tvOS);

    属性描述 : 表视图中每行分隔符的颜色。默认颜色为灰色。

    @property (nonatomic, strong, nullable) UIColor *separatorColor UI_APPEARANCE_SELECTOR API_UNAVAILABLE(tvOS);
    

    @property (nonatomic, strong, nullable) UIView *tableHeaderView;

    属性描述 : 设置显示在表内容上方的视图。使用此属性可为整个表指定头部标题视图。头部标题视图是出现在表视图的滚动内容中的第一项,它与添加到各个节中的头部视图是分开的。此属性的默认值为nil。

    将视图分配给此属性时,请将该视图的高度设置为非零值。表格视图只考虑视图的框架矩形的高度;它自动调整头部标题视图的宽度以匹配表视图的宽度。

    @property (nonatomic, strong, nullable) UIView *tableHeaderView;
    

    @property (nonatomic, strong, nullable) UIView *tableFooterView;

    属性描述 : 设置显示在表内容下方的视图。使用此属性指定整个表的尾部标题视图。尾部标题视图是出现在表视图的滚动内容中的最后一项,它与添加到各个部分的尾部视图是分开的。此属性的默认值为nil。

    将视图分配给此属性时,请将视图的高度设置为非零值。表格视图只考虑视图的框架矩形的高度;它自动调整尾部标题视图的宽度以匹配表视图的宽度。

    @property (nonatomic, strong, nullable) UIView *tableFooterView;
    

    @property (nonatomic) BOOL allowsSelection API_AVAILABLE(ios(3.0));

    属性描述 : 决定用户是否可以选中行的布尔值。如果此属性的值为“YES”(默认值),则用户可以选择行。如果将其设置为“NO”,则它们无法选择行。仅当表格视图未处于编辑模式时,设置此属性才会影响单元格选择。如果要在编辑模式下限制单元格的选择,请在编辑期间使用AllowSelectionDuringEditing。

    @property (nonatomic) BOOL allowsSelection API_AVAILABLE(ios(3.0));
    

    @property (nonatomic) BOOL allowsSelectionDuringEditing;

    属性描述 : 一个布尔值,用于确定用户在表视图处于编辑模式时是否可以选择单元格。如果此属性的值为“YES”,则用户可以在编辑期间选择行。默认值为“NO”。如果要限制单元格的选择,而不考虑模式,请使用allowsSelection。

    @property (nonatomic) BOOL allowsSelectionDuringEditing; 
    

    @property (nonatomic) BOOL allowsMultipleSelection API_AVAILABLE(ios(5.0));

    属性描述 : 一个布尔值,用于确定用户是否可以在编辑模式之外选择多行。此属性控制是否可以在编辑模式之外同时选择多行。当此属性的值为“YES”时,被点击的每一行将获取选定的外观。再次点击该行将删除选定的外观。如果访问indexPathsForSelectedRows,则可以获取标识选定行的索引路径。
    此属性的默认值为“NO”。

    @property (nonatomic) BOOL allowsMultipleSelection API_AVAILABLE(ios(5.0));   
    

    @property (nonatomic) BOOL allowsMultipleSelectionDuringEditing API_AVAILABLE(ios(5.0));

    属性描述 : 一个布尔值,控制用户在编辑模式下是否可以同时选择多个单元格。此属性的默认值为NO。如果将其设置为YES,则在编辑模式下在所选行的旁边出现复选标记。此外,当UITableView进入编辑模式时,它不会查询编辑样式。如果您访问indexPathsForSelectedRows,您可以获得标识所选行的索引路径。

    @property (nonatomic) BOOL allowsMultipleSelectionDuringEditing API_AVAILABLE(ios(5.0)); 
    

    @property (nonatomic) CGFloat estimatedRowHeight API_AVAILABLE(ios(7.0));

    属性描述 : 表视图中行的估计高度。

    提供行的高度的非负估计值可以提高加载表视图的性能。如果表包含可变高度行,则在加载表时计算它们的所有高度可能会很耗费资源。估算允许您将几何计算的某些成本从加载时间推迟到滚动时间。

    默认值是UITableViewAutomaticDimension,这意味着表视图选择一个要代表您使用的估计高度。将值设置为0将禁用估计高度,这将导致表视图请求每个单元格的实际高度。如果表使用自调整大小的单元格,则此属性的值不能为0。

    在使用高度估计时,表视图会主动管理从滚动视图继承的内容contentoffset和contentSize属性。不要试图直接读取或修改这些属性。

    @property (nonatomic) CGFloat estimatedRowHeight API_AVAILABLE(ios(7.0));
    

    @property (nonatomic) CGFloat estimatedSectionHeaderHeight API_AVAILABLE(ios(7.0));

    属性描述 : 表视图中每组头部标题的估计高度。与estimatedRowHeight使用类似。

    @property (nonatomic) CGFloat estimatedSectionHeaderHeight API_AVAILABLE(ios(7.0));
    

    @property (nonatomic) CGFloat estimatedSectionFooterHeight API_AVAILABLE(ios(7.0));

    属性描述 : 表视图中每组尾部标题的估计高度。与estimatedRowHeight使用类似。

    @property (nonatomic) CGFloat estimatedSectionFooterHeight API_AVAILABLE(ios(7.0));
    

    @property (nonatomic, readonly, nullable) NSArray<NSIndexPath *> *indexPathsForVisibleRows;

    属性描述 : 索引路径的数组,每个路径标识表视图中的一个可见行。此属性的值是一个NSIndexPath对象数组,每个对象表示行索引和节索引,它们一起标识表视图中的可见行。如果没有行可见,则值为nil。

    @property (nonatomic, readonly, nullable) NSArray<NSIndexPath *> *indexPathsForVisibleRows;
    

    例如获取最后一个可见单元格的代码片段:

    ///获取最后一个可见单元格
    - (YBPopupMenuCell *)getLastVisibleCell
    {
        //获取索引路径的数组
        NSArray <NSIndexPath *>*indexPaths = [_contentView indexPathsForVisibleRows];
        //返回一个数组,该数组按升序列出接收数组的元素,由给定NSComparator块指定的比较方法确定。
        indexPaths = [indexPaths sortedArrayUsingComparator:^NSComparisonResult(NSIndexPath *  _Nonnull obj1, NSIndexPath *  _Nonnull obj2) {
            return obj1.row < obj2.row;
        }];
        //获取排序后的索引路径的数组的第一个元素
        NSIndexPath *indexPath = indexPaths.firstObject;
        //返回指定索引处的单元格
        return [_contentView cellForRowAtIndexPath:indexPath];
    }
    

    UITableView常用函数

    -(void)initWithFrame:(CGRect)fram style:(UITableViewStyle)style;

    函数描述 :初始化一个UITableView,并且设置表格样式。创建表视图时必须指定其样式,以后不能更改该样式。如果使用UIView方法initWithFrame初始化表视图,则会将UITableViewStylePlain样式用作默认样式。

    参数 :

    frame : 一个矩形,指定表视图在其superview's坐标中的初始位置和大小。随着表单元格的添加和删除,表视图的frame也随之更改。

    style :指定表视图样式的常量。

    返回值 :

    返回初始化的UITableView对象。

    - (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style NS_DESIGNATED_INITIALIZER;
    

    - (nullable __kindof UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath;

    函数描述 : 返回指定索引路径处的表单元格。

    参数 :

    indexPath : 在表视图中定位行的索引路径。

    返回值 :

    表示表单元格的对象,如果单元格不可见或indexPath超出范围,则为nil。

    - (nullable __kindof UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath;
    

    - (nullable __kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier;

    函数描述 : 通过identifier在(缓存)池中找到对应的UITableViewCell对象。

    出于性能原因,表视图的数据源通常应该在为其table view:cellForRowAtIndexPath:函数中的行分配单元格时重用UITableViewCell对象。表视图维护数据源标记为可重用的UITableViewCell对象的队列或列表。当要求为表视图提供新单元格时,从数据源对象调用此方法。如果现有的单元格可用,或者使用以前注册的类或NIB文件,该方法将对现有的单元格进行出列。如果没有可重用的单元格,并且您没有注册类或nib文件,则此方法返回nil。

    如果您为指定的标识符注册了一个类,并且必须创建一个新的单元格,则此方法通过调用其initWithStyle:reuseIdentifier:函数初始化该单元格。对于基于nib的单元,此方法从提供的nib文件加载单元对象。如果现有的单元可用于重用,则该方法调用单元格的PraseReFuleRead方法。

    参数 :

    identifier : 标识要重用的单元格对象的字符串。此参数不能为nil。

    返回值 :

    一个带有关联标识符的UITableViewCell对象,如果可重用单元队列中不存在这样的对象,则为nil。

    - (nullable __kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier;
    

    - (void)setEditing:(BOOL)editing animated:(BOOL)animated;

    函数描述 : 是否开启编辑模式。

    当在editing值设置为YES时调用此方法时,表视图将通过在每个可见的UITableViewCell对象上调用setEditing:animated:进入编辑模式。在编辑设置为“否”时调用此方法将关闭编辑模式。在编辑模式下,表格的单元格可能在每个单元格的左侧显示一个插入或删除控件,在右侧显示一个重新排序控件,具体取决于单元格的配置方式。表视图的数据源可以通过实现table view:canEditRowAtIndexPath:从编辑模式中选择性地排除单元格。

    参数 :

    editing :YES,进入编辑模式;NO,退出编辑模式。默认值为NO。

    animated : YES,以动画方式转换到编辑模式;NO,以立即转换。

    - (void)setEditing:(BOOL)editing animated:(BOOL)animated;
    

    UITableView刷新的方式

    -(void)reloadData;

    函数描述 : 重新访问数据源,刷新界面。调用此方法重新加载用于构造表的所有数据,包括单元格、分组头部视图和分组尾部视图、索引数组等。为了提高效率,表视图只重新显示那些可见的行。如果由于重新加载而导致表收缩,则它将调整偏移。

    表视图的委托或数据源在希望表视图完全重新加载其数据时调用此方法。不应该在插入或删除行的方法中调用它,特别是在通过调用beginUpdates和endUpdates实现的动画块中。

    当hasUncommittedUpdates属性为YES时,不要调用此方法。这样做会强制表视图在重新加载数据之前删除任何未提交的更改。

    - (void)reloadData;
    

    整个tableview的刷新,例如:

    [self.tableView reloadData];
    

    注:reloadData 并不会等待tableview更新结束后才返回,而是立即返回,然后去计算表高度,获取cell等。如果表中的数据非常大,在一个 run loop 周期没执行完,这时,需要tableview视图数据的操作就会出问题了。
    延迟到reloadData结束在操作:

    方法一 : layoutIfNeeded会强制重绘并等待完成。
    [self.tableView reloadData];
    [self.tableView layoutIfNeeded];
    //刷新完成
    
    方法二: reloadData会在主队列执行,而 dispatch_get_main_queue 会等待机会,直到主队列空闲才执行。
    [self.tableView reloadData];
    dispatch_async(dispatch_get_main_queue(), ^{
        //刷新完成
    });
    

    刷新某Section中的某Row

    - (void)reloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation API_AVAILABLE(ios(3.0));

    函数描述 : 使用给定的动画效果重新加载指定的行。

    重新加载一行将导致表视图向其数据源请求该行的新单元格。该表将新单元格导入,同时将旧行导出。如果您想要警告用户单元格的值正在更改,请调用此方法。但是,如果通知用户并不重要(也就是说,您只想更改一个单元格正在显示的值),那么您可以获取特定行的单元格并设置它的新值。

    在beginUpdates和endpdates方法定义的animation块中调用此方法时,其行为类似于deleteRowsAtIndexPaths:withRowAnimation:。UITableView传递给方法的索引是在任何更新之前在表视图的状态中指定的。这与动画块中的插入、删除和重新加载方法调用的顺序无关。

    参数 :

    indexPaths : 标识要重新加载的行的NSIndexPath对象数组。

    animation : 指示如何设置重新加载动画的常数,例如,从底部淡出或滑出。

    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
            
    [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath,nil] withRowAnimation:UITableViewRowAnimationFade];
    

    刷新某Section

    - (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation API_AVAILABLE(ios(3.0));

    函数描述 :使用给定的动画效果重新加载指定的组。

    调用此方法将导致表视图向其数据源询问指定组的新单元格。表视图在设置旧单元格的动画时设置新单元格插入的动画。如果要提醒用户指定组的值正在更改,请调用此方法。但是,如果只想更改指定组的单元格中的值而不通知用户,则可以获取这些单元格并直接设置它们的新值。

    在beginUpdates和endpdates方法定义的animation块中调用此方法时,其行为类似于deleteSections:withRowAnimation:。UITableView传递给方法的索引是在任何更新之前在表视图的状态中指定的。这与动画块中的插入、删除和重新加载方法调用的顺序无关。

    参数 :

    sections : 标识要重新加载的组的索引集。

    animation : 指示如何设置重新加载动画的常数,例如,从底部淡出或滑出。

    NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndex:1];
    [self.tableView reloadSections:indexSet withRowAnimation:UITableViewRowAnimationFade];
    

    操作Cell系统提供的动画效果

    typedef NS_ENUM(NSInteger, UITableViewRowAnimation) {
            UITableViewRowAnimationFade,//动画淡入淡出
            UITableViewRowAnimationRight,//动画向右滑出
            UITableViewRowAnimationLeft,//动画向左滑出
            UITableViewRowAnimationTop,//动画向上滑出
            UITableViewRowAnimationBottom,//动画向下滑出
            UITableViewRowAnimationNone,//无动画效果
            UITableViewRowAnimationMiddle,//动画中间滑出
            UITableViewRowAnimationAutomatic = 100//自动选择动画
        };
    

    针对执行reloadRowsAtIndexPaths/reloadSections/reloadData方法出现界面跳动问题

    针对执行reloadRowsAtIndexPaths/reloadSections/reloadData方法出现界面跳动问题,可采用下面代码中尝试处理,尤其是有自适应cell高度的。

        // iOS 11 ScrollView TableView
        if (@available(iOS 11.0, *)) {
            UIScrollView.appearance.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
            UITableView.appearance.estimatedRowHeight = 0;
            UITableView.appearance.estimatedSectionHeaderHeight = 0;
            UITableView.appearance.estimatedSectionFooterHeight = 0;
        }
    
    
    Jietu20200306-172940.gif

    这段代码还是以这样的方式写更稳妥些,哪个表视图对象要处理哪个表视图对象设置属性:

    if (@available(iOS 11.0, *)) {
            self.tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
            self.tableView.estimatedRowHeight = 0;
            self.tableView.estimatedSectionHeaderHeight = 0;
            self.tableView.estimatedSectionFooterHeight = 0;
        }
    

    对于刷新某Section中的某Row时,页面跳动的现象:

    Jietu20210415-151422.gif

    可以尝试采用禁用视图转换动画的方式消除跳动,代码如下:

    //刷新表视图
    [UIView performWithoutAnimation:^{
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:idx inSection:0];
        [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
    }];
    

    UITableView的编辑模式

    UITableView有个editing属性,设置为YES时,可以进入编辑模式。在编辑模式下,可以管理表格中的行,比如改变行的排列顺序,增加行,删除行,但不能修改行的内容。

    开启编辑模式的方式:

    @property(nonatomic,getter = isEditing)Bool editing;

    -(void)setEditing:(Bool)editing animated:(Bool)animated

    删除UITableView的行

    - (void)deleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;

    函数描述 : 删除由索引路径数组指定的行,并使用一个选项设置删除的动画。

    当这个方法在一个由beginUpdates和endpdates方法定义的动画块中被调用时,UITableView会延迟任何行或组的插入,直到它处理了行或组的删除之后。无论插入和删除方法调用的顺序如何,都遵循此顺序。这与在可变数组中插入或删除项不同,在可变数组中,操作可以影响用于后续插入或删除操作的数组索引。

    参数 :

    indexPaths : 标识要删除的行的NSIndexPath对象数组。

    animation : 指示如何设置删除动画的常数,例如,从底部淡出或滑出。

    1.首先要开启编辑模式

    2.实现UITableViewDataSource的如下方法:

    -(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{
        if(editingStyle == UITableViewCellEditingStyleDelete){
            //删除真实数据
            [self.data removeObjectAtIndex:indexPath.row];
            //删除UITableView中的某一行(带动画效果)
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath, nil] withRowAnimation:UITableViewRowAnimationLeft];
            //如不考虑动画效果,也可以直接reloadData,但不建议这么做。
            [tableView reloadData];
        }
    }
    

    移动UITableView的行

    1.首先要开启编辑模式

    2.实现UITableViewDateSource的如下方法(如果没有实现此方法,将无法换行):

    - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath{
        NSInteger from = sourceIndexPath.row;
        NSInteger to = destinationIndexPath.row;
        if(from == to){
            return;
        }
         //交互数据
        [self.data exchangeObjectAtIndex:from withObjectAtIndex:to];
    }
    

    选中UITableView的行

    当某行被选中时会调用此方法(UITableViewDelegate的方法)

    -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
        //取消选中某行时,让被选行的高亮颜色消失(带动画效果)
        [tableView deselectRowAtIndexPath:indexPath animated:YES];
    }
    

    UITableViewRowAction

    UITableViewRowAction,当用户在表行中水平滑动时要显示的单个操作。

    创建一个UITableViewRowAction对象来为表行定义一个单独的自定义操作。用户在表视图中水平滑动以显示与一行相关的操作。每个row-action对象包含文本显示、要执行的操作以及要应用于该操作的任何特定格式。

    要向表视图的行添加自定义操作,请在表视图的委托对象中实现tableView:editActionsForRowAtIndexPath:方法。在该方法中,创建并返回指定行的操作。该表显示您的操作按钮,并在用户轻击其中一个按钮时执行相应的处理程序块。

    在ios11及以后版本中,使用UISwipeActionsConfiguration来配置表视图操作。

    UITableViewRowAction的属性与函数

    + (instancetype)rowActionWithStyle:(UITableViewRowActionStyle)style title:(nullable NSString *)title handler:(void (^)(UITableViewRowAction *action, NSIndexPath *indexPath))handler;

    函数描述 : 创建并返回一个新的表视图行操作对象。以后无法更改指定的样式和处理程序块。您可以更改操作按钮的标题。还可以使用此类的属性配置按钮的其他外观相关属性。可以将同一行操作对象指定给表的多行。

    参数 :

    style : 应用于按钮的样式特征。使用此值可将默认外观特征应用于按钮。这些特征提供了有关按钮功能的信息。例如,使用它来指示某个操作对底层数据具有破坏性。

    title : 要在按钮中显示的字符串。指定用户当前语言的本地化字符串。

    handler : 当用户点击与此操作相关联的按钮时要执行的块。UIKit会复制您提供的块。当用户选择此对象表示的操作时,UIKit将在应用程序的主线程上执行处理程序块。此参数不能为零。此块没有返回值,并接受以下参数:
    action : 表示用户选择的操作的操作对象。
    indexPath : 用户操作的表行索引。

    返回值 :

    可以从表视图的委托方法返回的新表行操作对象。

    //UITableViewRowActionStyle枚举值如下:
    typedef NS_ENUM(NSInteger, UITableViewRowActionStyle) {
        UITableViewRowActionStyleDefault = 0, //将默认样式应用于按钮。
        UITableViewRowActionStyleDestructive = UITableViewRowActionStyleDefault, //等于默认样式。
        UITableViewRowActionStyleNormal //应用反映标准的非破坏性操作的样式。
    } API_DEPRECATED("Use UIContextualAction and related APIs instead.", ios(8.0, 13.0)) API_UNAVAILABLE(tvOS);
    
    + (instancetype)rowActionWithStyle:(UITableViewRowActionStyle)style title:(nullable NSString *)title handler:(void (^)(UITableViewRowAction *action, NSIndexPath *indexPath))handler;
    

    @property (nonatomic, readonly) UITableViewRowActionStyle style;

    属性描述 : 应用于操作按钮的样式。此属性的值在创建时设置,以后无法更改。

    @property (nonatomic, readonly) UITableViewRowActionStyle style;
    

    @property (nonatomic, copy, nullable) NSString *title;

    属性描述 : 操作按钮的标题。

    @property (nonatomic, copy, nullable) NSString *title;
    

    @property (nonatomic, copy, nullable) UIColor *backgroundColor;

    属性描述 : 操作按钮的背景色。使用此属性指定按钮的背景色。如果未为此属性指定值,则UIKit将根据“style”属性中的值指定默认颜色。

    @property (nonatomic, copy, nullable) UIColor *backgroundColor; 
    

    数据源(dataSource)与代理(delegate)

    UITableView需要一个数据源(dataSource)来显示数据,UITableView会向数据源查询一共有多少行数据以及每一行显示什么数据等。没有设置数据源的UITableview只是个空壳,凡是遵守UITableViewDataSource协议的Object-c对象都可以作为UITableView的数据源。

    通常都要为UITableView设置代理(delegate),以便在UITableView触发事件时作出相应的处理,比如选中了某一行。凡是遵守了UITableViewDelegate协议的Object-c对象,都可以是UITableView的代理对象。

    一般情况下会让控制器充当UITableView的数据源和代理。

    UITableViewDataSource提供的一些方法

    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;

    函数描述 : 询问数据源共分为多少组数据,如果未实现,则默认值为1组数据。

    参数 :

    tableView : 表示请求此信息的表视图的对象。

    返回值 :

    tableView中数据分组的数量。

    //共分为多少组数据
    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    }
    

    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;

    函数描述 :询问数据源每组有多少行数据。

    参数 :

    tableView : 请求此信息的表视图对象。

    section : 表视图中标识组的索引号。

    返回值 :

    每组中的行数。

    //每组有多少行数据
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    }  
    

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

    函数描述 : 在实现中,为给定的索引路径创建并配置适当的单元格。使用表视图的dequeueReusableCellWithIdentifier:forIndexPath:函数创建单元格,该方法回收或创建单元格。

    创建单元格后,使用适当的数据值更新单元格的属性。不要自己调用这个方法。如果希望从表中检索单元格,请调用表视图的cellForRowAtIndexPath:方法。

    参数 :

    tableView : 请求单元格的表视图对象。

    indexPath : 在tableView中定位行的索引路径。

    返回值 :

    从UITableViewCell继承的对象,表视图可用于指定行。如果返回nil,UIKit将引发断言。

    //每行显示的内容
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    }
    

    - (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section;

    函数描述 : 向数据源询问表视图的指定组的头部标题。表格视图对节标题使用固定字体样式。

    如果需要其他字体样式,请改为在委托方法tableView:viewForHeaderInSection:中返回自定义视图(例如,UILabel对象)。

    如果未实现此方法或tableView:viewForHeaderInSection:函数,则表不显示节的标题。如果同时实现这两个方法,则tableView:viewForHeaderInSection:函数优先。

    参数 :

    tableView : 请求标题的表视图对象。

    section :标识tableView的每组的索引号。

    返回值 : 用作每组头部标题的字符串。如果您返回nil,则该部分将没有标题。

    //某一组的头部标题
    -(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
    }
    

    - (nullable NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section;

    函数描述 : 向数据源询问表视图的指定组的尾部标题。表视图对节页脚标题使用固定字体样式。

    如果需要其他字体样式,请改为在委托方法tableView:viewForFooterInSection:中返回自定义视图(例如,UILabel对象)。

    如果未实现此方法或tableView:viewForFooterInSection:方法,则表不显示节的页脚。如果同时实现这两个方法,则tableView:viewForFooterInSection:函数优先。

    参数 :

    tableView : 请求标题的表视图对象。

    section :标识tableView的每组的索引号。

    返回值 :

    用作每组尾部标题的字符串。如果您返回nil,则该部分将没有标题。

    //某一组的尾部标题
    -(NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section{
    }
    

    - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath;

    函数描述 : 询问数据源某一行是否可以编辑。该方法允许数据源将个别行排除在可编辑之外。

    可编辑的行显示其单元格中的插入或删除控件。如果未实现此方法,则假定所有行都是可编辑的。不可编辑的行忽略UITableViewCell对象的editingStyle属性,对于删除或插入控件不执行缩进。

    可编辑但不希望显示插入或移除控件的行可以从tableView:editingStyleForRowAtIndexPath:delegate方法返回UITableViewCellEditingStyleNone。

    参数 :

    tableView : 请求此信息的表视图对象。

    indexPath :在tableView中定位行的索引路径。

    返回值 :

    如果indexPath指示的行是可编辑的,则为“YES”;否则为“NO”。

    //某一行是否可以编辑
    -(BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath{
    }
    

    - (nullable NSArray<UITableViewRowAction *> *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath API_DEPRECATED_WITH_REPLACEMENT("tableView:trailingSwipeActionsConfigurationForRowAtIndexPath:", ios(8.0, 13.0)) API_UNAVAILABLE(tvOS);

    函数描述 : 请求代理在响应指定行的滑动时显示操作。当希望为某个表行提供自定义操作时,请使用此方法。当用户水平地在一行中滑动时,表视图将行内容移到一边,以显示您的操作。单击其中一个操作按钮将执行与操作对象一起存储的处理程序块。如果不实现此方法,则在用户滑动该行时,table视图将显示标准的附件按钮。

    参数 :

    tableView :请求此信息的表视图对象。

    indexPath :行的索引路径。

    返回值 :

    UITableViewRowAction对象的数组,代表行的动作。您提供的每个操作都用于创建用户可以点击的按钮。

    - (nullable NSArray<UITableViewRowAction *> *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath API_DEPRECATED_WITH_REPLACEMENT("tableView:trailingSwipeActionsConfigurationForRowAtIndexPath:", ios(8.0, 13.0)) API_UNAVAILABLE(tvOS);
    

    - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath;

    函数描述 : 询问数据源是否可以将给定行移动到表视图中的其他位置。此方法允许数据源指定不显示重新排序控件的指定行。

    默认情况下,如果数据源实现tableView:moveRowAtIndexPath:toIndexPath:函数,则显示重新排序控件。

    参数 :

    tableView : 请求此信息的表视图对象。

    indexPath :在tableView中定位行的索引路径。

    返回值 :

    如果行可以移动,则为“YES”;否则为“NO”。

    //某一行是否可以移动来进行重新排序
    -(BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath{
    }
    

    - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath;

    函数描述 : 请求数据源提交在接收方中插入或删除指定行的操作。

    当用户点击与表视图中的UITableViewCell对象相关联的插入(绿色加号)控件或删除按钮时,表视图将此消息发送给数据源,要求它提交更改。(如果用户点击删除(红色-)控件,那么表视图将显示Delete按钮以获得确认。)

    数据源通过调用UITableView方法insertRowsAtIndexPaths:withRowAnimation:或deleteRowsAtIndexPaths:withRowAnimation:来提交插入或删除操作。要启用表视图的“滑动删除”功能(用户水平地滑动一行来显示“删除”按钮),必须实现此方法。

    不应该在这个方法的实现中调用setEditing:animated:。如果出于某种原因必须调用它,那么在延迟之后使用performSelector:withObject:afterDelay:方法调用它。

    参数 :

    tableView : 请求插入或删除的表视图对象。

    editingStyle : 与indexPath指定的行所请求的插入或删除相对应的单元格编辑样式。可能的编辑样式是UITableViewCellEditingStyleInsert或UITableViewCellEditingStyleDelete。

    indexPath : 在tableView中定位行的索引路径。

    //UITableViewCellEditingStyle枚举值如下:
    typedef NS_ENUM(NSInteger, UITableViewCellEditingStyle) {
        UITableViewCellEditingStyleNone, //单元格没有编辑控件。这是默认值。
        UITableViewCellEditingStyleDelete, //单元格具有删除编辑控件;这个控件是一个包含负号的红色圆圈。
        UITableViewCellEditingStyleInsert  //单元格具有插入编辑控件;这个控件是一个绿色的圆圈,里面有一个加号。
    };
    
    //当用户点击与表视图中的UITableViewCell对象相关联的插入(绿色加号)控件或删除按钮时
    - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath;
    

    - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath;

    函数描述 :通知数据源将表视图中特定位置的行移动到另一个位置。当用户按下fromRow中的reorder控件时,UITableView对象将此消息发送到数据源。

    参数 :

    tableView : 请求此操作的表视图对象。

    sourceIndexPath : 在tableView中定位要移动的行的索引路径。

    destinationIndexPath : 一个索引路径,用于定位作为移动目标的tableView中的行。

    - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath;
    

    - (nullable NSArray<NSString *> *)sectionIndexTitlesForTableView:(UITableView *)tableView;

    函数描述 : 要求数据源返回表视图各组的标题作为右边索引栏的内容。

    参数 :

    tableView : 请求此信息的表视图对象。

    返回值 :

    字符串数组,用作表视图中组的标题并显示在表视图右侧的索引列表中。表视图必须是纯样式(UITableViewStylePlain)。例如,对于按字母顺序排列的列表,可以返回一个包含字符串“A”到“Z”的数组。

    //UITableView右边索引栏的内容
    - (NSArray<NSString *> *)sectionIndexTitlesForTableView:(UITableView *)tableView{
    }
    

    UITableViewDelegate提供的一些方法

    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;

    函数描述 : 选中了UITableView的某一行。代理处理此方法中的选择。它可以做的一件事是专门将检查标记图像(UITableViewCellAccessoryCheckmark)分配给section中的一行(单选列表样式)。当表的编辑属性设置为“是”(即表视图处于编辑模式)时,不会调用此方法。

    参数 :

    tableView : 通知代理有关新行选择的表视图对象。

    indexPath :在tableView中定位新选定行的索引路径。

    //选中了UITableView的某一行
    -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    }
    

    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;

    函数描述 : 要求代理指定某一行的高度使用的高度。当表中的行高度不完全相同时,重写此方法。如果行的高度相同,请不要重写此方法;而是为UITableView的rowHeight属性指定一个值。此方法返回的值优先于rowHeight属性中的值。

    在它出现在屏幕上之前,表视图为表的可见部分中的项调用此方法。当用户滚动时,表视图仅在项目在屏幕上移动时调用该方法。每次项目出现在屏幕上时,它都调用该方法,而不管它以前是否出现在屏幕上。

    参数 :

    tableView : 请求此信息的表视图对象。

    indexPath : 在tableView中定位行的索引路径。

    返回值 :

    一个非负浮点值,指定行的高度(以点为单位)。

    //某一行的高度
    -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    }
    

    - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section;

    函数描述 : 向代理询问用于特定分组头部视图的高度。使用此方法指定tableView:viewForHeaderInSection:函数返回的自定义头视图的高度。

    参数 :

    tableView : 请求此信息的表视图对象。

    section :标识tableView的分组的索引号。

    返回值 :

    一个非负浮点值,指定分组头部视图高度(以点为单位)。

    //某一组头部的高度
    -(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
    }
    

    - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section;

    函数描述 : 向代理询问用于特定分组尾部视图的高度。使用此方法可以指定tableView:viewForFooterInSection:函数返回的自定义尾部视图的高度。

    参数 :

    tableView : 请求此信息的表视图对象。

    section :标识tableView的分组的索引号。

    返回值 :

    指定分组的尾部视图高度(以点为单位)的非负浮点值。

    //某一组尾部的高度
    -(CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{
    }
    

    - (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section;

    函数描述 : 要求代理在表视图的指定分组的头部标题中显示视图对象。

    使用此方法返回标题的UILabel、UIImageView或自定义视图。如果实现此方法,则还必须实现tableView: heightForHeaderInSection:函数以指定自定义视图的高度。

    参数 :

    tableView :请求视图对象的表视图对象。

    section :包含头部视图的分组的索引号。

    返回值 :

    要显示在指定分组头部的视图对象

    //某组头部显示的视图
    -(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
    }
    

    例如:头部某组头部显示一个标签

    - (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
        if(section == 0){
            //初始化要显示的标签富文本
            NSMutableAttributedString *newAttributedString = [[NSMutableAttributedString alloc]initWithString:@"所有信息保密,仅用于处方药审核"];
            //创建Image的富文本格式
            NSTextAttachment *attach = [[NSTextAttachment alloc] init];
            //这个-2.5是为了调整下标签跟文字的位置
            attach.bounds = CGRectMake(0, -2.5, 16, 16);
            //设置图片
            attach.image = [UIImage imageNamed:@"dun_pai"];
            //添加到富文本对象里
            NSAttributedString * imageStr = [NSAttributedString attributedStringWithAttachment:attach];
            //加入文字前面
            [newAttributedString insertAttributedString:imageStr atIndex:0];
            //初始化要显示的标签
            UILabel *titleLabel = [[UILabel alloc]initWithFrame:CGRectZero];
            //设置文本字体
            titleLabel.font = [YSCUiUtils fontFour];
            //设置文本颜色
            titleLabel.textColor = [YSCUiUtils colorThree];
            //设置文本对齐方式
            titleLabel.textAlignment = NSTextAlignmentCenter;
            //设置富文本
            titleLabel.attributedText = newAttributedString;
            //返回标签
            return titleLabel;
        }
        return nil;
    }
    
    -(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
        if(section == 0){
            return 40;
        }
        return 10;
    }
    

    样式如图 :

    截屏2020-08-18上午10.31.10.png

    - (nullable UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section;

    函数描述 : 要求代理在表视图的指定分组的尾部标题中显示视图对象。

    使用此方法返回页脚的UILabel、UIImageView或自定义视图。如果实现此方法,则还必须实现tableView: heightForFooterInSection:函数以指定自定义视图的高度。

    参数 :

    tableView :请求视图对象的表视图对象。

    section :包含尾部视图的分组的索引号。

    返回值 :

    要显示在指定分组尾部的视图对象。

    //某组尾部显示的视图
    -(UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section{
    }
    

    - (NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath;

    函数描述 : 要求代理返回给定分组中某行的缩进级别。

    参数 :

    tableView : 请求此信息的表视图对象。

    indexPath : 在tableView中定位行的索引路径。

    返回值 :

    返回指定行的深度以显示其在分组中的层次结构位置。

    //设置每一行的等级缩进(字越小等级越高)
    -(NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath{ 
    }
    

    - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;

    函数描述 : 通知代理表视图将要为特定的行绘制单元格。在绘制一行单元格之前,表视图将此消息发送给它的委托,从而允许代理在显示单元格对象之前自定义该对象。此方法使代理有机会重写表视图先前设置的基于状态的属性,例如选择时的颜色和背景色。代理返回后,表视图仅设置alpha和frame属性,然后仅设置行滑入或滑出时的动画。

    参数 :

    tableView : 通知代理即将发生的事件的表视图对象。

    cell : tableView在绘制行时要使用的表视图单元格对象。

    indexPath : 在tableView中定位行的索引路径。

    - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;
    

    - (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell )cell forRowAtIndexPath:(NSIndexPath)indexPath API_AVAILABLE(ios(6.0));

    函数描述 : 通知代理指定的单元格已从表中删除。使用此方法可以检测何时从表视图中删除单元格,而不是监视单元格本身以查看它何时出现或消失。

    参数 :

    tableView : 删除视图的表视图对象。

    cell :被移除的单元格。

    indexPath : 单元格的索引路径。

    - (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath API_AVAILABLE(ios(6.0));
    

    - (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section API_AVAILABLE(ios(6.0));

    函数描述 : 通知代理表将要绘制指定节的标题视图。

    参数 :

    tableView : 通知代理此事件的表视图对象。

    view : 即将显示的标题视图。

    section : 包含标题视图的节的索引号。

    - (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section API_AVAILABLE(ios(6.0));
    

    - (void)tableView:(UITableView *)tableView didEndDisplayingHeaderView:(UIView *)view forSection:(NSInteger)section API_AVAILABLE(ios(6.0));

    函数描述 : 通知代理指定的节的标题视图已从表中删除。使用此方法检测从表视图中删除标题视图时的情况,而不是监视标题视图本身,以查看它何时出现或消失。

    参数 :

    tableView : 删除视图的表视图对象。

    view : 已删除的标题视图。

    section : 包含标题视图的节的索引。

    - (void)tableView:(UITableView *)tableView didEndDisplayingHeaderView:(UIView *)view forSection:(NSInteger)section API_AVAILABLE(ios(6.0));
    

    - (void)tableView:(UITableView *)tableView willDisplayFooterView:(UIView *)view forSection:(NSInteger)section API_AVAILABLE(ios(6.0));

    函数描述 : 与tableView:(UITableView *)tableView didEndDisplayingHeaderView:(UIView *)view forSection:(NSInteger)section函数类似,针对节页脚标题视图。

    - (void)tableView:(UITableView *)tableView willDisplayFooterView:(UIView *)view forSection:(NSInteger)section API_AVAILABLE(ios(6.0));
    

    - (void)tableView:(UITableView *)tableView didEndDisplayingFooterView:(UIView *)view forSection:(NSInteger)section API_AVAILABLE(ios(6.0));

    函数描述 : 与tableView:(UITableView *)tableView didEndDisplayingHeaderView:(UIView *)view forSection:(NSInteger)section函数类似,针对节页脚标题视图。

    - (void)tableView:(UITableView *)tableView didEndDisplayingFooterView:(UIView *)view forSection:(NSInteger)section API_AVAILABLE(ios(6.0));
    

    UITableViewCell

    UITableView的每一行都是一个UITableViewCell,通过dataSource的tableView:cellForRowIndexPath:方法来初始化每一行。

    UITableViewCell是UIView的子类,内部有个默认的子视图:contentView。contentView是UITableViewCell所显示内容的父视图,并负责显示一些辅助指示视图。辅助指示视图是显示一个表示动作的图标,可以通过设置UITableViewCell的accessoryType来显示,默认是UITableViewCellAccessoryNone(不显示辅助指示图),其他值如下:


    屏幕快照 2019-03-03 下午11.41.04.png

    UITableViewCell的contentView

    contentView下默认有三个子视图,其中的2个是UILabel(通过UITableViewCell的textLabel和detailTextLabel属性访问),第三个是UIImageView(通过UITableViewCell的imageView属性访问)。

    UITableViewCell还有一个UITableViewStyle属性,用于决定使用contentView的哪些子视图,以及这些子视图在contentView中的位置,起样式如下:


    屏幕快照 2019-03-04 下午8.00.26.png

    UITableViewCell自定义

    当系统提供的UITableViewCell样式不能满足需求时,我们可以创建UITableViewCell类并继承UITableViewCell来自定义样式,重写UITableViewCell的初始化方法,例如:

    -(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
        self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
        if(self){
            self.backgroundColor = [UIColor whiteColor];
            [self drawCell];
        }
        return self;
    }
    

    UITableViewCell对象的重用原理

    ios设备的内存有限,如果用UITableView显示成千上万条数据,就需要成千上万个UITableViewCell对象,那样的话将会耗尽ios设备的内存。要解决该问题,需要从用UITableViewCell对象。

    重用原理:当滚动列表时,部分UITableViewCell会移除窗口,UITableView会将窗口外的UITableViewCell放入一个对象池中,等待重用。当UITableView要求dataSource返回UITableViewCell时,dataSource会先查看这个对象池,如果池中有未使用的UITableViewCell,dataSource会用新的数据配置这个UITableViewCell,然后返回给UITableView,重新显示到窗口中,从而避免创建新对象。

    还有一个非常重要的问题:有时需要自定义UITableViewCell(用一个子类继承UITableViewCell),而且每一行用的不一定是同一种UITableViewCell(如微信聊天的布局),所以一个UITableView可能拥有不同类型的UITableViewCell,对象池中也会有很多不同类型的UITableViewCell,那么UITableView在重用UITableViewCell时可能会得到错误类型的UITableViewCell

    解决方案:UITableViewCell有个NSString *reuseIdentifier属性,可以在初始化UITableViewCell的时候传入一个特定的字符串标识来设定reuseIdentifier(一般用UITableViewCell的类名)。当UITableView要求dataSource返回UITableViewCell时,先通过一个字符串标识到对象池中查找对应类型的UITableViewCell对象,如果有,就重用,如果没有,就传入这个字符串标识来初始化一个UITableViewCell对象。

    重用UITableViewCell对象的代码片段:

    - (UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
        static NSString *identifier = @"UITableViewCell";
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
        if(cell == nil){
            cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
        }
        cell.textLabel.text = [NSString stringWithFormat:@"Text %li",(long)indexPath.row];
        return cell;
    }
    

    不重用UITableViewCell对象的代码片段:

    - (UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
        static NSString *identifier = @"UITableViewCell";
        UITableViewCell *cell =[tableView cellForRowAtIndexPath:indexPath];
        if(cell == nil){
            cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
        }
        cell.textLabel.text = [NSString stringWithFormat:@"Text %li",(long)indexPath.row];
        return cell;
    }
    

    UITableViewCell的常用属性

    @property (nonatomic, strong, nullable) UIView *backgroundView;

    属性描述 : 用作单元格背景的视图。

    对于普通样式表(UITableViewStylePlain)中的单元格,默认值为nil;对于分组样式表UITableViewStyleGrouped,默认值为非nil。UITableViewCell将背景视图添加为所有其他视图后面的子视图,并使用其当前帧位置。

    @property (nonatomic, strong, nullable) UIView *backgroundView;
    

    @property (nonatomic, strong, nullable) UIView *selectedBackgroundView;

    属性描述 : 选定单元格时用作其背景的视图。

    对于纯样式表(UITableViewStylePlain)中的单元格,默认值为nil;对于节组表UITableViewStyleGrouped,默认值为非nil。UITableViewCell仅在选定单元格时才将此属性的值添加为子视图。它将选定的背景视图作为子视图添加到背景视图(background view)正上方(如果不是nil,则为backgroundView)或所有其他视图的后面。调用setSelected:animated:使选定的背景视图以alpha淡入和淡出动画。

    @property (nonatomic, strong, nullable) UIView *selectedBackgroundView;
    

    @property (nonatomic) UITableViewCellSelectionStyle selectionStyle;

    属性描述 : 单元格的选择样式。选择样式是backgroundView常量,用于确定选定单元格时单元格的颜色。

    //UITableViewCellSelectionStyle枚举值如下:
    typedef NS_ENUM(NSInteger, UITableViewCellSelectionStyle) {
        UITableViewCellSelectionStyleNone,  //没有颜色   
        UITableViewCellSelectionStyleBlue,  //蓝色 
        UITableViewCellSelectionStyleGray,  //灰色  
        UITableViewCellSelectionStyleDefault API_AVAILABLE(ios(7.0))  //默认值
    };
    
    @property (nonatomic) UITableViewCellSelectionStyle   selectionStyle;      
    

    自定义UITableViewCell

    通过代码往UITableViewCell的contentView中添加子视图,在初始化方法(比如init,initWithStyle:reuseIdentifier:)中添加子控件,在layoutSubViews方法中分配子控件的位置和大小

    设置UITableView中UITableViewCell的宽度

    设置UITableViewCell的宽度需要在自定义的Cell中重写父类的
    - (void)setFrame:(CGRect)frame方法,例如Cell的宽度左右缩进10:

    - (void)setFrame:(CGRect)frame {
        frame.origin.x += 10;
    
        frame.size.width -= 2 * 10;
    
        [super setFrame:frame];
    }
    

    效果大概是这样:


    Jietu20191126-094847@2x.gif

    UITableViewController

    是UIViewController的子类,UITableViewController默认扮演了三种角色:视图控制器,UITableView的数据源和代理
    UITableViewController的View是个UITableView,由UITableViewController负责设置和显示这个对象。UITableViewController对象被创建后,会将这个UITableView对象的dataSource和delegate指向UITableViewController自己。

    练习代码

    不带选择按钮的cell

    //
    //  MessageListItemCell.h
    
    
    #import <UIKit/UIKit.h>
    #import "MessageItemModel.h"
    
    UIKIT_EXTERN NSString * const MessageListItemCellReuseIdentifier;
    
    @interface MessageListItemCell : UITableViewCell
    
    @property (nonatomic, strong) UIView *readIdentifierView;//未读消息标记视图
    @property (nonatomic, strong) UIImageView *customImageView;//消息图标视图
    @property (nonatomic, strong) UILabel *titleLabel;//消息标题标签
    @property (nonatomic, strong) UILabel *contentLabel;//消息内容标签
    @property (nonatomic, strong) UILabel *dateLabel;//日期标签
    
    @property (nonatomic, strong)MessageItemModel *mdoel;//模型
    
    @end
    
    
    //
    //  MessageListItemCell.m
    
    
    #import "MessageListItemCell.h"
    
    NSString * const MessageListItemCellReuseIdentifier = @"MessageListItemCellReuseIdentifier";
    static CGSize const ReadIdentifierViewSize = {10.0, 10.0};
    
    @interface MessageListItemCell()
    
    @property (nonatomic, strong) CALayer *separatorLineLayer;//底部分割线
    
    @end
    
    
    @implementation MessageListItemCell
    
    - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
        self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
        if(self){
            self.selectionStyle = UITableViewCellSelectionStyleNone;
            [self createUI];
        }
        return self;
    }
    
    - (void)createUI{
        ///消息图标视图
        self.customImageView = [[UIImageView alloc] initWithFrame:CGRectZero];
        [self.contentView addSubview:self.customImageView];
        
        ///未读消息标记视图
        self.readIdentifierView = [[UIView alloc] initWithFrame:CGRectZero];
        [self.contentView addSubview:self.readIdentifierView];
        self.readIdentifierView.backgroundColor = [UIColor greenColor];
        self.readIdentifierView.layer.cornerRadius = ReadIdentifierViewSize.height * 0.5;
        self.readIdentifierView.layer.masksToBounds = YES;
        
        ///消息标题标签
        self.titleLabel = [[UILabel alloc] initWithFrame:CGRectZero];
        [self.contentView addSubview:self.titleLabel];
        self.titleLabel.font = [UIFont systemFontOfSize:16];
        self.titleLabel.textColor = [UIColor blackColor];
        
        ///消息内容标签
        self.contentLabel = [[UILabel alloc] initWithFrame:CGRectZero];
        [self.contentView addSubview:self.contentLabel];
        self.contentLabel.font = [UIFont systemFontOfSize:13];
        self.contentLabel.textColor = [UIColor grayColor];
        
        ///日期标签
        _dateLabel = [[UILabel alloc] initWithFrame:CGRectZero];
        [self.contentView addSubview:_dateLabel];
        _dateLabel.font = [UIFont systemFontOfSize:11];
        _dateLabel.textColor = [UIColor grayColor];
        
        ///底部分割线
        self.separatorLineLayer = [[CALayer alloc] init];
        [self.contentView.layer addSublayer:_separatorLineLayer];
        self.separatorLineLayer.backgroundColor = [UIColor grayColor].CGColor;
        
        [self setupConstraints];
        
    }
    
    ///设置布局
    - (void)setupConstraints {
        
        ///消息图标视图
        [self.customImageView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.centerY.equalTo(self.contentView);
            make.left.equalTo(self.contentView).offset(10);
            make.size.mas_equalTo(CGSizeMake(54.0, 54.0));
        }];
        
        ///未读消息标记视图
        [self.readIdentifierView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.top.equalTo(self.customImageView.mas_top).offset(10 * 0.5);
            make.right.equalTo(self.customImageView.mas_right).offset(-10 * 0.5);
            make.size.mas_equalTo(ReadIdentifierViewSize);
        }];
        
        ///日期标签
        [self.dateLabel mas_makeConstraints:^(MASConstraintMaker* make) {
            make.top.equalTo(self.contentView).offset(10);
            make.right.equalTo(self.contentView).offset(-10);
        }];
        
        ///消息标题标签
        [self.titleLabel mas_makeConstraints:^(MASConstraintMaker* make) {
            make.top.equalTo(self.contentView).offset(10);
            make.left.equalTo(self.customImageView.mas_right).offset(10);
            make.right.lessThanOrEqualTo(self.contentView).offset(-10);
        }];
        
        ///消息内容标签
        [self.contentLabel mas_makeConstraints:^(MASConstraintMaker* make) {
            make.left.equalTo(self.titleLabel);
            make.top.equalTo(self.titleLabel.mas_bottom).offset(8);
            make.right.lessThanOrEqualTo(self.contentView).offset(-10);
            make.bottom.equalTo(self.contentView).offset(-10);
        }];
    
    }
    
    ///底部分割线
    - (void)layoutSubviews {
        [super layoutSubviews];
        self.separatorLineLayer.frame = CGRectMake(59, CGRectGetHeight(self.contentView.frame) - 0.5, CGRectGetWidth(self.contentView.frame), 0.6);
    }
    
    ///格式化时间戳
    - (NSString *)formatDate:(NSString *)timeStr{
        
        //返回当前时间的时间戳
        double timeStamp = [timeStr doubleValue];
        //NSTimeInterval 时间间隔,double类型
        NSTimeInterval time = timeStamp;
        //得到Date类型的时间,这个时间是1970-1-1 00:00:00经过你时间戳的秒数之后的时间
        NSDate * detaildate = [NSDate dateWithTimeIntervalSince1970:time];
        //实例化NSDateFormatter对象
        NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
        //设定时间格式,这里可以设置成自己需要的格式
        [dateFormatter setDateFormat:@"yyyy-MM-dd"];
        //转换成字符串
        NSString * currentDateStr = [dateFormatter stringFromDate:detaildate];
        return currentDateStr;
        
    }
    
    ///设置模型
    - (void)setMdoel:(MessageItemModel *)mdoel{
        self.titleLabel.text = mdoel.title;
        self.contentLabel.text = mdoel.content;
        self.dateLabel.text = [self formatDate:mdoel.send_time];
        self.customImageView.image = [UIImage imageNamed:@"bg_message_notice"];
    }
    
    - (void)awakeFromNib {
        [super awakeFromNib];
    }
    
    - (void)setSelected:(BOOL)selected animated:(BOOL)animated {
        [super setSelected:selected animated:animated];
    }
    
    @end
    
    

    带选择按钮的cell

    //
    //  MessageListItemEditCell.h
    
    #import "MessageListItemCell.h"
    
    UIKIT_EXTERN NSString *const MessageListItemEditCellReuseIdentifier;
    
    @interface MessageListItemEditCell : MessageListItemCell
    
    
    @end
    
    
    //
    //  MessageListItemEditCell.m
    
    
    #import "MessageListItemEditCell.h"
    
    NSString *const MessageListItemEditCellReuseIdentifier = @"MessageListItemEditCellReuseIdentifier";
    
    static CGSize const ReadIdentifierViewSize = {10.0, 10.0};
    
    @interface MessageListItemEditCell()
    
    @property (nonatomic, strong) UIButton *checkoutButton;
    
    @end
    
    
    @implementation MessageListItemEditCell
    
    - (void)setupConstraints {
        
        ///选中按钮
        [self.checkoutButton mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.equalTo(self.contentView).offset(10);
            make.centerY.equalTo(self.contentView);
            make.size.mas_equalTo(CGSizeMake(22, 22));
        }];
        
        ///消息图标视图
        [self.customImageView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.centerY.equalTo(self.contentView);
            make.left.equalTo(self.checkoutButton.mas_right).offset(10);
            make.size.mas_equalTo(CGSizeMake(54.0, 54.0));
        }];
        
        ///未读消息标记视图
        [self.readIdentifierView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.top.equalTo(self.customImageView.mas_top).offset(10 * 0.5);
            make.right.equalTo(self.customImageView.mas_right).offset(-10 * 0.5);
            make.size.mas_equalTo(ReadIdentifierViewSize);
        }];
        
        ///日期标签
        [self.dateLabel mas_makeConstraints:^(MASConstraintMaker* make) {
            make.top.equalTo(self.contentView).offset(10);
            make.right.equalTo(self.contentView).offset(-10);
        }];
        
        ///消息标题标签
        [self.titleLabel mas_makeConstraints:^(MASConstraintMaker* make) {
            make.top.equalTo(self.contentView).offset(10);
            make.left.equalTo(self.customImageView.mas_right).offset(10);
            make.right.lessThanOrEqualTo(self.contentView).offset(-10);
        }];
        
        ///消息内容标签
        [self.contentLabel mas_makeConstraints:^(MASConstraintMaker* make) {
            make.left.equalTo(self.titleLabel);
            make.top.equalTo(self.titleLabel.mas_bottom).offset(8);
            make.right.lessThanOrEqualTo(self.contentView).offset(-10);
            make.bottom.equalTo(self.contentView).offset(-10);
        }];
        
    }
    
    ///懒加载选中按钮
    - (UIButton *)checkoutButton {
        if (_checkoutButton == nil) {
            _checkoutButton = [UIButton buttonWithType:UIButtonTypeCustom];
            [self.contentView addSubview:_checkoutButton];
            _checkoutButton.userInteractionEnabled = NO;
            [_checkoutButton setImage:[UIImage imageNamed:@"bg_check_normal"] forState:UIControlStateNormal];
            [_checkoutButton setImage:[UIImage imageNamed:@"bg_check_selected"] forState:UIControlStateSelected];
        }
        return _checkoutButton;
    }
    
    ///设置模型
    - (void)setMdoel:(MessageItemModel *)model{
        [super setMdoel:model];
        //设置按钮的选中状态
        self.checkoutButton.selected = model.selected;
    }
    
    - (void)awakeFromNib {
        [super awakeFromNib];
    }
    
    - (void)setSelected:(BOOL)selected animated:(BOOL)animated {
        [super setSelected:selected animated:animated];
    
    }
    
    @end
    
    

    操作的模型

    //
    //  MessageItemModel.h
    
    
    #import <Foundation/Foundation.h>
    
    
    @interface MessageItemModel : NSObject
    
    @property (nonatomic, copy) NSString *send_time;
    @property (nonatomic, copy) NSString *title;
    @property (nonatomic, copy) NSString *content;
    @property (nonatomic, assign) BOOL selected;
    
    - (instancetype)initWithModel;
    
    @end
    
    
    //
    //  MessageItemModel.m
    
    
    #import "MessageItemModel.h"
    
    @implementation MessageItemModel
    
    - (instancetype)initWithModel{
        
        self = [super init];
        if(self){
            self.send_time = @"1576139886";
            self.content = @"亲爱的顾客小懒,您的账户有余额变动,变动资金-103.40元,请您及时查看账户资金明细。";
            self.title = @"余额变动提醒";
            self.selected = NO;
        }
        
        return self;
    }
    
    @end
    
    

    控制器

    //
    //  TestTableViewDetelateCellController.h
    
    
    #import <UIKit/UIKit.h>
    
    @interface TestTableViewDetelateCellController : UIViewController
    
    @end
    
    
    //
    //  TestTableViewDetelateCellController.m
    
    
    #import "TestTableViewDetelateCellController.h"
    #import "MessageListItemCell.h"
    #import "MessageListItemEditCell.h"
    #import "MessageItemModel.h"
    
    ///管理视图
    @interface MessageManagementView : UIView
    
    @property (nonatomic, strong) UIButton *checkButton;//全部选中按钮
    @property (nonatomic, strong) UIButton *deleteButton;//删除按钮
    @property (nonatomic, strong) NSString *selectedCount;//选中的消息条数
    @property (nonatomic, strong) CALayer *separatorLineLayer;//顶部分割线
    
    @end
    
    @implementation MessageManagementView
    
    - (instancetype)initWithFrame:(CGRect)frame {
        self = [super initWithFrame:frame];
        if (self) {
            
            self.backgroundColor = [UIColor whiteColor];
            
            ///顶部分割线
            _separatorLineLayer = [[CALayer alloc] init];
            [self.layer addSublayer:_separatorLineLayer];
            _separatorLineLayer.backgroundColor = [UIColor blackColor].CGColor;
            
            ///全部选中按钮
            _checkButton = [UIButton buttonWithType:UIButtonTypeCustom];
            [self addSubview:_checkButton];
            _checkButton.titleLabel.font = [UIFont systemFontOfSize:15];
            _checkButton.titleEdgeInsets = UIEdgeInsetsMake(0, 5.0, 0, -5.0);
            [_checkButton setTitleColor:[UIColor grayColor] forState:UIControlStateNormal];
            [_checkButton setImage:[UIImage imageNamed:@"bg_check_normal"] forState:UIControlStateNormal];
            [_checkButton setImage:[UIImage imageNamed:@"bg_check_selected"]forState:UIControlStateSelected];
            
            ///删除按钮
            _deleteButton = [UIButton buttonWithType:UIButtonTypeCustom];
            [self addSubview:_deleteButton];
            _deleteButton.backgroundColor = [UIColor redColor];
            _deleteButton.titleLabel.font = [UIFont systemFontOfSize:15];
            [_deleteButton setTitle:@"删除" forState:UIControlStateNormal];
            [_deleteButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    
            [self setupConstraints];
        }
        return self;
    }
    
    - (void)setupConstraints {
        ///全部选中按钮
        [_checkButton mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.equalTo(self).offset(10);
            make.centerY.equalTo(self.deleteButton);
        }];
        
        ///删除按钮
        [_deleteButton mas_makeConstraints:^(MASConstraintMaker *make) {
            make.top.right.equalTo(self);
            make.height.mas_equalTo(50);
            make.width.mas_equalTo(50 * 2.0);
        }];
    }
    
    ///布局子视图
    - (void)layoutSubviews {
        [super layoutSubviews];
        _separatorLineLayer.frame = CGRectMake(0, 0, CGRectGetWidth(self.bounds), 0.5);
    }
    
    ///设置选择了多少条消息
    - (void)setSelectedCount:(NSString *)selectedCount {
        _selectedCount = [selectedCount copy];
        if (_selectedCount == nil) {
            _selectedCount = @"0";
        }
    
        NSString *selectedCountFormat = nil;
        selectedCountFormat = [NSString stringWithFormat:@"已选 %@ 条消息", _selectedCount];
        NSMutableAttributedString *selectedAttrCountFormat = [[NSMutableAttributedString alloc] initWithString:selectedCountFormat];
        //设置按钮标题为黑色
        [selectedAttrCountFormat setAttributes:@{NSForegroundColorAttributeName: [UIColor blackColor]} range:NSMakeRange(0, selectedCountFormat.length)];
        //设置消息条数为k红色
        [selectedAttrCountFormat setAttributes:@{NSForegroundColorAttributeName: [UIColor redColor]} range:NSMakeRange(3, _selectedCount.length)];
        //设置按钮标题
        [_checkButton setAttributedTitle:selectedAttrCountFormat forState:UIControlStateNormal];
    }
    
    
    @end
    
    
    
    @interface TestTableViewDetelateCellController()<UITableViewDataSource,UITableViewDelegate>
    
    @property (nonatomic, strong) NSMutableArray *dataSource;//数据源
    @property (nonatomic) BOOL editMode;//编辑模式
    @property (nonatomic, strong) MessageManagementView *managementView;//消息管理视图
    @property (nonatomic, strong) UITableView *tableView;//表视图
    
    @end
    
    @implementation TestTableViewDetelateCellController
    
    - (void)viewDidLoad{
        
        [super viewDidLoad];
        self.navigationItem.title = @"消息盒子";
        [self initDataSource];
        [self setupNavigationBarButtonItem];
        [self setupTableView];
        [self setMessageManagementView];
    }
    
    ///初始化导航栏右侧管理按钮
    - (void)setupNavigationBarButtonItem {
        
        NSDictionary *textAttribute = @{NSFontAttributeName: [UIFont systemFontOfSize:15],
                                        NSForegroundColorAttributeName: [UIColor grayColor]};
        
        self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"管理" style:UIBarButtonItemStylePlain target:self action:@selector(editCollections:)];
        [self.navigationItem.rightBarButtonItem setTitleTextAttributes:textAttribute forState:UIControlStateNormal];
        [self.navigationItem.rightBarButtonItem setTitleTextAttributes:textAttribute forState:UIControlStateHighlighted];
        [self.navigationItem.rightBarButtonItem setTitleTextAttributes:textAttribute forState:UIControlStateDisabled];
    }
    
    ///管理按钮点击事件
    - (void)editCollections:(UIBarButtonItem *)sender {
        
        if ([sender.title isEqualToString:@"管理"]) {
            sender.title = @"完成";
            self.editMode = YES;
        } else {
            sender.title = @"管理";
            self.editMode = NO;
        }
        NSInteger count = 0;
        for (MessageItemModel *model in self.dataSource) {
            //如果模型中有一条消息处于未选中的状态
            if (model.selected == YES) {
                //累加选中消息的条数
                count++;
            }
        }
        //设置消息管理视图选中的消息条数
        self.managementView.selectedCount = [NSString stringWithFormat:@"%zd", (size_t)count];
    }
    
    ///初始化表视图
    - (void)setupTableView {
        self.tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
        [self.view addSubview:self.tableView];
        self.tableView.backgroundColor = [UIColor grayColor];
        self.tableView.dataSource = self;
        self.tableView.delegate = self;
        self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
        self.tableView.rowHeight = UITableViewAutomaticDimension;
        self.tableView.estimatedRowHeight = 70.0;
        [self.tableView registerClass:[MessageListItemCell class] forCellReuseIdentifier:MessageListItemCellReuseIdentifier];
        [self.tableView registerClass:[MessageListItemEditCell class] forCellReuseIdentifier:MessageListItemEditCellReuseIdentifier];
        
        [self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.edges.equalTo(self.view);
        }];
    }
    
    
    ///设置消息管理视图
    - (void)setMessageManagementView {
        
        self.managementView = [[MessageManagementView alloc] initWithFrame:CGRectZero];
        self.managementView.selectedCount = 0;
        self.managementView.hidden = YES;
        [self.view addSubview:self.managementView];
         [self.managementView.checkButton addTarget:self action:@selector(selectAll:) forControlEvents:UIControlEventTouchUpInside];
        [self.managementView.deleteButton addTarget:self action:@selector(deleteMessage) forControlEvents:UIControlEventTouchUpInside];
    }
    
    ///消息管理视图选中按钮的点击事件
    - (void)selectAll:(UIButton *)sender {
        //对选中状态取反
        sender.selected = !sender.selected;
        if (sender.selected) {
            //是选中状态,设置选中的消息条数作为消息管理视图的选中按钮的标题
            self.managementView.selectedCount = [NSString stringWithFormat:@"%zd", (size_t)self.dataSource.count];
        } else {
            //不是选中状态,设置选中的消息条数为0作为消息管理视图的选中按钮的标题
            self.managementView.selectedCount = @"0";
        }
        
        //修改数据源中模型所有的是否选中的状态
        for (MessageItemModel *model in self.dataSource) {
            model.selected = sender.selected;
        }
        //刷新表视图
        [self.tableView reloadData];
    }
    
    ///消息管理视图删除按钮的点击事件
    - (void)deleteMessage{
        //倒序遍历数据源,防止由于删除元素时索引的变化,会造成删除错误
        [self.dataSource enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(MessageItemModel *model, NSUInteger idx, BOOL * _Nonnull stop) {
            //如果模型中的是否选中的状态是选中的,点击删除按钮时将其移出数据源
            if(model.selected){
                [self.dataSource removeObject:model];
            }
        }];
        //设置选中的消息条数为0作为消息管理视图的选中按钮的标题
        self.managementView.selectedCount = @"0";
        //刷新表视图
        [self.tableView reloadData];
        
    }
    
    ///编辑模式改变时执行的方法(管理按钮管理与完成切换时)
    - (void)setEditMode:(BOOL)editMode {
        _editMode = editMode;
        
        if (self.dataSource.count == 0) {
            return;
        }
        if (_editMode) {
            //点击管理按钮时
            self.managementView.hidden = NO;
            [self.managementView mas_remakeConstraints:^(MASConstraintMaker *make) {
                make.left.bottom.right.equalTo(self.view);
                make.height.mas_equalTo(50 + [self bottomPadding]);
            }];
            
            [self.tableView mas_remakeConstraints:^(MASConstraintMaker *make) {
                make.top.left.right.equalTo(self.view);
                make.bottom.equalTo(self.managementView.mas_top);
            }];
            
        } else {
            //点击完成按钮时
            self.managementView.hidden = YES;
            
            [self.tableView mas_remakeConstraints:^(MASConstraintMaker *make) {
                make.edges.equalTo(self.view);
            }];
        }
        //设置消息管理视图选择按钮位未选中状态
        self.managementView.checkButton.selected = NO;
        //刷新表视图
        [self.tableView reloadData];
    }
    
    ///初始化数据源
    - (void)initDataSource{
        self.dataSource = [[NSMutableArray alloc]init];
        for (int i = 0; i < 15; i++) {
            MessageItemModel *model = [[MessageItemModel alloc]initWithModel];
            [self.dataSource addObject: model];
        }
    }
    
    ///返回底部安全区高度
    - (CGFloat)bottomPadding{
        
        if (DEVICE_IS_IPHONE_X) {
            return 34.0;
        } else {
            return 0;
        }
    }
    
    ///消息管理视图的选择按钮是否要处于相中状态
    - (BOOL)checkSelectAll {
        //默认全部是选中的
        BOOL selectAll = YES;
        //继续选中的消息数量
        NSInteger count = 0;
        for (MessageItemModel *model in self.dataSource) {
            //如果模型中有一条消息处于未选中的状态
            if (model.selected == NO) {
                //修改全部选中的状态
                selectAll = NO;
            } else {
                //累加选中消息的条数
                count++;
            }
        }
        //设置消息管理视图选中的消息条数
        self.managementView.selectedCount = [NSString stringWithFormat:@"%zd", (size_t)count];
        //返回消息管理视图的选中按钮是否要选中
        return selectAll;
    }
    
    
    #pragma mark - UITableViewDataSource
    
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
        return self.dataSource.count;
    }
    
    - (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath {
        if (!self.editMode) {
            //未开始管理与管理完成状态的cell
            MessageListItemCell *cell = [tableView dequeueReusableCellWithIdentifier:MessageListItemCellReuseIdentifier];
            [cell setMdoel:self.dataSource[indexPath.row]];
            return cell;
        }else{
            //管理状态的cell
            MessageListItemEditCell *cell = [tableView dequeueReusableCellWithIdentifier:MessageListItemEditCellReuseIdentifier];
            [cell setMdoel:self.dataSource[indexPath.row]];
            return cell;
        }
    }
    
    #pragma mark - UITableViewDelegate
    
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
        
        if (self.editMode) {
            //进入管理状态时
            MessageItemModel *model = self.dataSource[indexPath.row];
            //对模型的选中状态进行取反
            model.selected = !model.selected;
            //刷新点击的的行
            [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
            //消息管理视图的选择按钮是否要处于选中状态
            self.managementView.checkButton.selected = [self checkSelectAll];
        }
    }
    
    - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath{
        if(self.editMode){
            //进入管理状态时不允许单行删除
            return NO;
        }else{
            return YES;
        }
    }
    
    - (NSArray<UITableViewRowAction *> *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath{
        //滑动单行cell以删除
        UITableViewRowAction *deteleAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDestructive title:@"删除" handler:^(UITableViewRowAction * _Nonnull action, NSIndexPath * _Nonnull indexPath) {
            [self.dataSource removeObjectAtIndex:indexPath.row];
            [self.tableView reloadData];
        }];
        deteleAction.backgroundColor = [UIColor blueColor];
        return @[deteleAction];
    }
    
    
    @end
    
    

    用到的宏

    #define DEVICE_IS_IPHONE_X ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1125.0, 2436.0), [[UIScreen mainScreen] currentMode].size) : NO)
    

    效果如下 :


    Jietu20191215-231828.gif

    相关文章

      网友评论

        本文标题:UITableView表视图学习笔记(摘自传智播客)

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