自动布局视图来实现聊天室的界面

发布于:2025-08-03 ⋅ 阅读:(16) ⋅ 点赞:(0)

自动布局视图来实现聊天室的界面

在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: 批量激活

  1. 建立相等/不等关系 (最常用)

这些方法用于将一个锚点与另一个同类型的锚点关联起来。

  • (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,正值表示向外拉伸,通常需要使用负值来向内收缩
  1. 尺寸锚点的特殊方法

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 * 40
        NSLayoutConstraint* 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高度