[IOS]

UITableView 表视图 是IOS使用非常频繁的布局视图

UITableView 什么样子呢? 一般用在什么地方呢?看下边的图

[IOS]
像是电话薄,好友列表 这种列表排列的视图一般都是使用UITableView实现的

UITableView 一共包含两种内置的布局格式:

  1. UITableViewStylePlain 普通的表格样式(默认)
  2. UITableViewStyleGrouped 带有分组的表格样式

在项目中如何使用UITableView,UITableView相较于其他基础视图对象使用还是稍微复杂一点点的

UITableView 的使用主要触及到了这么几个类:

  1. UITableView 列表视图,常用API
  2. UITableViewCell 列表中每个单元格的具体样式
  3. UITableViewDelegate 包含列表一些基本操作的功能,常用API
  4. UITableViewDataSource 主要用于管理数据并为表视图提供单元格对象, 常用API

先来一个列表视图尝尝鲜,

  1. 创建一个UITableView对象:
// 1. 实例化一个tableView对象
UITableView* tableView = [[UITableView alloc] init]];
// 2. 为tableView设置视图位置和大小(这里设置为屏幕大小)
[tableView setFrame:[UIScreen mainScreen].bounds];
// 3. 为tableView设置基础代理与数据代理(这里将列表的相关代理设置为了当前对象)
[tableView setDelegate:self];
[tableView setDataSource:self];
// 4. 将列表视图添加到当前显示的视图中
[self.view addSubview:tableView];
  1. 上边将self设置为了列表视图的代理对象,所以我们需要实现代理协议中的相关方法,为列表视图对象的显示提供必需的支持
// 1. 确定列表有多少组数据的协议方法(这个协议方法不是必须实现的,默认返回1组)
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}
// 2. 确定列表中每组有多少行的协议方法(必须实现);  section是当前组的序号(真实环境中可能需要根据每组返回不同的单元格数量,这里只是演示一下返回了10个单元格)
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return 10;
}
// 3. 确定每个单元格的具体对象(必须实现); indexPath中包含了当前单元格的组和行
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell* cell = [[UITableViewCell alloc] init];
    [cell.textLabel setText:[NSString stringWithFormat:@"%ld", indexPath.row]];
    reutrn cell;
}
  1. 执行后的样子
    [IOS]

上边只是简单实现了列表的基本显示(其实这样实现是有问题的),还有更多的(自定义cell,cell注册,cell重用,单元格操作)等相关需要了解的技能

简单的自定义cell, 在cell中有一个contentView属性(一般都是将视图添加到这个视图中自定义cell单元格)

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell* cell = [[UITableViewCell alloc] init];
    // 头像图片
    UIImageView* imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:[NSString stringWithFormat:@"%ld.jpg",indexPath.row + 1]]];
    [imageView setFrame:CGRectMake(0, 0, 60, 60)];
    // 姓名label
    UILabel* nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(65, 10, 100, 15)];
    [nameLabel setText:[NSString stringWithFormat:@"张三%ld",indexPath.row + 1]];
    [nameLabel setFont:[UIFont systemFontOfSize:15]];
    // 描述lable
    UILabel* descLabel = [[UILabel alloc] initWithFrame:CGRectMake(65, 30, 100, 15)];
    [descLabel setText:[NSString stringWithFormat:@"Hello,张三%ld",indexPath.row + 1]];
    [descLabel setFont:[UIFont systemFontOfSize:15]];
    // 添加自定义视图内容到cell中
    [cell.contentView addSubview:imageView];
    [cell.contentView addSubview:nameLabel];
    [cell.contentView addSubview:descLabel];
    reutrn cell;
}

执行后样子
[IOS]

cell重用,一般情况下为了节省资源的问题,都会使用cell重用(不再屏幕显示范围中的cell,会被重新使用而不会创建新的cell)

// 1. 首先需要注册cell到列表对象中(一般有两种注册方式,一种是通过class注册和nib注册),下边举例使用class注册的方式
self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"CellId"];

// 2. 从列表重用队列中取出cell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell * cell = [self.tableView dequeueReusableCellWithIdentifier:@"CellId"];
    [cell.textLabel setText:[NSString stringWithFormat:@"%ld", indexPath.row]];
    return cell;
}

上边简单介绍了UITableView及其简单的使用方法,其实UICollectionView的与UITableView是有一些相似的,像是下边这张图中这种布局的页面如果使用UITableView实现的话就有一些不太方便实现了,但是使用UICollectionView 去实现这种多列的瀑布流布局就很方便了

[IOS]

UICollectionView的使用可能比UITableView更加麻烦一些,主要涉及到的类有下边几个:

  1. UICollectionView 瀑布流视图类
  2. UICollectionViewCell 瀑布流单元格视图类
  3. UICollectionViewLayout 瀑布流视图布局类
  4. UICollectionViewDataSource 瀑布流视图数据代理协议类

然后还是让我们先创建一个简单的瀑布流视图看看它的样子

  1. 首先需要先创建一个继承自UICollectionViewLayout的自定义瀑布流布局类,因为在创建瀑布流对象的时候会首先使用到, 新建继承自UICollectionViewLayout类的自定义类 MyViewLayout 文件
  2. 创建UICollectionView对象
// 1. 创建瀑布流自定义布局类对象
MyViewLayout* myLayout = [[MyViewLayout alloc] init];
// 2. 创建UICollectionView对象
UICollectionView* collectionView = [[UICollectionView alloc] initWithFrame:[UIScreen mainScreen].bounds collectionViewLayout:myLayout];
// 3. 设置瀑布流的数据代理对象
[collectionView setDataSource:self];
// 4. 注册重用单元格队列
[collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"CellId"];
  1. 实现数据代理协议的方法
// 单元格的数量
- (NSInteger) collectionView:(UICollectionView*)collectionView numberOfItemsInSection:(NSInteger)section
{
    return 30;
}
// 单元格的内容
- (UICollectionViewCell*) collectionView:(UICollectionView*)collectionView cellForItemAtIndexPath:(NSIndexPath*)indexPath
{
    UICollectionViewCell* cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"CellId" forIndexPath:indexPath];
    
    UILabel* label = [[UILabel alloc] init];
    [label setText:[NSString stringWithFormat:@"%ld", indexPath.item];
    [label sizeToFit];
    
    [cell.contentView addSubView:label];
    return cell;
}

  1. 完成MyViewLayout中的布局方法
// 单元格列数
static NSInteger const DefaultColumnCount = 3;
// 单元格之间的间隙
static CGFloat const DefaultColumnSpacing = 10;
// 单元格之间的行间距
static CGFloat const DefaultRowSpacing = 10;
// 瀑布流视图的四周边距
static UIEdgeInsets const DefaultEdgeInsets = {10,10,10,10};

@implementation MyViewLayout()

// 存放所有单元格的布局对象
@property (nonatomic,strong) NSMutableArray* attrArray;
// 每列最高的长度
@property (nonatomic,strong) NSMutableArray* maxYArray;

@end


@implementation MyViewLayout


// 布局对象初始化
- (void) prepareLayout
{
    [super prepareLayout];
    // 初始化数据
    self.attrArray = [NSMutableArray array];
    self.maxYArray = [NSMutableArray array];
    for (NSInteger i = 0; i < DefaultColumnCount; i ++)
    {
        [self.maxYArray addObject:@(DefaultEdgeInsets.top)];
    }
    
    // 初始化单元格布局样式
    NSInteger itemCount = [self.collectionView numberOfItemsInSection:0];
    for (NSInteger i = 0; i < itemCount; i ++)
    {
        NSIndexPath* indexPath =  [NSIndexPath indexPathForItem:i inSection:0];
        [self.attrArray addObject:[self layoutAttributesForItemAtIndexPath:indexPath]];
    }
}
// 所有单元格的布局属性数组获取
- (NSArray<UICollectionViewLayoutAttributes*>*) layoutAttributesForElementsInRect:(CGRect)rect
{
    return self.attrArray;
}
// 每个单元格的布局样式
- (UICollectionViewLayoutAttributes*) layoutAttributesForItemAtIndexPath:(NSIndexPath*)indexPath
{
    UICollectionViewLayoutAttributes* attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    // 取出最短的列和最短距离
    NSInteger __block minHeightColumn = 0;
    NSInteger __block minHeight = [self.maxYArray[minHeightColumn] floatValue];
    [self.maxYArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL* _Nonnull stop) {
        CGFloat columnHeight = [(NSNumber*)obj floatValue];
        if (minHeight > columnHeight)
        {
            minHeight = columnHeight;
            minHeightColumn = idx;
        }
    }];    


    //  剪掉瀑布流视图边框 剪掉分割空隙距离 除于列数
    CGFloat width = (CGRectGetWidth(self.collectionView.frame) - DefaultEdgeInsets.left - DefaultEdgeInsets.right - DefaultColumnSpacing * (DefaultColumnCount - 1)) / DefaultColumnCount ;
    CGFloat height = arc4random_uniform(400);
    // 在最短的列上添加元素
    CGFloat originX = DefaultEdgeInsets.left + minHeightColumn * (width + DefaultColumnSpacing);
    CGFloat originY = minHeight;
    // 如果不是最开始的单元格,需要添加行间隙
    if (originY != DefaultEdgeInsets.top)
    {
        originY += DefaultRowSpacing;
    }    
    // 设置单元格属性
    [attributes setFrame:CGRectMake(originX, originY, width, height)];
    // 更新单元格距离
    self.maxYArray[minHeightColumn] = @(CGRectGetMaxY(attributes.frame));
    return attributes;
}
// 瀑布流视图的内容大小
- (CGSize)collectionViewContentSize
{
    NSInteger __block maxHeight = 0;
    [self.maxYArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL* _Nonnull stop) {
        CGFloat columnHeight = [(NSNumber*)obj floatValue];
        if (maxHeight < columnHeight)
        {
            maxHeight = columnHeight;
        }
    }];    
    
    return CGSizeMake(0, maxHeight + DefaultEdgeInsets.bottom); 
}

@end

最后显示效果是下面这样的
[IOS]