问题场景

假设我们有如下布局代码,目标是在一个容器 logoTitleContainer 内居中显示一个 Logo 图片 _appLogoImageView,并指定其高度为 20 points:

UIView *logoTitleContainer = [[UIView alloc] init];
logoTitleContainer.backgroundColor = [UIColor clearColor];
[_containerView addSubview:logoTitleContainer];
[logoTitleContainer mas_makeConstraints:^(MASConstraintMaker *make) {
    make.centerX.equalTo(_containerView);
    make.top.mas_offset(40);
    make.height.mas_equalTo(20);
    make.width.mas_equalTo(192);
}];

// Logo ImageView
_appLogoImageView = [[UIImageView alloc] init];
_appLogoImageView.contentMode = UIViewContentModeScaleAspectFit;
[logoTitleContainer addSubview:_appLogoImageView];
[_appLogoImageView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.height.mas_equalTo(20);
    make.centerY.equalTo(logoTitleContainer);
    make.centerX.equalTo(logoTitleContainer);
    make.width.lessThanOrEqualTo(logoTitleContainer);
}];

// 加载网络图片 (原始尺寸 210x44 像素)
NSURL *imageUrl = [NSURL URLWithString:@"YOUR_IMAGE_URL_HERE"];
[_appLogoImageView yy_setImageWithURL:imageUrl placeholder:nil];

预期效果: _appLogoImageView 的高度被固定为 20 points,ScaleAspectFit 模式会根据图片原始宽高比(210/44 ≈ 4.77)计算出适应此高度的宽度(20 * 4.77 ≈ 95.4 points),图片应该以此尺寸显示在容器中心。

实际结果 (使用 YYWebImage): 通过 Debug View Hierarchy 或打印 _appLogoImageView 的 frame,我们发现:

<UIImageView: 0x7fd6438a3e40; frame = (61 0; 70 20); ... image = <YYImage:... (70 15)@3>; ...>

UIImageView 的 frame 宽度竟然是 70 points,而不是我们根据高度和比例计算出的 95.4 points 左右。图片内容确实被缩放了,但 UIImageView 本身的宽度不正确。

对比 (使用 SDWebImage): 如果使用 SDWebImage 加载同一张图片,可能会看到不同的结果:

<UIImageView: 0x7f7ee806b470; frame = (0 0; 192 20); ... image = <UIImage:... (210 44)@1>; ...>

这里 UIImageView 的 frame 宽度变成了其父容器的宽度 192 points。

问题根源:YYWebImage 的优化与 intrinsicContentSize

这个现象的核心在于 YYWebImage 的一项优化策略以及 Auto Layout 对 intrinsicContentSize的处理。

  1. YYWebImage 的 Scale 优化: 为了提高内存效率和渲染性能(像素对齐),YYWebImage 在加载图片时,倾向于创建一个 UIImage 对象,其 scale 属性与当前设备的屏幕 scale (@2x, @3x) 匹配。对于一张 210x44 像素的图片,在 @3x 屏幕上,YYWebImage 可能生成一个 UIImage,其 size 属性是 (70, 14.67) points,而 scale 属性是 3.0。 (因为 210 / 3 = 70, 44 / 3 ≈ 14.67)
  2. intrinsicContentSize: UIImageViewintrinsicContentSize 是根据其 image 属性的 size(以 points 为单位)来确定的。因此,当 YYWebImage 设置了 size=(70, 14.67) 的图片后,_appLogoImageViewintrinsicContentSize 就变成了 (70, 14.67)。
  3. Auto Layout 的决策: 在我们的约束中:
    • make.height.mas_equalTo(20) 强制设定了高度。
    • make.centerYmake.centerX 负责居中。
    • 关键: 我们没有为 _appLogoImageView 提供一个明确的、必须满足的宽度约束。make.width.lessThanOrEqualTo(logoTitleContainer) 只是一个上限,它允许宽度小于 192。
    • 当 Auto Layout 系统解析约束时,它会尝试满足所有约束,同时也会参考 intrinsicContentSize。由于没有强制的宽度,系统发现 intrinsicContentSize 提供了一个宽度值 (70 points),并且这个值满足 lessThanOrEqualTo(192) 的条件,于是就采用了 70 points 作为最终的宽度。

这就是为什么使用 YYWebImage 时,_appLogoImageView 的宽度变成了 70 points。

解决方案:明确 UIImageView 的布局范围

最直接且常用的解决方案是:不要依赖 intrinsicContentSize 来决定 UIImageView 的宽度,而是明确地约束它的宽度。

我们可以让 UIImageView 的宽度填充其父容器 logoTitleContainer,然后让 contentMode = UIViewContentModeScaleAspectFit 来负责在 UIImageView 的 frame 内正确地按比例显示图片内容。

修改 _appLogoImageView 的约束如下:

[_appLogoImageView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.height.mas_equalTo(20);
    make.centerY.equalTo(logoTitleContainer);

    // --- 修改部分 ---
    // 移除 centerX 约束 (因为左右约束会确定水平位置)
    // make.centerX.equalTo(logoTitleContainer);

    // 让 UIImageView 的左右边缘贴紧其父容器 logoTitleContainer
    make.leading.equalTo(logoTitleContainer);
    make.trailing.equalTo(logoTitleContainer);
    // 或者,如果你想让它宽度等于父容器,也可以这样写:
    // make.width.equalTo(logoTitleContainer);

    // `lessThanOrEqualTo` 也不再需要,因为宽度已被完全确定
    // make.width.lessThanOrEqualTo(logoTitleContainer);
}];

工作原理:

  1. 通过 leadingtrailing (或 width.equalTo) 约束,我们强制 _appLogoImageView 的宽度等于其父容器 logoTitleContainer 的宽度,即 192 points。
  2. 现在 _appLogoImageView 的 frame 是 (0, 0, 192, 20)。
  3. contentMode = UIViewContentModeScaleAspectFit 会将原始图片(无论是 210x44 还是 YYWebImage 处理后的 70x15@3x)按比例缩放,以适应这个 192x20 的区域。图片会被缩放到大约 95.4x20 points 的大小,并在这个 192 points 宽的 UIImageView 内部水平居中显示。

优点:

  • 代码简单,不依赖图片原始尺寸或 scale
  • 布局稳定,UIImageView 的 frame 大小由你的 Auto Layout 规则明确定义。
  • 无论使用 YYWebImage 还是 SDWebImage,UIImageView 的 frame 都会是你期望的大小(192x20),图片内容都会在其中正确显示。

注意: 这种方法下,UIImageView 的可触摸区域或背景色区域(如果设置了)会是整个 192x20,可能比实际显示的图片内容要宽。如果这对你的 UI 不构成影响,那么这是非常推荐的解决方案。

总结

YYWebImage 出于优化目的改变 UIImagescale 属性,进而影响 UIImageViewintrinsicContentSize,这在与不完全指定的 Auto Layout 约束结合时,可能导致非预期的布局结果。通过为 UIImageView 设置明确的宽度约束(例如,使其填充父容器),我们可以覆盖 intrinsicContentSize 的影响,获得稳定、可预测的布局效果。