来得学习进程,到底快非常慢
分类:计算机编程

当需要显示学习进度完成的情况,发现以"时间线"的方式显示效果不错,写个简单demo以供参考.

主要实现方式:通过自定义tableViewCell即可.Cell的形式可以换成气泡样式效果.

  • 自定义tableview有很多种方式,这里介绍一种拖拽Table View Controller加建立xib文件的方式完成自定义样式的方法,作为菜鸟,从新手的角度上内容尽量写的详细,话不多说先上两个效果图:

本文的重点并不仅是UITableView的基本使用方法,而是强调有关UITableView和UITableViewCell开发过程中的一些具体细节问题。基本信息请参阅Apple开发文档《Table View Programming Guide for iOS》。

随着iOS11的到来,开发app支持的最低版本也就是iOS8了。iOS8中添加的Self Sizing Cells功能有没有使用过呢?比自己计算cell的高度真的快很多吗?今天写个demo来看一看。demo中会创建两个Controller,分别使用手动计算cell高度的方式和cell自己计算高度的方式加载同一类型同样数据的cell。
Demo地址:https://github.com/huibaoer/Demo_TableViewOptimize.git

一.相关图形剖析
  1. 第一个是我自己做的一个字典app的生词本列表:

概述

Table可能是最擅长于展示数据的一种UI部件。因此UITableView这个类是iOS App开发中除button和Label之外最常用的控件类型。几乎任何一个App都离不开tableView。使用UITableView很简单,核心就是要实现两个protocol。这是由于tableView在MVC中只是V这一环,所以它需要额外的支持提供另外的MC两个环节的功能才能让一个table完整的工作。而这种支持实现的途径就是protocol,Apple要求开发者提供实现** UITableViewDataSource** 和UITableViewDelegate 这两个protocol的支持类来协同对应的UITableView的工作。一般情况下,实现delegate protocol的类是UITableViewController,而data source protocol 可以是controller负责,也可以是其他helper类完成。但更多情况下,我更倾向于单独的modal wrapper类来完成。现在很多的建议data source尽可能不要放在controller中,这样有利于解决mass controller的问题。可以参考objc.io这篇文章:《Lighter View Controllers》

鉴于UITableView是如此的重要,iOS替我们定制化了两种类型的tableView:static和dynamic。前者适用于表格内容相对固定的场景,更多的使用在诸如Setting panel,Detail panel的地方;而后者适用于表格内容不固定,行数动态变化的场景,更多的使用在网络请求返回后,将动态的数据进行内容展示等。虽然Apple内置了几种(确切的说到目前为止是4种)table的style,但是对于App开发而言,大多数情况下都需要自行定制table对数据的展示方式,因此相对应的,table cell的定制化成为App开发的必修课题。

那么下文将介绍一下Static table的使用中需要注意的一些地方和dynamic table中cell开发的方法总结。

1. 编写自定义cell

TableViewCell是一个能展示图片和文字,或者只展示文字的cell,为了快速编写,使用xib方式创建。cell上方是一个640:530的imageView,下方是一个展示文字的label。当传进来的数据有图片和文字的时候就同时显示;当传进来的数据只有文字的时候就隐藏imageView只显示文字。约束一般情况下动态修改都是修改constant值,但是imageView没有高度约束,它的高度是由宽高比计算来的,想使用通过改变imageView的高度达到只显示文字的效果不是很好实现。所以为label分别添加了两个top约束,通过active属性控制哪个约束生效来达到展示图文或者只展示文字的效果。
创建问基本UI后,为cell添加手动计算高度的方法。

  (CGFloat)cellHeightWithDictionary:(NSDictionary *)dic {
    CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
    NSString *image = dic[@"image"];
    CGFloat imageHeight = -10;
    if (image) {
        imageHeight = (screenWidth-32)*530.0/640;
    }
    NSString *text = dic[@"text"];
    NSDictionary *attributes = @{NSFontAttributeName : [UIFont systemFontOfSize:17.f ]};
    CGRect rect = [text boundingRectWithSize:CGSizeMake(screenWidth-32, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading  attributes:attributes context:nil];

    return 16   imageHeight   10   ceil(rect.size.height)   16   0.5;//系统分割线占了0.5
}

添加数据的方法,这里cell根据传入的数据不同,显示不同的内容和样式。

- (void)setDictionary:(NSDictionary *)dic {
    _descLabel.text = dic[@"text"];

    NSString *image = dic[@"image"];
    if (image) {
        _labelTop2Image.active = YES;
        _labelTop2ContentView.active = NO;
        _mediaImgView.hidden = NO;
        _mediaImgView.image = [UIImage imageNamed:image];
    } else {
        _labelTop2Image.active = NO;
        _labelTop2ContentView.active = YES;
        _mediaImgView.hidden = YES;
    }
    [self setNeedsLayout];
}
普通cell显示样式

此处需注意上下两处的延长长度.

标注1: 上方黄色线条的view标注2: 下方黄色线条的view标注3: 中间黄色圆点标注4: 其它地方自定义即可

图片 1时间线cell.png

![](https://upload-images.jianshu.io/upload_images/8605641-60911263f0cc5fd8.PNG)

自定义tableview.PNG

关于Static Table的报错

static table的设置方法很简单,在Xcode中设置UITableView的类型为static即可。
这里着重强调一个问题,绝大多数使用static table view的人都会遇到xcode报出的一个莫名其妙的错误:

error.png

而这个问题的原因是:放置static table的View Controller <u> ** 必须为iOS内置的UITableViewController类型 ** </u>
据说这是xcode的一个bug,但是到目前为止还没有被“修复”的迹象,总而言之,造成的结果就是,如果你想在某个页面放置一个static table view,你必须单独放在UITableViewController中,而且仅仅手动将自己的viewController的类继承自UITableViewController或者在xib中强制改成UITableViewController都不行,必须是原生的UITableViewController。可是很多情况下我们确实需要在自己的viewcontroller中添加一个static table view。怎么办?解决方法是使用Container View

  1. 在你自己的ViewController中拖入一个Container View
  2. 删除这个Container View自动创建的segue和对应的target view controller
containerView.png
  1. 拖入一个新的UITableViewController,加入一个table view,修改类型为static;
  2. Ctrl-drag container view到这个UITableViewController,在弹出的segue类型中选择Embed:
statictable.gif

当然,这种方式只适用于storyboard操作并且要求支持Container VC的iOS版本。在其他情况下,可以直接使用addSubView:,将UITableViewController的view(当然就是tableView)加到自己的“container”view之下。

2. 创建一点假数据

DataAccess类负责数据的提供,demo中就直接本地随机创建的假数据。为了保证两个Controller获取的数据一致。DataAccess就只有在单例初始化的时候创建了一次数据。

static DataAccess *instance = nil;
  (instancetype)sharedInstance {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[[self class] alloc] init];
    });
    return instance;
}

- (instancetype)init {
    self = [super init];
    if (self) {
        _array = [[self class] getData];
    }
    return self;
}

  (NSArray *)getData {
    NSMutableArray *arr = [NSMutableArray array];
    for (int i = 0; i < 99; i  ) {
        NSDictionary *dic = [self generateCellData];
        [arr addObject:dic];
    }
    return arr;
}

  (NSDictionary *)generateCellData {
    NSMutableString *str = [NSMutableString string];
    int random = arc4random()0   1;
    for (int i = 0; i < random; i  ) {
        [str appendString:@"随机生成的内容"];
    }
    NSString *imageName = @"eva_image";
    if (random % 3 == 0) {
        imageName = nil;
    }
    NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithObject:str forKey:@"text"];
    if (imageName) {
        [dic setObject:imageName forKey:@"image"];
    }
    return dic;
}
气泡cell显示样式

增加背景ImageView显示气泡图片.

关于气泡图片:1.如果对Demo中图片不满意,可以自行使用Sketch进行创建.标红区域是需要用到的"剪刀"与"组合"工具.

图片 2SketchBubble.png

2.此处需注意气泡图片的拉伸效果.这里我使用的方法是使用Xcode的Asset Catalog的Slicing.好处是:不需代码去拉伸图片,Xcode会自动识别图片的圆角.使用方法:

图片 3Slicing.png

点击ShowSlicing可以调整拉伸区域.

图片 4ShowSlicing.png

关于图片拉伸的几种方法,查看此链接即可.

  1. 第二个是我参考的文章作者自定的样式,一并放上来对比作参考:

UITableViewCell的使用方法

下文的重点是总结UITableView和UITableViewCell的核心方法。更多详情可以参考Apple的官方文档。

3. 手动计算cell高度的tableView

在FirstViewController中我们让tableView用手动方式计算cell, heightForRowAtIndexPath方法将会被调用多次来计算每个要展示的cell的高度

@interface FirstViewController () <UITableViewDataSource, UITableViewDelegate>
@property (nonatomic, strong) NSArray *dataArray;
@property (nonatomic, strong) UITableView *tableView;
@end

@implementation FirstViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    _dataArray = [DataAccess sharedInstance].array;

    _tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
    _tableView.dataSource = self;
    _tableView.delegate = self;
    [_tableView registerNib:[UINib nibWithNibName:@"TableViewCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:@"cell"];
    [self.view addSubview:_tableView];

}

#pragma mark - tableView
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return _dataArray.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSDictionary *dic = _dataArray[indexPath.row];
    TableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
    [cell setDictionary:dic];
    return cell;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSDictionary *dic = _dataArray[indexPath.row];
    CGFloat cellHeight = [TableViewCell cellHeightWithDictionary:dic];
    return cellHeight;
}
@end
二.主要代码

判断完成情况设置颜色即可.

if (model.isFinsish == 0) { self.FinisnLabel.text = @"未完成"; self.cellImgView.backgroundColor = UnFinsishColor self.TopView.backgroundColor = UnFinsishColor; self.BottomView.backgroundColor = UnFinsishColor;}else{ self.FinisnLabel.text = @"已完成"; self.cellImgView.backgroundColor = FinsishColor self.TopView.backgroundColor = FinsishColor; self.BottomView.backgroundColor = FinsishColor;}

去掉第一个"上方view"与最后一个"下方view".

//定义方法传值当前row-  bindDataWithTimeModel:(TimeModel *)model withRow:(NSInteger)row withArrCount:(NSInteger)count{ } //第一个去掉顶部viewif  self.TopView.hidden = YES;//最后一个去掉底部viewif (row==count-1) self.BottomView.hidden = YES;
![](https://upload-images.jianshu.io/upload_images/8605641-92d4133028ffa485.png)

709739-b7b1d1dc4512528c.png

1. 预定义Cell

iOS自定义了4种常见的Cell格式,在UITableViewCell.h中的注释中Apple给了一些明确的提示这些预置的style一般都适用于什么场景:

typedef enum {
  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).
} UITableViewCellStyle

4种类型在Xcode中对应的选项为:Basic, Right Detail, Left Detail和Subtitle。在iOS8下测试,对应的示例图如下:

  • Basic:
Basic.png
  • Right Detail有图时:
rightdetail1.png
  • Right Detail无图时:
rightdetail2.png
  • Left Detail:
leftdetail.png
  • Subtitle:
subtitle.png

在这4种格式都统一有3个property可以供你使用:

  • textLabel:一个主标题
  • detailTextLabel:一个副标题
  • imageView:一张详情缩略图片

实际上这些预置类型就是把这3种元素做了些取舍然后在不同的位置组合了一下。我觉得在设计App的时候,在任何情况下,设计师和工程师都应当首先考虑这些预置的类型能不能满足需求,除非有足够的必要,否则不要轻易的浪费经历在重复构造定制化的View上。个人觉得Subtile模式已经适用于绝大多数对UI要求不高的场合。你完全可以自己修改这3个控件的一些属性来对UI进行微调。比如你可以尝试将imageView的大小放大一些。

4. cell 自动计算高度

SecondViewController中使用自动计算高度的方式,对比下代码发现去掉了heightForRowAtIndexPath方法,多了estimatedRowHeight和rowHeight两个属性的设置。因为cell内部UI元素的纵向约束都已经加好了,所以cell就可以根据具体的内容自动计算高度撑起来。iOS11中estimatedRowHeight属性默认是开启的,看来苹果已经很推荐我们使用这种方式了,不过一些老代码可能需要将其关闭。

@interface SecondViewController () <UITableViewDataSource, UITableViewDelegate>
@property (nonatomic, strong) NSArray *dataArray;
@property (nonatomic, strong) UITableView *tableView;

@end

@implementation SecondViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    _dataArray = [DataAccess sharedInstance].array;

    _tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
    _tableView.dataSource = self;
    _tableView.delegate = self;
    _tableView.estimatedRowHeight = 300;
    _tableView.rowHeight = UITableViewAutomaticDimension;
    [_tableView registerNib:[UINib nibWithNibName:@"TableViewCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:@"cell"];
    [self.view addSubview:_tableView];
}

#pragma mark - tableView
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return _dataArray.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    TableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
    NSDictionary *dic = [_dataArray objectAtIndex:indexPath.row];
    [cell setDictionary:dic];
    return cell;
}
三.实现效果

普通Cell样式显示效果

图片 5时间线进度.png

Cell气泡样式显示效果

图片 6气泡cell.png

  • 定义tableview及其swift文件主要步骤

2. 自定义Cell

UITableView是通过调用UITableViewDataSource中的tableView:cellForRowAtIndexPath:方法来获取每一行所需要的Cell的,所以绝大多自定义Cell的处理过程都是在这一步完成,另外由于UITableViewCell本身也是一个UIView,所以你也可以在UITableViewDelegate中的tableView:willDisplayCell:forRowAtIndexPath: 方法中对Cell的view做最后的定制化。

注意: Apple明确指出,在tableView:willDisplayCell:forRowAtIndexPath:中应当只修改“state-based properties”,比如selection和background color等等,但是不应该是内容(content),也就是不要在这里进行任何数据处理。

5. 看看结果

demo写完了,我们使用 Instruments 下的 Time Profiler 看看两个页面的tableView的耗时情况,连接真机,开始启动后,我分别将两个tableView的内容滚动到了最低端,没有做其他的操作。可以看出耗时最高的就是手动计算cell高度的方法。看来使用Self Sizing cells还是能提升tableView的不少性能的。

图片 7

Demo_TableViewOptimize.png

四.Demo地址

欢迎star :

  1. 从Xcode中拖出Table View Controller,如图:

    图片 8

    屏幕快照 2018-04-05 下午10.15.07.png

  2. 在Xcode上方点击File->New->File,建立一个Cocoa Touch Class文件,填上你要定义的tableview的名字,并选择继承于UITableViewController,如图:

    图片 9

    new file.png

2.1 自定义Cell的创建

有以下几种方法:

  • ##### 方法1:使用Code继承预定义的cell样式,然后再手动添加自己的view

这是Apple官方文档中的示例,它通过调用initWithStyle:reuseIdentifier:使用UITableViewCellStyleDefault参数来创建一个Cell,为了方便期间,我对代码稍做了些改动,一个是简化了数据加载,另一个是用NSTextAlignment替换了废弃的UITextAlignment:

  #define MAINLABEL_TAG 1
  #define SECONDLABEL_TAG 2
  #define PHOTO_TAG 3

  - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        static NSString *CellIdentifier = @"ImageOnRightCell";
        static int i = 0;
        UILabel *mainLabel, *secondLabel;
        UIImageView *photo;

        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

        if (cell == nil) {
              cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
              cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;

              mainLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, 220.0, 15.0)];
              mainLabel.tag = MAINLABEL_TAG;
              mainLabel.font = [UIFont systemFontOfSize:14.0];
              mainLabel.textAlignment = NSTextAlignmentRight;
              mainLabel.textColor = [UIColor blackColor];
              mainLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight;
              [cell.contentView addSubview:mainLabel];

              secondLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 20.0, 220.0, 25.0)];
              secondLabel.tag = SECONDLABEL_TAG;
              secondLabel.font = [UIFont systemFontOfSize:12.0];
              secondLabel.textAlignment = NSTextAlignmentRight;
              secondLabel.textColor = [UIColor darkGrayColor];

              secondLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight;
              [cell.contentView addSubview:secondLabel];

              photo = [[UIImageView alloc] initWithFrame:CGRectMake(225.0, 0.0, 80.0, 45.0)];
              photo.tag = PHOTO_TAG;
              photo.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight;
              [cell.contentView addSubview:photo];
        } else {
              mainLabel = (UILabel *)[cell.contentView viewWithTag:MAINLABEL_TAG];
              secondLabel = (UILabel *)[cell.contentView viewWithTag:SECONDLABEL_TAG];
              photo = (UIImageView *)[cell.contentView viewWithTag:PHOTO_TAG];
        }

        mainLabel.text = [NSString stringWithFormat:@"Title_%d", i];
        secondLabel.text = [NSString stringWithFormat:@"Description_%d", i];
        i  ;
        NSString *imagePath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"jpg"];
        photo.image = theImage;

        return cell;
  }

最终效果如下:

CustomCell1.png

这里有两个重点:

  1. Cell的重用机制
    重用机制对于UITableView而言是非常重要的概念,这能够显著提高滑动TableView时的性能。如果不使用重用,那么每次新的Cell出现在屏幕上时都需要新创建一个,这对快速滑动而言是非常影响用户体验,并且会占用系统大量的内存开销。iOS的TableView使用的重用机制在原理上很简单,就是系统自动维护一个queue,这个队列放着一定数量的准备好的Cell UI object,滑出屏幕范围之外一定“距离”的Cell都会被回收到这个queue里,给马上要滑入屏幕范围内的Cell复用。
    使用重用的方法核心是两个概念:Cell Identity和dequeue。前者是标识一种具体的Cell的id,后者是将Cell从复用队列中取出的实际操作。首先你需要用这个id告诉系统你将要用于重用的Cell是谁(注册),然后你使用这个id从重用的队列里取出来(复用)。具体到本例中的API就是如下两个:initWithStyle:reuseIdentifier:dequeueReusableCellWithIdentifier:。后面将看到,对于不同情况下创建的Cell,注册和复用的调用也并不相同。

  2. 必须把自定义的控件添加在cell的contentView上,而不是view上,也就是:

     cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
     ...
     // Correct!
     [cell.contentView addSubview:yourCustomComponetView];
     // Wrong!
     // [cell.view addSubview:yourCustomComponentView];
    

再次强调一点,Cell的Content View不是自己的root view。关于Cell View的结构可以参考这篇文章:《制作一个可以滑动操作的 Table View Cell》
这里只摘出最终结论,能够让你对一个Cell的view有直观的理解。
对于一个如图所示的Table而言:

sample.png

它的TableViewCell的View层级结构是:

<UITableViewCell; frame = (0 396; 320 44);> //1
   | <UITableViewCellScrollView; frame = (0 0; 320 44); > //2
   |    | <UIButton; frame = (302 16; 8 12.5)> //3
   |    |    | <UIImageView; frame = (0 0; 8 12.5);> //4
   |    | <UITableViewCellContentView; frame = (0 0; 287 44);> //5
   |    |    | <UILabel; frame = (15 0; 270 43);> //6

这个Cell 里有六个视图:
* UITableViewCell 这是最高层的视图。 Frame 显示它有 320 点宽和 44 点高——宽度和高度都和预期的一致,因为它和屏幕一样宽,而高度就是 44 点。
* UITableViewCellScrollView 虽然你不能直接使用这个私有类,但它的名字很好地暗示了它的功能。它的 Size 和 Cell 的一样。
* UIButton 它在 Cell 的最右边,就是 Disclosure Indicator 按钮。
* UIImageView 是上面 UIButton 的子视图,装载着 Disclosure Indicator 的图像。
* UITableViewCellContentView 另外一个私有类,它包含 Cell 的内容。这个类对于开发者来说就是 UITableViewCell 的 contentView 属性。但它只作为一个 UIView 来暴露在外,这就意味着你只在其上调用使用公开的 UIView 方法;而不能使用任何与这个类关联的任何私有方法。
* UILabel 显示 “Item #” 文本。

很显然,这里cell.contentView并不是cell.view。

  • ##### 方法2:使用xib绘制Custom Cell View

这是自定义Cell最常见也是最省力的方法:

  1. 首先在xcode中创建custom xib,拖拽需要的控件到xib上,并做好布局和限制;
    2)新建自己的custom class,定义对应的outlet properties;
    3)将xib的class设置成对应的custom class,然后将outlet和控件连接起来;
    4)在适当的位置(一般为初始化的地方)调用registerNib:forCellReuseIdentifier:注册Cell;
    5)在tableView:cellForRowAtIndexPath:中调用dequeueReusableCellWithIdentifier:forIndexPath:复用cell

下面的这个示例演示了上述步骤。在这个示例中,首先创建Custom Cell和xib的界面,其中有一张大图,下面有两个独立的label,然后我使用了tableView:willDisplayCell:forRowAtIndexPath:对每一行Cell的背景颜色做出最终设置,而在tableView cellForRowAtIndexPath:中对Cell的内容进行填充:

Custom View:

@interface MyTableCellView : UITableViewCell
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property (weak, nonatomic) IBOutlet UILabel *name;
@property (weak, nonatomic) IBOutlet UILabel *date;
@end

Custom Xib:

CustomXib.png

在table View Controller中:

  • (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup here ...
    ...
    // register the cell to tell the system preparing to reuse it.
    [self.tableView registerNib:[UINib nibWithNibName:@"TableCellView" bundle:nil] forCellReuseIdentifier:cellId];
    }

      - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
          // always reuse the cell
          MyTableCellView *cell = [tableView dequeueReusableCellWithIdentifier:cellId forIndexPath:indexPath];
    
          cell.frame = CGRectMake(0, 0, [[UIScreen mainScreen] bounds].size.width, 250);
          NSString *path = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"jpg"];
          cell.imageView.image = [UIImage imageWithContentsOfFile:path];
          NSDate *object = self.objects[indexPath.row];
          cell.date.text = [object description];
          return cell;
      }
    
      - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
      {
          NSLog(@"indexpath = %ld", indexPath.row);
          if ( indexPath.row % 2 == 0) {
            cell.backgroundColor = [UIColor redColor];
          }
          else {
            cell.backgroundColor = [UIColor greenColor];
          }
      }
    

Demo:

CustomCell2.png

  • ##### 方法3:使用code定义一个Custom Cell Class

这个在本质上和上一种使用xib的方法是一样的,只不过一个是用代码完全定制,一个是借助xib操作。但是在Cell重用的方式上有一定区别。上一种方法中,需要调用registerNib:forCellReuseIdentifier:来注册重用的Cell,在这里就需要用registerClass:forCellReuseIdentifier:方法来注册。与之对应的,获取重用的Cell的时候,调用dequeueReusableCellWithIdentifier:forIndexPath:方法。

Class myClass = [MyTableCellView class]; 
[self.tableView registerClass: myClass forCellReuseIdentifier:@"CustomCell"];
...
...
MyTableCellView *cell = [self.tableView dequeueReusableCellWithIdentifier:@"CustomCell" forIndexPath:path];
cell.label.text = @"text";
...

注意 dequeueReusableCellWithIdentifier:dequeueReusableCellWithIdentifier:forIndexPath:的区别!!

  • 如果你注册过Cell,在没有可用的cell时,前者会返回nil;而后者永远都会从注册的nib或者class中替你创建一个可用的Cell。也就是说,前者调用你需要手动检查nil,而后者不需要;
  • 如果你从没有注册过cell,在没有可用的cell时,前者会返回nil,后者……直接崩溃!也就是说,调用后者你 必须确保注册过cell
6. TableView的一些其他优化
  • cell复用,cell的复用是tableView展示内容的一个良好机制,可以有效的减少cell对象的个数减少内存占用,需要注意的是根据业务需要尽量减少cell的类型,理论上可以减少cell复用池中的总个数。另外自定义cell的一些UI不要在cell的调用过程中频繁创建,尽量在cell创建时只创建一次,通过显示隐藏等方式使cell展示不同的样式。nib文件可以方便我们创建UI添加约束,但是相较于纯代码编写,纯代码的效率更高,可根据实际情况取舍。
  • cell高度的计算,传统的手动计算cell高度的方式,尽量避免在heightForRow方法中频繁计算,可考虑在数据获取后,统一计算一次cell高度存起来,在heightForRow方法中直接返回。现在Self Sizing Cells是另一种更好的优化方式。
  • tableView在快速滚动时,会频繁调用cellForRow方法获取cell,尽量快速的返回cell可以避免由于等待cell的返回造成的卡顿。建议在cellForRow方法中不要对cell做过多的设置和数据绑定,可以将这些操作放在willDisplayCell回调中。
  • 在实际项目中,往往性能瓶颈在网络请求,适当的做一些缓存可以提升用户体验。
五.参考资料

1.

有任何问题请私信或者留言.

![](https://upload-images.jianshu.io/upload_images/8605641-a1e4c3f1b927a6d8.png)

屏幕快照 2018-04-05 下午10.24.31.png

2.2 访问Cell中的控件:

  • xib中使用outlet
    这个应该不用多说,在Cell的原型中(不管是Static cell还是Dynamic cell)定义outlet properties,然后在xib中拖拽连接对应的控件即可;Apple官方文档上的示意图:

connect_outlet.jpg

connect_static_objects.jpg

  • 代码中使用viewWithTag:
    这个是获取parent view上某个特定view的快捷方法,首先需要设置一个sub view的tag,然后使用viewWithTag:来访问这个sub view。设置时可以通过xcode在xib中设置tag标签,也可以直接通过tag property手动设置:

创建时:

mainLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, 220.0, 15.0)];
mainLabel.tag = MAINLABEL_TAG;
// customize the label here...
[cell.contentView addSubview:mainLabel];

访问时:

    mainLabel = (UILabel *)[cell.contentView viewWithTag:MAINLABEL_TAG];
  1. 点击之前拖拽出来的Table View Controller上方最左边的按钮,将你新建的Cocoa Touch Class文件填入Custom Class中的Class与之关联,如图(注: 图中的myTableViewController就是你刚才新建的Cocoa Touch Class文件的类名):

    图片 10

    关联.png

有关Table和Cell的性能需要注意的问题

关于这个话题,Apple并没有用过多的篇幅介绍,但是在官方开发文档中明确提出了3点意见:

  1. 注意重用(Reuse Cell)
    这点我们已经在上文中着重强调过了。

  2. 避免反复的调整Cell的布局(Avoid relayout of content)
    只在创建每一个Cell的时候布局一次,而不要每一次获取Cell的时候都去重新布局。

  3. 避免透明的subviews (Use opaque subviews)
    自定义cell的时候,尽可能避免使用透明的控件,因为透明控件在table滑动时将增大渲染开销。

至此我们建立一个tableview的步骤就大致完成了,此时Xcode已经自动帮你生成了tableview相关的代码,大部分在注释中1,需要哪个功能删除`/* */`并修改代码即可:  

![](https://upload-images.jianshu.io/upload_images/8605641-584058493ae9f974.png)

屏幕快照 2018-04-05 下午10.29.01.png

总结

本文主要从Static table和Dynamic table两方面总结了UITableView和UITableViewCell的核心使用方法和问题,并着重介绍了Custom Cell的几种方法和注意事项。

另外,有兴趣的话,UITableView还有另外几个比较关键的功能可以继续研究,一个是Editing mode,一个是Table Index,还有一个相对也很重要的功能自定义Header和Footer View。有空的话可以再总结一下。希望此篇能帮助到您。

2016年4月5日,完稿于南京。

  • 建立xib文件步骤
  1. 建立一个tableviewCell样式的xib和对应的swift文件,如下图所示:
    (注:此处直接拿网上的图,所以创建的文件名和我的不一样,图片上的是CustomTableViewCell而我实际创建的是NewCellTableViewCell,下面会用到,特此说明)

    图片 11

    建立xib文件

  2. 建立完cell 文件之后我们就可以在CustomTableViewcell.xib文件中来进行编写自定义的单元格了:

  • 首先设置一下xib文件中table view cell中的content view背景为Light Gray Color:

图片 12

设置color

  • 然后,在content view中铺一层View,注意不要铺满整个content view,因为要实现开头效果的话,边缘要露出一点灰色(上面一个步骤已经将content view的背景设为Light Gray Color了)
  • 最后在view中铺上你要定义的样式,我的单词表(如图)加了几个标签,此外你还可以铺Image View,Text View等等:

图片 13

自定义Cell

  1. 在xib文件中对应的swift文件(上文创建xib文件时同时创建的swift文件NewCellTableViewCell)加入添加的这些控件,下面是我文件中添加的这些控件代码:
//
//  newCellTableViewCell.swift
//  dicEg3
//
//  Created by HJ on 2018/3/26.
//  Copyright © 2018年 sherry. All rights reserved.
//

import UIKit
import CoreData
import Foundation

class newCellTableViewCell: UITableViewCell {

    @IBOutlet weak var customView: UIView!
    @IBOutlet weak var titleName: UILabel!
    @IBOutlet weak var visitTime: UILabel!
    @IBOutlet weak var searchTimes: UILabel!
    @IBOutlet weak var detailImage: UILabel!


    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
        // 设置cell 圆角
        customView.layer.masksToBounds = true
        customView.layer.cornerRadius = 10
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

}
  • 初始化函数override func awakeFromNib()中的customView.layer.masksToBounds = truecustomView.layer.cornerRadius = 10是将cell设置为圆角

定义完cell的xib文件后,就是要将它放入tableview中了,下面是步骤:

  • 设置myTableViewController.swift文件
  1. 在初始化文件override func viewDidLoad()中加入加载cell代码:
 self.tableView.register(UINib(nibName:"newCellTableViewCell", bundle:nil), forCellReuseIdentifier:"newCellTableViewCell")
  1. 然后开始将cell加载到tableview 中,其代码如下图所示(这一部分需要按照你自己的实际情况改,下面只是展示一下我大概的代码做个参考,一些无关主题的细节就不放了):
// MARK: - Table view data source
//设置不同种类的单元格的样式有多少
override func numberOfSections(in tableView: UITableView) -> Int {
        // #warning Incomplete implementation, return the number of sections
        return 1
 }
//设置所需要的单元格的个数
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete implementation, return the number of rows
        switch section {
        case 0:
            return items.count
        case 1:
            return items.count
        default:
            return items.count
        }
}
//在单元格中加载cell
 override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell : newCellTableViewCell = tableView.dequeueReusableCell(withIdentifier: "newCellTableViewCell") as! newCellTableViewCell
//        cell?.textLabel?.numberOfLines = 0       // 根据内容显示高度

        print(self.items)
        self.mainViewModel.getSearchDate(infoName: items[indexPath.row], found: { (word) in
            cell.titleName.text = self.items[indexPath.row]
            cell.visitTime.text = word.date
            cell.searchTimes.text = "查询次数:"   String(word.count)
            print("cell中的内容n"   cell.titleName.text!   "n"   cell.visitTime.text!)
        })

        return cell
}
//设置单元格的大小
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 120
}
  1. 到这里,如果你成功跟着做出了你想要的tableview,你可能会发现下面这个问题:我们可以看到tableview的线条还存在,而且下面的显示还是白色的
    (如图,图片直接选自参考文章的博主,他的图片看上去明显一点):
![](https://upload-images.jianshu.io/upload_images/8605641-eb6580eb6058a5c1.png)

bug图片
  • 下面做一些设置让这些线条消失:将tableview中separtor设置为none,和view中的background的颜色设置与cell中的背景颜色一样即可:

图片 14

设置separtor

图片 15

设置背景颜色

最后,贴一下参考文章的地址:自定义tableview样式

本文由pc28.am发布于计算机编程,转载请注明出处:来得学习进程,到底快非常慢

上一篇:有线打字与印刷,AirPrint无线打字与印刷 下一篇:没有了
猜你喜欢
热门排行
精彩图文