UITableView 表视图 是IOS使用非常频繁的布局视图
UITableView 什么样子呢? 一般用在什么地方呢?看下边的图
像是电话薄,好友列表 这种列表排列的视图一般都是使用UITableView实现的
UITableView 一共包含两种内置的布局格式:
- UITableViewStylePlain 普通的表格样式(默认)
- UITableViewStyleGrouped 带有分组的表格样式
在项目中如何使用UITableView,UITableView相较于其他基础视图对象使用还是稍微复杂一点点的
UITableView 的使用主要触及到了这么几个类:
先来一个列表视图尝尝鲜,
- 创建一个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];
- 上边将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;
}
- 执行后的样子
上边只是简单实现了列表的基本显示(其实这样实现是有问题的),还有更多的(自定义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;
}
执行后样子
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 去实现这种多列的瀑布流布局就很方便了
UICollectionView的使用可能比UITableView更加麻烦一些,主要涉及到的类有下边几个:
- UICollectionView 瀑布流视图类
- UICollectionViewCell 瀑布流单元格视图类
- UICollectionViewLayout 瀑布流视图布局类
- UICollectionViewDataSource 瀑布流视图数据代理协议类
然后还是让我们先创建一个简单的瀑布流视图看看它的样子
- 首先需要先创建一个继承自UICollectionViewLayout的自定义瀑布流布局类,因为在创建瀑布流对象的时候会首先使用到, 新建继承自UICollectionViewLayout类的自定义类 MyViewLayout 文件
- 创建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"];
- 实现数据代理协议的方法
// 单元格的数量
- (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;
}
- 完成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
最后显示效果是下面这样的