zoukankan      html  css  js  c++  java
  • iOS:UITableView相关(18-10-20更)

    UITableView用得较多,遇到的情况也较多,单独记录一篇。

    一、零散的技巧

    二、取cell

    三、cell高度

    四、导航栏、TableView常见问题相关

    五、自定义左滑删除按钮图片

    六、仅做了解

    一、零散的技巧

    1、 cell的选中效果是cell的属性,可以有的有,无的无。

    // 自定义cell
    self.selectionStyle = UITableViewCellSelectionStyleNone;
    // 取cell
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    

    2、cell的下划线是Table的属性,全部有,或全部无。

    self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
    

    3、cell下划线左边顶住屏幕左边。

    cell.preservesSuperviewLayoutMargins = NO;
    cell.layoutMargins = UIEdgeInsetsZero;
    cell.separatorInset = UIEdgeInsetsZero;
    

      后续补充:也可以隐藏掉系统的下划线,自定义LineView,要多宽就多宽,且可以实现不同cell不同下划线样式。

    4、cell的重用ID,可以用类名

    NSStringFromClass([MyCell class])
    

    5、根据 indexPath 获取 cell

    [self.mTableView cellForRowAtIndexPath:indexPath];
    

    6、根据 cell 获取 indexPath

    [self.mTableView indexPathForCell:cell];
    

    7、superView

    // 第一个superview 是contentView,第二个就cell
    UITableViewCell *cell = (UITableViewCell*)button.superview.superview; 
    

    8、UIScrollView 和 UITableView 的 内边距差别

    // 自动偏移 contentOffset = CGPointMake(0, -200);
    tableView.contentInset = UIEdgeInsetsMake(200, 0, 200, 0);
    
    // 需要设置偏移量。否则停留在偏移量(0.0)。需要再下拉一下,
    scrollView.contentInset = UIEdgeInsetsMake(200, 0, 200, 0);
    scrollView.contentOffset = CGPointMake(0, -200);
    

    9、监听 contentOffset ,可以得到类似拖动代理的效果。如写第三方给别人用。

    - (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    
    }
    

    10、取消cell的左滑 编辑、删除 状态。如,按了其他位置的按钮,cell不会自动复原。

    [self.mTableView setEditing:NO animated:YES];
    

    11、style ,风格样式

    // 分组 风格
    //    1、自动间隔开每组
    //    2、如需设置间隔需要注意,组头组尾都要处理。(坑过一次,只设置组尾高度,结果发现怎么还很高,而且不显示不能为0,要 = CGFLOAT_MIN)
    //    3、滑动,组头不会悬停
    self.mTableView = [[UITableView alloc]initWithFrame:CGRectZero style:UITableViewStyleGrouped];
    
    // 扁平化 风格
    //    1、每组的间隙可通过组头、组尾,自行调整。(相对上面风格,组头组尾高度默认为0)
    //    2、滑动,组头组尾会悬停
    self.mTableView = [[UITableView alloc]initWithFrame:CGRectZero style:UITableViewStylePlain];
    

    12、获取当前显示的cells

    // 直接得到 cells
    self.mTableView.visibleCells
    // 得到 indexPath ,看需求通过 cellForRowAtIndexPath: 转换。
    self.mTableView.indexPathsForVisibleRows
    

    13、滑动时,使用低分辨率图片,停止时再加载高分辨率图片。(利用 代理 和上面 “12、获取当前显示的cells” )

    - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;
    - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;  
    

    14、UIScrollView、UITableView 实时 位置 相关

      参照 《iOS:手势与矩形、点运算相关》 -> “1、矩形、点运算” -> “4、UIScrollView、UITableView 实时 位置 相关”

    15、拖动状态。比如判断当前滚动是否拖动引起。拖动的代理只有开始和结束,拖动中没有。

    mScrollView.dragging
    

    16、滚动到具体的cell位置。如,1、外卖,左右tableView联动。2、聊天,滚动到最新信息。

        if (self.dataSource.count > 0) {
            [self.mTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:self.dataSource.count-1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom
    animated:YES];
        }
    

    17、选中效果,动画

    // 用途可参考外卖、电商类APP(左边tbV的分类,随着右边商品的拖动,跟新cell选中位置)
    [self.leftTableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:section inSection:0] animated:YES scrollPosition:UITableViewScrollPositionTop];
    

      

      

    N、如果一个 tableView 对应多个 dataSource 。通过按钮切换,那么要考虑,点击/滑动 切换时,请求返回的数据,是否是当前 “功能选中”的位置,比如:

      判断对比请求前后的字段 parameterDics、状态。若不是,

        1)、可丢弃。

        2)、可刷新该状态对应的 dataSource 数组(有的话),下次切换,可先刷出数据,再请求。界面友好(防止网络请求,一片空)。

    边输入边搜索,同理,避免,如快速删除完后,又刷出删除前的请求数据。

      

     

    二、取cell

    1、cell初始化的一些区别

    1)、TableViewCell

    1-1)、没注册

    没注册的(一开始会取不到):
    cell  = 从队列取
    if(cell取不到)
    {
    	创建cell
    	创建子视图,加tag
    }
    cell从tag取子视图,刷新 tag 或 属性
    

    1-2)、注册

    注册的(100%取得到):
    cell  = 从队列取(有indexPath的方法)
    刷新 tag 或 属性
    
    (
    	系统取不到,会走自定义的initWithStyle:reuseIdentifier:
    	if(cell创建成功)
    	{
    		创建子视图,加tag
    	}
    )
    

    2)、CollectionViewCell

    2-1)、没注册

    2-2)、注册

    注册的(100%取得到):
    cell  = 从队列取(有indexPath的方法)
    if(cell取得到)
    {
    	(判断是否有子视图)创建子视图
    }
    刷新 tag 或 属性
    
    
    collectionViewCell 流程有点不同
    	1、没 TableViewCell 的 initWithStyle:reuseIdentifier:
    	2、但 每次都能从队列取到
    	3、所以 需要判断取到的cell是否有子视图,不然会不断创建
    

    2、加载XIB

    1)、从多个cell样式的XIB加载。只有1个cell样式,可直接lastObject加载。(先根据不同的ID取,取不到再加载。)

      1-1)、获取XIB里的所有对象

    NSArray *cellArry = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([MyTableCell class]) owner:self options:nil];

       1-2)、读取对应的Cell样式,此时的参数type为枚举,或基本数据类型。

    cell = [cellArry objectAtIndex:type];

    2)、在 UIView + xxx 的类别文件里,可以添加这个类。方便加载单种Cell样式的XIB。

    + (instancetype)viewFromXib
    {
        return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self) owner:nil options:nil] lastObject];
    }
    

    三、cell高度

    0、不固定内容的cell,可弄数组、模型存高度,以免每次计算。

      貌似系统计算的(下面的4、5、),耗时都长?复杂的cell滑动不流畅?所以还是能手动就手动咯(下面的2、3、)?

      还有,如果是富文本,记得要把font加进去计算,经常算行距的时候,忘了字体大小。   

    1、全部固定高度

      self.tableView.rowHeight = 44;

    2、自定义cell类方法

    + (CGFloat)getCellHeight
    {
        return 44;
    }
    
    + (CGFloat)getCellHeightWithData:(id)data
    {
        // 手动计算label的高度
        return 计算高度;
    }
    

      后续补充:对于固定高度,没问题,好用。对于根据Data计算的,根据情况保存计算高度。

    3、模型(只有属性的特殊类)

      后续补充:通过get方法读取。需要才计算(懒加载),可能还要判断是否计算过,否则每次都要计算?

    4、系统自动计算(iOS6后,使用 UIView 的 类别 UIConstraintBasedLayoutFittingSize 的方法,控件需要全是 Autolayout 约束?)

    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        // 取出不带 indexPath 的
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([MyCell class])];
        
        // 填充数据
        //cell.model = model[indexPath.row];
        [cell initData:data[indexPath.row]];
        
        // 计算高度
        // UILayoutFittingCompressedSize 返回最小可能的值
        // UILayoutFittingExpandedSize 返回最大可能的值
        cellHeight = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height + 0.5f;
        
        return cellHeight;
    }
    

      后续补充:1、根据情况保存计算高度。  

           2、普通View非Cell,的高度计算也可用,但同样要 Autolayout 约束。

           3、注册cell,一般是取出带indexPath的。不带indexPath一般是在自写cell重用机制的用的。

              但是,注册cell 还可以取出普通的cell样式,不带 indexPath。填充数据,计算高度。

           4、对3、补充,如果是xib可以用 NSBundle。

           5、对3、再补充,可以弄个局部变量,用懒加载获取普通cell,不用每次都获取。

           6、label类,多行,除了  label.numberOfLines = 0。

                好像还需要设置  label.preferredMaxLayoutWidth = SCREEN_WIDTH - 20 ;

    5、系统自动计算(iOS8后,UITableViewAutomaticDimension,控件需要全是 Autolayout 约束?)

      1)、先给cell高度一个估算值,好让TableView,知道contentSize有多大

    tableView.estimatedRowHeight = 80.0f;
    

      2)、设置为自动计算

    tableView.rowHeight = UITableViewAutomaticDimension;
    

    iOS8后,UITableViewAutomaticDimension自动计算,不用实现 heightForRowAtIndexPath 了,不过为了兼容ios8前,可能需要再写、判断

    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
    {
    	if ( [[[UIDevice currentDevice] systemVersion ] integerValue] >= 8) 
    	{
    		return UITableViewAutomaticDimension;
    	}
    	else
    	{
    		
    	}
    }
    

     

    四、导航栏、TableView常见问题相关

    1、导航栏、TableView

    //调整contentInset。
    //NO:不调整,按设定的frame、contentInset的显示
    //YES:会调整contentInset.top的高,让显示的顶在导航栏下面,【有滑过半透明效果】
    self.automaticallyAdjustsScrollViewInsets =NO;
    
    //调整frame
    //    UIRectEdgeNone   //会顶在导航栏下面【没有滑过半透明效果】
    //    UIRectEdgeTop    //对齐原点
    //    UIRectEdgeLeft   //对齐左边
    //    UIRectEdgeBottom //对齐顶部
    //    UIRectEdgeRight  //对齐右边
    //    UIRectEdgeAll    //对齐所有
    self.edgesForExtendedLayout = UIRectEdgeNone;
    
    //导航栏半透明
    self.navigationController.navigationBar.translucent = YES;
    
    //隐藏navigationBar(1、它推过的所有的VC共用1个Bar;2、用继承View的hidden属性,隐藏不了!)
    self.navigationController.navigationBarHidden=YES;
    

     

    2、iOS11

        此处参考自简书 “iOS 11 安全区域适配总结 ” -- sonialiu

    1)、TableView 默认开启Cell高度估算,关掉。

    [UITableView appearance].estimatedRowHeight = 0;
    [UITableView appearance].estimatedSectionHeaderHeight = 0;
    [UITableView appearance].estimatedSectionFooterHeight = 0;
    

    2)、ScrollView新增安全区域。

      2-1)、如果之前让TabelView顶住屏幕,然后设置顶部内边距 = 20+44,刚好在导航栏下面的话,

            会被系统向下偏移64的 SafeAreaInsets,再加上自己设置的64,就出现下移64问题。

      2-2)、同理,没导航栏的时候,也会下移20 -> 状态栏的高度。

      2-3)、以前若设置 automaticallyAdjustsScrollViewInsets  = YES 让系统自动调整,不会有问题

     解决方案:添加下面,相当于 automaticallyAdjustsScrollViewInsets = NO

    #ifdef __IPHONE_11_0   
    if ([tableView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) {
        [UIScrollView appearance].contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
    }
    #endif
    

      2-4)、contentInsetAdjustmentBehavior 其他类型

    UIScrollViewContentInsetAdjustmentScrollableAxes:  adjustedContentInset = ( 可滚动方向 ? safeAreaInset + contentInset : contentInset );

    UIScrollViewContentInsetAdjustmentNever:         adjustedContentInset = contentInset;

    UIScrollViewContentInsetAdjustmentAlways:       adjustedContentInset = safeAreaInset + contentInset;

    UIScrollViewContentInsetAdjustmentAutomatic:    (controller里automaticallyAdjustsScrollViewInsets = YES) && (controller被navigation包含) == Always,否则 == Axes

    五、自定义左滑删除按钮图片

        参考自简书 《【支持iOS11】UITableView左滑删除自定义 - 实现多选项并使用自定义图片 》 -- pika11

    0、写在前面

      尽管iOS11已经支持自定义删除图片了,但还是要兼容以前的。

    1、进入编辑模式,标记view为需要layout。

    - (void)tableView:(UITableView *)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath
    {
        self.editingIndexPath = indexPath;
        [vc.view setNeedsLayout];
    }
    
    - (void)tableView:(UITableView *)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath
    {
        self.editingIndexPath = nil;
    }
    

    2、在VC的,layout子View完成的时候,判断是否需要改变cell删除样式

    -(void)viewDidLayoutSubviews
    {
        [super viewDidLayoutSubviews];
        
        if (self.dataSource.editingIndexPath != nil)
        {
            // 改变cell 删除文字 为 删除图片
            [self resetCellDeleteButton];
        }
    }
    

    3、配置,不同系统

    - (void)resetCellDeleteButton
    {
        // 获取选项按钮的reference
        if ( [[[UIDevice currentDevice] systemVersion] integerValue] >= 11  )
        {
            for (UIView *subview in self.mTableView.subviews)
            {
                //iOS11(Xcode 8编译): UITableView -> UITableViewWrapperView -> UISwipeActionPullView
                if ([subview isKindOfClass:NSClassFromString(@"UITableViewWrapperView")])
                {
                    for (UIView *subsubview in subview.subviews)
                    {
                        if ([subsubview isKindOfClass:NSClassFromString(@"UISwipeActionPullView")] && [subsubview.subviews count] >= 1)
                        {
    #warning - 可能需要判断类型再去改,会比较好,暂时没去试。
                            UIButton *deleteButton = subsubview.subviews.lastObject;
                            [self configDeleteButton:deleteButton];
                        }
                    }
                }
                //iOS11(Xcode 9编译): UITableView -> UISwipeActionPullView
                else if ([subview isKindOfClass:NSClassFromString(@"UISwipeActionPullView")] && [subview.subviews count] >= 1)
                {
    #warning - 可能需要判断类型再去改,会比较好,暂时没去试。
                    UIButton *deleteButton = subview.subviews.lastObject;
                    [self configDeleteButton:deleteButton];
                }
            }
        }
        else
        {
            // iOS8-10: UITableView -> UITableViewCell -> UITableViewCellDeleteConfirmationView
            SignCell *tableCell = [self.mTableView cellForRowAtIndexPath:self.dataSource.editingIndexPath];
            for (UIView *subview in tableCell.subviews)
            {
                if ( [subview isKindOfClass:NSClassFromString(@"UITableViewCellDeleteConfirmationView")] && [subview.subviews count] >= 1)
                {
                    UIButton *deleteButton = subview.subviews.lastObject;
                    [self configDeleteButton:deleteButton];
                }
            }
        }
    }
    

    4、实现删除样式

    - (void)configDeleteButton:(UIButton*)deleteButton
    {
        deleteButton.backgroundColor = kBgColor;
        [deleteButton setTitle:@"" forState:UIControlStateNormal];
        [deleteButton setImage:[UIImage imageNamed:@"delete"] forState:UIControlStateNormal];
    }
    

    六、仅做了解

    1、cell 异步加载网络图片,主线程更新UI。

      1)、现在有了 SDWebImage ,只做为一种思路了解

      2)、重用机制。

        在取 cell 的同时刷新 imageView 的 tag ,当 imageView 异步获取到图片,判断自己的 tag 还是不是请求前传进来的 index + basetag。

        如果不加这样的判断,当网络差,会出现图片错位的情况。

      3)、cacheDic。

          目前写法,只是一个可变字典。

          往后考虑,1)、获取图片前,先判断自定义缓存NSCache(或者字典)是否有相应url名的图片。

                  1)、有 ->  直接调用

                  2)、没有 ->  去本地查找,url名的图片

                     1)、有,提取到缓存NSCache。key = url,object = image。调用

                     2)、没有?请求,以url命名保存到本地、缓存,调用。

               2)、NSCache设置一定大小,会自动删除旧。如缓存读不到,又会去本地读取,并刷新到NSCache里。

          缓存缺陷,如果后台更新图片,且名字用原来的,就不会被刷新。 

    0、宏定义
    #define kImageBaseTag   2000
    
    1、判断数据
    // 更新imageView的标签
    imgView.tag = indexPath.row + kImageBaseTag;
    // 在单元格显示的时候,先清掉
    imgView.image = nil;
    // 判断是否加载过,有就用,没有就请求
    if ([[self.imageCacheDic allKeys] containsObject:self.dataSource[indexPath.row]]) {
        imgView.image = [self.imageCacheDic objectForKey:indexPath];
        
    }else{
        dispatch_async(_queue, ^{
            NSURL *url = [NSURL URLWithString:self.dataSource[indexPath.row]];
            [imgView requestImgFromUrl:url cache:self.imageCacheDic index:indexPath];
        });
    }
    
    2、请求数据
    #import "UIImageView+MyWebCache.h"
    -(void)requestImgFromUrl:(NSURL*)url cache:(NSMutableDictionary*)cache indexPath:(NSIndexPath*)indexPath
    {
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
        [request setURL:url];
        [request setHTTPMethod:@"GET"];
        [request setTimeoutInterval:3];
        
        [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            
            UIImage *image = [UIImage imageWithData:data];
            
            dispatch_async(dispatch_get_main_queue(), ^{
                if (image != nil) {
                    // 添加到缓存
                    [cache setObject:image forKey:indexpath];
                    // 判断是否需要刷新
                    if (self.tag == indexPath.row + kBaseImageTag) {
                        self.image = image;
                    }
                }
            });
        }] resume];
    }
    

    2、自定义循环池

    NSMutableSet *recyclePool;
    MyCell *cell = [self.recyclePool anyObject];
    if (cell)
    {
        // 从循环池内取出
        [self.recyclePool removeObject:cell];
    }
    else
    {
        // 创建
        cell = [MyCell cell];
    }
    
    // 超出屏幕再 add 进循环池
    

    3、图片缓存相关

      参照《iOS:图片相关》

  • 相关阅读:
    汇编基础概念
    linux实验小结
    乐视三合一体感摄像头开发记录
    IplImage* 格式与Mat 格式的转换
    STM32关于优先级设定的理解 NVIC_SetPriority()
    "IRQn_Type" is undefined
    串口通信实验编译没有错误,但是串口调试助手收不到数据
    SecureCRT 8版本 自用备份
    Matlab使用robot Toolbox
    开篇
  • 原文地址:https://www.cnblogs.com/leonlincq/p/7645464.html
Copyright © 2011-2022 走看看