自动布局视图来实现聊天室的界面
自动布局视图来实现聊天室的界面
在share项目中,需要实现私信界面,但由于我比较懒,于是选择了学习自动布局视图来实现聊天室的内容
在实现聊天室之前,我们需要弄清楚聊天室是怎么构成的
每条消息实际上是就是一个cell,整个界面就是一个tableView,在这个tableView的底部加上一个textField,就成了基本的聊天室界面
那实现的难点就在于,如何根据你输入文字的多少,来修改整个cell的高度
我这里使用了cell的自动布局
首先我们在tableView里把高度设置设为自动计算,并且给定一个预设高度
self.messageView.tableView.rowHeight = UITableViewAutomaticDimension;
self.messageView.tableView.estimatedRowHeight = 100.0;
这里的预设高度是方便系统计算实际高度的,设置合适的自动高度可以节省内存
由于tableView的计算高度是根据自动布局计算的,所以我们的cel内部需要使用自动布局
第一步,禁用系统默认布局
translatesAutoresizingMaskIntoConstraints
translatesAutoresizingMaskIntoConstraints是系统自动布局转换的一个属性,当你创建一个view的时候,系统会默认
// 系统默认设置
view.translatesAutoresizingMaskIntoConstraints = YES;
这意味系统会自动把你写的frame布局转换为自动布局
默认规则
在默认情况下,转换为
// 假设你设置了 frame
view.frame = CGRectMake(10, 20, 100, 50);// 系统会自动转换为这些约束:
view.leading = superview.leading + 10
view.top = superview.top + 20
view.width = 100
view.height = 50
如果不设置no,那么我们设置的自动布局就会和系统设置的产生冲突
第二步,设置约束并激活
Apple在iOS9中推出了NSLayoutAnchor,是目前用代码写约束的官方推荐写法
它为一系列控件提供了一系列锚点(Anchor)属性,我们可以通过这些锚点来创建约束关系
在外面写约束的时候,实际上就是用一根绳子(NSLayoutConstraint)来把一个视图的挂钩跟另一个视图的挂钩链接起来
锚点有三种锚点,每个UIView都有一整套的锚点属性,且会自动匹配属性,你不能告诉一个负责垂直的锚点去挂负责水平的锚点
三种锚点分别是:
NSLayoutXAxisAnchor
:负责水平方向的挂钩NSLayoutYAxisAnchor
:负责垂直方向的挂钩NSLayoutDimension
:负责**尺寸(宽和高)**的挂钩
NSLayoutXAxisAnchor
水平锚点
这些锚点定义了视图在x轴方向的位置,一共有五种
leadingAnchor
: 前缘锚点。在“从左到右”的语言环境下(如中文、英文),它就是左边缘。在“从右到左”的语言环境下(如阿拉伯语),它就是右边缘(其实无所谓因为你如果能写出国际化软件你也就不会看我的博客了…)trailingAnchor
: 后缘锚点。与leadingAnchor
对应,是国际化安全的右边缘。强烈推荐使用leftAnchor
: 左边缘锚点。无论语言方向如何,它永远是左边rightAnchor
: 右边缘锚dian。无论语言方向如何,它永远是右边centerXAnchor
: X轴中心锚点。视图水平方向的中点
NSLayoutYAxisAnchor
垂直锚点
定义了视图在y轴方向的位置
topAnchor
: 顶部锚点bottomAnchor
: 底部锚点centerYAnchor
: Y轴中心锚点。视图垂直方向的中点firstBaselineAnchor
: 首基线锚点。主要用于对齐包含文字的视图(如UILabel
,UITextField
),它代表第一行文字的基线lastBaselineAnchor
: 末基线锚点。代表最后一行文字的基线。当你希望两个多行标签的文字底端对齐时,这个非常有用
NSLayoutDimension
尺寸锚点
定义了视图自身的大小
widthAnchor
: 宽度锚点heightAnchor
: 高度锚点
常用的约束方法
创建约束的通用发送为
[view1.某个锚点 constraintEqualToAnchor:view2.另一个锚点];
这个方法会返回一个 NSLayoutConstraint
对象,但它默认是 不激活 的。你需要手动激活它,或者用 NSLayoutConstraint
的类方法 activateConstraints:
批量激活
- 建立相等/不等关系 (最常用)
这些方法用于将一个锚点与另一个同类型的锚点关联起来。
(NSLayoutConstraint *)constraintEqualToAnchor:(NSLayoutAnchor<AnchorType> *)anchor;
- 作用:本锚点 == 另一个锚点
(NSLayoutConstraint *)constraintGreaterThanOrEqualToAnchor:(NSLayoutAnchor<AnchorType> *)anchor;
- 作用:本锚点 >= 另一个锚点
(NSLayoutConstraint *)constraintLessThanOrEqualToAnchor:(NSLayoutAnchor<AnchorType> *)anchor;
- 作用:本锚点 <= 另一个锚点
带偏移量 (constant):
在上面的基础上,你还可以增加一个固定的偏移量。
(NSLayoutConstraint *)constraintEqualToAnchor:(NSLayoutAnchor<AnchorType> *)anchor constant:(CGFloat)c;
- 作用:本锚点 == 另一个锚点 + c
- 注意:
constant
的正负值含义取决于锚点。对于top
,leading
,centerX
,centerY
,正值表示向下、向右移动。对于bottom
,trailing
,正值表示向外拉伸,通常需要使用负值来向内收缩
- 尺寸锚点的特殊方法
NSLayoutDimension
因为代表的是尺寸,所以它有一些更强大的方法。
与固定值比较:
(NSLayoutConstraint *)constraintEqualToConstant:(CGFloat)c;
- 作用:本尺寸 == 固定值c
(NSLayoutConstraint *)constraintGreaterThanOrEqualToConstant:(CGFloat)c;
(NSLayoutConstraint *)constraintLessThanOrEqualToConstant:(CGFloat)c;
与另一个尺寸成比例 (multiplier):
这是实现动态比例布局的关键。
(NSLayoutConstraint *)constraintEqualToAnchor:(NSLayoutDimension *)anchor multiplier:(CGFloat)m;
- 作用:本尺寸 == 另一个尺寸 * m
(NSLayoutConstraint *)constraintEqualToAnchor:(NSLayoutDimension *)anchor multiplier:(CGFloat)m constant:(CGFloat)c;
- 作用:本尺寸 == (另一个尺寸 * m) + c
聊天室cell代码
直接给出代码
#import "messageCell.h"// 添加一个类扩展
@interface messageCell()
// 1的布局约束
@property (nonatomic, strong) NSArray<NSLayoutConstraint *> *myLayoutConstraints;
// 0的布局约束
@property (nonatomic, strong) NSArray<NSLayoutConstraint *> *otherLayoutConstraints;
@end@implementation messageCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];if (self) {self.backgroundColor = [UIColor clearColor];self.selectionStyle = UITableViewCellSelectionStyleNone;self.headImage = [[UIImageView alloc] init];self.headImage.layer.cornerRadius = 20;self.headImage.layer.masksToBounds = YES;self.headImage.contentMode = UIViewContentModeScaleAspectFill;self.messageLabel = [[UILabel alloc] init];self.messageLabel.numberOfLines = 0;self.messageLabel.font = [UIFont systemFontOfSize:16];
// self.messageLabel.layer.cornerRadius = 8;
// self.messageLabel.layer.masksToBounds = YES;self.backgroundView = [[UIView alloc] init];self.backgroundView.layer.cornerRadius = 8;self.backgroundView.layer.masksToBounds = YES;// 准备工作// 取消掉系统给控件默认的一套布局方法self.headImage.translatesAutoresizingMaskIntoConstraints = NO;self.messageLabel.translatesAutoresizingMaskIntoConstraints = NO;self.backgroundView.translatesAutoresizingMaskIntoConstraints = NO;// 添加到 contentView[self.contentView addSubview:self.headImage];[self.contentView addSubview:self.backgroundView];[self.contentView addSubview:self.messageLabel];// 添加遮罩层来完成背景设置
// // 公共约束,不管是什么cell都需要遵守,label的顶部与cell的顶部留出10的间距
// NSLayoutConstraint *labelTop = [self.messageLabel.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:10];
// // label的底部与cell的底部留出10的间距
// NSLayoutConstraint *labelBottom = [self.messageLabel.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor constant:-10];NSLayoutConstraint* backGroundViewUpPaddingY = [self.backgroundView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:10];NSLayoutConstraint* backGroundViewDownPaddingY = [self.backgroundView.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor constant:-10];NSLayoutConstraint* labelTop = [self.messageLabel.topAnchor constraintEqualToAnchor:self.backgroundView.topAnchor constant:10];NSLayoutConstraint* labelBotton = [self.messageLabel.bottomAnchor constraintEqualToAnchor:self.backgroundView.bottomAnchor constant:-10];NSLayoutConstraint* labelLeft = [self.messageLabel.leadingAnchor constraintEqualToAnchor:self.backgroundView.leadingAnchor constant:10];NSLayoutConstraint* labelRight = [self.messageLabel.trailingAnchor constraintEqualToAnchor:self.backgroundView.trailingAnchor constant:-10];// 头像也是同理NSLayoutConstraint* headTop = [self.headImage.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:10];// 固定头像的大小为40 * 40NSLayoutConstraint* headWidth = [self.headImage.widthAnchor constraintEqualToConstant:40];NSLayoutConstraint* headHeight = [self.headImage.heightAnchor constraintEqualToConstant:40];// 设置背景遮罩层的最高高度为多少NSLayoutConstraint* backViewMaxWidth = [self.backgroundView.widthAnchor constraintLessThanOrEqualToConstant:250];// 激活约束[NSLayoutConstraint activateConstraints:@[backGroundViewUpPaddingY, backGroundViewDownPaddingY, labelBotton,labelTop,labelLeft,labelRight, headTop, headWidth, headHeight, backViewMaxWidth]];// 对方self.otherLayoutConstraints = @[// 头像在左边[self.headImage.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:10],// Label在头像右边[self.backgroundView.leadingAnchor constraintEqualToAnchor:self.headImage.trailingAnchor constant:10]];// 我的self.myLayoutConstraints = @[// 头像在右边[self.headImage.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:-10],// Label在头像左边[self.backgroundView.trailingAnchor constraintEqualToAnchor:self.headImage.leadingAnchor constant:-10]];}return self;
}- (void)configureWithMessage:(NSString *)message sender:(NSInteger)sender {self.messageLabel.text = message;// 根据发送者选择布局if (sender == 1) {// 我[NSLayoutConstraint deactivateConstraints:self.otherLayoutConstraints];[NSLayoutConstraint activateConstraints:self.myLayoutConstraints];// 设置样式self.headImage.image = [UIImage imageNamed:@"newfollow0.PNG"];self.backgroundView.backgroundColor = [UIColor colorWithRed:0.2 green:0.6 blue:1.0 alpha:1.0];self.messageLabel.textColor = [UIColor whiteColor];} else {// 对方// 激活“对方”的约束,禁用“我”的约束[NSLayoutConstraint deactivateConstraints:self.myLayoutConstraints];[NSLayoutConstraint activateConstraints:self.otherLayoutConstraints];// 设置样式self.headImage.image = [UIImage imageNamed:@"newfollow1.PNG"];self.backgroundView.backgroundColor = [UIColor colorWithRed:0.9 green:0.9 blue:0.9 alpha:1.0];self.messageLabel.textColor = [UIColor blackColor];}
}
这样就完成了cell的自动布局视图
之后在VC中使用cell的configureWithMessage:(NSString *)message sender:(NSInteger)sender方法即可设置cell,在VC中,也无需使用heightForRowAtIndexPath来设置每个cell的高度
label的高度由label内的文本决定,而其他控件分层向外传递高度,使用cursor解析代码给出高度传递链
文字内容尺寸→messageLabel高度→backgroundView高度→cell高度 文字内容尺寸 → messageLabel 高度 → backgroundView 高度 → cell 高度 文字内容尺寸→messageLabel高度→backgroundView高度→cell高度