iOS开发基础10-UIButton 内边距与 UIImage 拉伸深度解析
UIButton 内边距与 UIImage 拉伸深度解析
UIButton 的内边距控制与 UIImage 的拉伸是实现自适应按钮样式的核心技能。本文详细解析三种内边距属性的区别、图片拉伸的演进历程与现代最佳实践。
一、UIButton 的内边距
UIButton 提供三种内边距属性,分别控制整体内容、标题和图片的偏移:
| 属性 | 作用对象 | 说明 |
|---|---|---|
contentEdgeInsets |
图片 + 文字整体 | 将内部所有内容视为一个整体进行偏移 |
titleEdgeInsets |
标题 Label | 仅偏移标题文字 |
imageEdgeInsets |
图片 ImageView | 仅偏移图标 |
iOS 15+ 注意:以上三个属性在 iOS 15 中被标记为废弃,推荐使用
UIButton.Configuration的contentInsets、imagePadding、titlePadding替代。本文先介绍传统属性,末尾补充现代用法。
1. contentEdgeInsets
contentEdgeInsets 将按钮内部的 UIImageView 和 UILabel 视为一个整体,统一设置内边距。
Objective-C
UIButton *btn = [[UIButton alloc] init];
btn.frame = CGRectMake(50, 100, 200, 60);
[btn setTitle:@"按钮" forState:UIControlStateNormal];
[btn setImage:[UIImage imageNamed:@"icon"] forState:UIControlStateNormal];
btn.backgroundColor = [UIColor blueColor];
// 整体内容向下偏移 30pt
btn.contentEdgeInsets = UIEdgeInsetsMake(30, 0, 0, 0);
[self.view addSubview:btn];
Swift
let btn = UIButton(type: .custom)
btn.frame = CGRect(x: 50, y: 100, width: 200, height: 60)
btn.setTitle("按钮", for: .normal)
btn.setImage(UIImage(named: "icon"), for: .normal)
btn.backgroundColor = .blue
// 整体内容向下偏移 30pt
btn.contentEdgeInsets = UIEdgeInsets(top: 30, left: 0, bottom: 0, right: 0)
view.addSubview(btn)
效果:图片和文字作为整体一起向下移动 30pt。
2. titleEdgeInsets 与 imageEdgeInsets
titleEdgeInsets 和 imageEdgeInsets 分别独立控制标题和图片的偏移,互不干扰。
Objective-C
UIButton *btn = [[UIButton alloc] init];
btn.frame = CGRectMake(50, 100, 200, 60);
[btn setTitle:@"按钮" forState:UIControlStateNormal];
[btn setImage:[UIImage imageNamed:@"icon"] forState:UIControlStateNormal];
btn.backgroundColor = [UIColor blueColor];
// 文字向右偏移 30pt
btn.titleEdgeInsets = UIEdgeInsetsMake(0, 30, 0, 0);
// 图片向下偏移 30pt
btn.imageEdgeInsets = UIEdgeInsetsMake(30, 0, 0, 0);
[self.view addSubview:btn];
Swift
let btn = UIButton(type: .custom)
btn.frame = CGRect(x: 50, y: 100, width: 200, height: 60)
btn.setTitle("按钮", for: .normal)
btn.setImage(UIImage(named: "icon"), for: .normal)
btn.backgroundColor = .blue
btn.titleEdgeInsets = UIEdgeInsets(top: 0, left: 30, bottom: 0, right: 0)
btn.imageEdgeInsets = UIEdgeInsets(top: 30, left: 0, bottom: 0, right: 0)
view.addSubview(btn)
效果:文字右移 30pt,图片下移 30pt,两者独立变化。
3. 三者的关系与布局顺序
按钮内部布局的计算顺序为:
- 先根据
contentEdgeInsets确定内容整体的可用区域。 - 在该区域内,图片和文字默认左图右文排列。
- 再分别应用
imageEdgeInsets和titleEdgeInsets对各自进行偏移。
注意:使用
edgeInsets调整图文位置时,可能影响按钮的intrinsicContentSize(固有内容尺寸),在 Auto Layout 下可能导致布局不准确。复杂的图文布局推荐使用 iOS 15+ 的UIButton.Configuration。
4. iOS 15+ 现代方式:UIButton.Configuration
iOS 15 引入的声明式配置 API 原生支持内容内边距和图文间距,且与 Auto Layout 完美兼容:
Objective-C
UIButtonConfiguration *config = [UIButtonConfiguration filledButtonConfiguration];
config.title = @"按钮";
config.image = [UIImage systemImageNamed:@"star"];
config.imagePlacement = NSDirectionalRectEdgeLeading; // 图片在左
config.imagePadding = 8; // 图片与文字间距
config.contentInsets = NSDirectionalEdgeInsetsMake(10, 20, 10, 20); // 整体内边距
UIButton *btn = [UIButton buttonWithConfiguration:config primaryAction:nil];
btn.frame = CGRectMake(50, 100, 200, 50);
[self.view addSubview:btn];
Swift
var config = UIButton.Configuration.filled()
config.title = "按钮"
config.image = UIImage(systemName: "star")
config.imagePlacement = .leading
config.imagePadding = 8
config.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 20)
let btn = UIButton(configuration: config)
btn.frame = CGRect(x: 50, y: 100, width: 200, height: 50)
view.addSubview(btn)
二、UIImage 的拉伸
在自适应 UI 中,按钮背景图、聊天气泡等需要根据内容尺寸动态拉伸。UIImage 提供了九切片(9-patch)拉伸机制,通过指定四个边的保护区域(cap),使边角保持不变,中间区域被拉伸或平铺。
拉伸原理
┌─────────┬─────────────────┬─────────┐
│ top-left│ │ top-right│
│ cap │ 可拉伸区域 │ cap │
│ │ (水平/垂直拉伸) │ │
├─────────┼─────────────────┼─────────┤
│ │ │ │
│ left cap│ │ right cap│
│ │ │ │
├─────────┼─────────────────┼─────────┤
│bottom-l │ │bottom-r │
│ cap │ │ cap │
└─────────┴─────────────────┴─────────┘
四个角的 cap 区域保持原样不被拉伸;上下边的中间区域仅水平拉伸;左右边的中间区域仅垂直拉伸;中心区域同时水平和垂直拉伸。
1. iOS 5 之前:stretchableImageWithLeftCapWidth:topCapHeight:
早期 API 只能指定左边和顶部的保护区域,右边和底部由系统自动计算(右 cap = 宽度 - 左 cap - 1,底 cap = 高度 - 顶 cap - 1)。该方法在 iOS 5 起被废弃。
// ⚠️ 已废弃
UIImage *image = [UIImage imageNamed:@"button_bg"];
UIImage *resizable = [image stretchableImageWithLeftCapWidth:5 topCapHeight:5];
[btn setBackgroundImage:resizable forState:UIControlStateNormal];
2. iOS 5+:resizableImageWithCapInsets:
通过 UIEdgeInsets 同时指定四个边的保护区域,更直观灵活。默认拉伸模式为 UIImageResizingModeStretch(平滑拉伸)。
Objective-C
UIImage *image = [UIImage imageNamed:@"button_bg"];
// 四边各保护 5pt,中间区域拉伸
UIEdgeInsets insets = UIEdgeInsetsMake(5, 5, 5, 5);
UIImage *resizable = [image resizableImageWithCapInsets:insets];
[btn setBackgroundImage:resizable forState:UIControlStateNormal];
btn.frame = CGRectMake(100, 100, 200, 80);
[self.view addSubview:btn];
Swift
let image = UIImage(named: "button_bg")!
let insets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
let resizable = image.resizableImage(withCapInsets: insets)
btn.setBackgroundImage(resizable, for: .normal)
btn.frame = CGRect(x: 100, y: 100, width: 200, height: 80)
view.addSubview(btn)
中心点拉伸(常见技巧)
对于对称的渐变或纯色背景,可将 cap 设为图片尺寸的一半,使中间仅 1 像素参与拉伸:
UIEdgeInsets insets = UIEdgeInsetsMake(image.size.height * 0.5,
image.size.width * 0.5,
image.size.height * 0.5,
image.size.width * 0.5);
UIImage *resizable = [image resizableImageWithCapInsets:insets];
当 top + bottom >= 图片高度时,中间可拉伸区域为 1 像素,系统取正中间的 1 像素进行拉伸。这种方式适合径向渐变、纯色圆角等对称图案。
3. iOS 6+:resizableImageWithCapInsets:resizingMode:
进一步支持指定拉伸模式:
| 模式 | 说明 |
|---|---|
UIImageResizingModeStretch |
平滑拉伸(默认),中间区域被均匀拉伸 |
UIImageResizingModeTile |
平铺,中间区域被重复平铺 |
Objective-C
UIImage *image = [UIImage imageNamed:@"button_bg"];
UIEdgeInsets insets = UIEdgeInsetsMake(5, 5, 5, 5);
// 平滑拉伸
UIImage *stretched = [image resizableImageWithCapInsets:insets resizingMode:UIImageResizingModeStretch];
// 平铺模式
UIImage *tiled = [image resizableImageWithCapInsets:insets resizingMode:UIImageResizingModeTile];
Swift
let image = UIImage(named: "button_bg")!
let insets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
let stretched = image.resizableImage(withCapInsets: insets, resizingMode: .stretch)
let tiled = image.resizableImage(withCapInsets: insets, resizingMode: .tile)
4. 现代方式:Asset Catalog Slicing
Xcode 的 Asset Catalog 支持可视化设置图片切片,无需代码:
- 在 Asset Catalog 中选中图片。
- 点击 Show Slicing → 选择 Start Slicing。
- 选择水平、垂直或水平垂直方向的切片。
- 拖拽可视化滑块设置 cap 区域,或在 Attributes Inspector 中输入精确数值。
- 代码中直接使用
imageNamed:加载,系统自动应用切片设置。
这种方式将拉伸配置与代码解耦,设计师可直接在 Asset Catalog 中调整,推荐使用。
三、总结
| 知识点 | 核心要点 |
|---|---|
| contentEdgeInsets | 图片+文字整体偏移 |
| titleEdgeInsets | 仅标题偏移 |
| imageEdgeInsets | 仅图片偏移 |
| iOS 15+ Configuration | contentInsets + imagePadding 替代传统 edgeInsets |
| stretchableImage... | iOS 5 前的旧 API,已废弃 |
| resizableImageWithCapInsets: | iOS 5+,默认 Stretch 模式 |
| resizableImageWithCapInsets:resizingMode: | iOS 6+,支持 Stretch / Tile 两种模式 |
| Asset Catalog Slicing | 可视化配置切片,推荐现代项目使用 |
- 传统
edgeInsets在复杂图文布局和 Auto Layout 下有局限,新项目优先使用UIButton.Configuration。 - 图片拉伸的核心是九切片机制:四角 cap 不变,中间区域拉伸/平铺。
- 优先使用 Asset Catalog 可视化配置切片,减少硬编码。

浙公网安备 33010602011771号