iOS 利用autolayout自定义行高仿写朋友圈界面,OC与Swift版本

在此,本文章将会提到并用到的知识

1、tableview预设行高与自动布局autolayout综合使用,
2、autoLayout布局在tableviewCell里的约束细节。
3、关于MVC,tableview的解耦与封装。

1、tableview的朋友圈布局

微信朋友圈截图,接下来我们将要去实现它,分别用OC跟Swift
那么,先实现第一步,tableview的布局。里面的结构不多说了,各个结构部分,头像,用户名称,发布内容,以及图片,还有点赞评论区域。关于发布内容是一个label,这也是利用自动布局不计算高度的关键所在,无论label里多少内容,通过预设cell高度的方法,使其高度不需要开发者计算而是利用autolayout系统计算的方法获得。
对于图片的展现,用collectionview的方法,那么如何根据图片有无图片数目来判断collectionview的高度,我的方法是控制collectionview的高度约束线。如果有更好的方法请指导我。对于底部评论区域我用的是一个tableview,通过评论点赞按钮控制其显示,对于tabeview的高度,我也是用控制约束线的方法,不过应该也是有更加优秀的方法,其cell的高度则是通过使用autolayout和tableview的预设高度来设定。
通过分析朋友圈主要的视图结构,我们主要应用到三点。1、通过autoLayout自动布局以及tableviewcell的预设高度来系统计算cell的行高,2、通过约束线控制视图的高度。主要的还是在控制cell的行高上。接下里我们会分层次一步一步的进行分析。

2、autoLayout与tableview预设行高结合控制cell的行高

我所要是实现的就是,通过结合autoLayout跟设置tableview预设行高来实现自适应cell的高度,我会通过控制cell里两个视图的高度来做到不同cell有不同高度。
简单效果图
看一下约束线描述
约束线描述
简单代码,主要是为了实现效果,
- #pragma mark - UITableViewDelegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 6;
}

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

demoTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"demoTableViewCell" forIndexPath:indexPath];
   cell.greenViewHeight.constant = [_viewHeight[indexPath.row] intValue];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;

}

-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 200;
}

通过观察代码,我通过控制greenViewHeigh的值,来确保每个cell的高度不一样,然后做了一个cell预设高度的代理,利用autolayout来实现cell高度的计算,很简介。

两个布局坑

1、tableview属性中的Row Height必须设置为44,哪怕是45系统也不会计算出来的。如图
tableview RowHeight必须设置为44
2、关于约束冲突的问题,你可能会遇到,也可能不会遇到,即便是遇到了也不会影响布局,但是理清思路很重要,autolayout计算cell是怎么计算cell的高度的,它跟代码控制的关系是怎样的。我接下来会高度大家。而对于这种情况,一般只需控制约束进行的优先顺序就好。如图:
约束线优先级

我是通过减小上下两个view之间约束来保证不会有约束冲突。那对于用autoLayout自动算行高跟控制view约束线大小,很显然,当我们在代码控制完成之后,autoLayout 才会计算cell的行高,此时,会跟据Prioorty的优先级进行计算,当约束线多,特别是在特殊的cell里时,因为我们是需要通过内容充实cell的高度的,也就有可能出现约束冲突的情况,即便是你约束的没有误差,所以我们做约束的优先级,这样的话就不会有约束冲突的情况了。

明白了这个的实现方式以及注意事项,对于朋友圈的视图布局就显得有思路了。

理解了这种在tableview里利用autoLayout布局来确定行高的方式,以及注意事项,在布局的时候强调一点是,高度的约束一定要充满整个cell,也就是在高度方面跟tableview里的cell保和。接下来我们就是向tableview里加载label,collectionView,评论的tablview等一系列的元素。先用OC的方法来实现:

布局图例

tableviewCell中的约束线

我用颜色块区分各个区域,明显的看出各个部分。值得注意的是,我是秉承内容驱动cell高度的布局,在布局上能够确保cell的高度确定,否则会出现cell显示不全内容的情况。

布局基本完成,接下来就是考虑视图的实现逻辑。关键点主要在图片的显示,以及评论区域的显示和各自的高度的方面,这个是需要我们依据内容来进行高度的判断的。所以,要求有一个很好的数据结构来支持。对于实现,上面部分以及提及了,根据要显示的内容通过改约束线的高度更改各种区域的高度,从而来通过autoLayout的方式来驱动cell的高度。

主要是通过获取的数据来搞定collectionView的高度跟评论区域tableview的高度。在此,我贴出根据image的数目来判断collectionView高度的方法。
if(_collectionNum == 0) {
//没有图片,collectionView的高度约束线为0,
_contentCollectionHeight.constant = 0
}else {
if (_collectionNum == 3) {
//1-3 个图片的情况,
_contentCollectionHeight.constant = ([UIScreen mainScreen].bounds.size.width - 90 - 8) / 3 ;
}else if(_collectionNum == 6) {
_contentCollectionHeight.constant = ([UIScreen mainScreen].bounds.size.width - 90 - 8) / 3 * 2 + 4;
}else if(_collectionNum == 9){
_contentCollectionHeight.constant = ([UIScreen mainScreen].bounds.size.width - 90 - 8) / 3 * 3 + 8;
}else {
_contentCollectionHeight.constant = ([UIScreen mainScreen].bounds.size.width - 90 - 8) / 3 * 1.5;
}
[_contentCollection reloadData];
}

而对应的,我们在确认collectionView的代理中,cell的长跟宽我是这样做的。
//设置每个 UICollectionView 的大小
- (CGSize)collectionView:(UICollectionView )collectionView layout:(UICollectionViewLayout)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
//由图片个数确定UICollectionView的大小
if (_collectionNum == 1) {
//如果只有一个图片,则确定图片的大小
return CGSizeMake((self.contentCollection.frame.size.width - 8 )/ 3 * 2.2,(self.contentCollection.frame.size.width - 8 )/ 3 * 1.5);
}else{
return CGSizeMake((self.contentCollection.frame.size.width - 8 )/ 3,(self.contentCollection.frame.size.width - 8)/ 3);
}
}

frame布局的坑

关于MVC,swift与oc代码比较

我们从代码中获取自定布局中控件frame的width跟height,如果是在创建视图的时候,我们获取到frame的width跟height是1000,而在加载完成后我们获取的是正确的frame,所以在加载的时候如果需要获取空间的frame注意不要通过空间frame的形式。

其实对于oc的语法已经讲过了,在这里说一些swift的语法。swift语法跟oc的语法是不相同的,但是思路是类似的甚至说是相同的,我们在oc里的大部分逻辑已经实现思路,在swift里还是可以用到的,但是我还是会进一步的把主要代码贴出来,一起优化探究。

在用到tableview时,我们会想到MVC模式,我们也用swift里的MVC实现吧。

关于V,上文的stroboard已经给出来了,跟在oc里是一样的,M,model,简单的列出代码,不全,只是为了体现思路

import UIKit
class autoTableModel: NSObject {
var name:NSString!
var content:NSString!
}

那么我们对比oc里的model

#import <Foundation/Foundation.h>
@interface tableObjModel : NSObject
@property (strong, nonatomic) NSString*userName;
@property (strong, nonatomic) NSString
*userContent;
@end

单从代码简洁上,swift的确实比oc好多了

我们下面来在controller里面的内容,特别是tableview的代理。

var contentLabelArray = ["我终于醒来了,我的青春不在了。我不该哭泣,我该鼓掌。请你也为我鼓掌。","不争,是一种慈悲 不辩","境明,千里皆明 心美,一切皆美 情深,万象皆深","每晚和繁星相对,我把它们认得很熟了"]
var username = ["开家小店","尤克里里","莫子","西诗"];
var tableDataModelArray = [autoTableModel]()     //定义autoTableModel的数组。

这是我们自定义的假数据,我们也定义了一个model类型的数组,下面我们来存储数据

 for i in 0..<username.count {
        let tableModel = autoTableModel()  //定义model
        tableModel.name = username[i] as NSString!
        tableModel.content = contentLabelArray[i] as NSString!
        tableDataModelArray.append(tableModel)
    }

这段代码,定义一个里面包含有model类型的数组,实现思想大致是不变的,只是在代码的表达上跟oc是有不同的,所以只是熟悉swift代码表达的意思对于oc玩家来说就可以了,跳过了逻辑层面,swift并不难。
重点来了,tableview的几个代理方法

 //显示cell多少行
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
    return 6
}
//绘制cell
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "AutoTableViewCell", for: indexPath) as! AutoTableViewCell
    cell.headImage.image = UIImage(named: "图片05")
    cell.tableViewModel = tableDataModelArray[indexPath.row]
    //cell.userContent.text = contentLabelArray[indexPath.row]

    return cell
}
//选中cell
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    print("选中\(indexPath.row)")
}
//预设行高
@objc(tableView:estimatedHeightForRowAtIndexPath:) public func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    return 100
}

这些tableview的代理,跟oc的思路一样,只不过表达方式改变了一些, 可以对应研究。我在定义cell的代理中,写了 cell.tableViewModel = tableDataModelArray[indexPath.row]
这样的代码,我是需要在tableviewCell里实现的,我不想在这个controller里实现。另外,如果需要利用autoLayout的方法得到行高,需要走预设行高的代理,而不是自定义行高的代理。这点注意。

下面展示tableviewCel里的代码,在这里是需要实现展示图片的collection跟评论区域的tableview的。对,我是在这里写的。

import UIKit
class AutoTableViewCell: UITableViewCell, UITableViewDelegate, UITableViewDataSource, UICollectionViewDelegate, UICollectionViewDataSource{
@IBOutlet var headImage: UIImageView!
@IBOutlet var userName: UILabel!
@IBOutlet var userContent: UILabel!
@IBOutlet var imageCollection: UICollectionView!
@IBOutlet var commentsTableView: UITableView!
@IBOutlet var collectionViewHeight: NSLayoutConstraint!
@IBOutlet var commentsViewHeight: NSLayoutConstraint!
var contentLabelArray = ["我终于醒来了,我的青春不在了。我不该哭泣,我该鼓掌。请你也为我鼓掌。","不争,是一种慈悲 不辩,是一种智慧 不闻,是一种清净 不看,是一种自在","说的有有理"]
var tableViewModel:autoTableModel {
    set {
        userName.text = newValue.name as String?            //注意newValue
        userContent.text = newValue.content as String?
    }
    get {
        return self.tableViewModel
    }
}
override func awakeFromNib() {
    super.awakeFromNib()
    // Initialization code
    commentsTableView.delegate = self;
    commentsTableView.dataSource = self;
    imageCollection.delegate = self;
    imageCollection.dataSource = self;
}
override func setSelected(_ selected: Bool, animated: Bool) {
    super.setSelected(selected, animated: animated)
    // Configure the view for the selected state
}

我并没有展示全,我只是想主要展示

 var tableViewModel:autoTableModel {
    set {
        userName.text = newValue.name as String?            //注意newValue
        userContent.text = newValue.content as String?
    }
    get {
        return self.tableViewModel
    }
}

这个方法,我们在AutoTableViewCell文件里定义处理,在controller文件里存入数据源。此外关于在cell里的collection跟tableview如果处理以及一些数据结构,我们下次再说。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值