SwiftUI 按钮图标如何在工具栏和溢出菜单中都正确显示?
建议保留包含文字和图标的 Label,先使用工具栏的默认标签样式。如果自定义图片仍以原始尺寸显示,再把图片预先绘制成目标尺寸的 UIImage,交给 Label 使用。
原问题没有提供 iOS 版本和完整的工具栏声明,无法确认具体触发条件,也不能保证只增加某个 modifier 就能解决所有环境中的问题。
为什么两种写法表现不同?
第一种写法只有 Image,没有向系统提供按钮的文字标题。第二种写法用 Label 同时提供标题和图标,系统可以根据工具栏或菜单的显示环境使用这些信息。Apple 的 Label 文档介绍了这种标题与图标的组合方式。
.resizable()、.scaledToFit() 和 .frame(...) 调整的是 SwiftUI 视图的布局和绘制方式,不会生成尺寸更小的图片资源。
根据题目描述的现象,可以怀疑工具栏处理 Label 图标时没有沿用这些视图修饰符,而是使用了底层图片。这只是一个排查方向,现有信息还不足以认定它是某个版本的框架缺陷。
先使用默认的 Label 样式
先移除显式设置的 .labelStyle(.iconOnly),保留标题和图标,观察系统在工具栏和溢出菜单中的默认表现:
Button {
// action
} label: {
Label {
Text("Button label")
} icon: {
Image(imageName)
.resizable()
.scaledToFit()
.frame(
width: toolbarButtonImageSize,
height: toolbarButtonImageSize
)
}
}
这样保留了菜单需要的文字信息,也避免在按钮上额外强制标签样式。如果外层视图设置了 .labelStyle(...),也要检查它的影响。
这一步用于验证样式是否参与了问题。如果工具栏仍显示原始尺寸的图标,再处理图片本身。
将自定义图片预先绘制到目标尺寸
对于 iOS 上通过 Image(imageName) 加载的普通图片资源,可以先生成目标尺寸的 UIImage。这样,即使工具栏直接使用底层图片,拿到的也是已经缩小的图像。
下面的函数保持宽高比,将图片的最长边调整到 maxSide。maxSide 的单位是 point:
import SwiftUI
import UIKit
func resizedToolbarIcon(
_ image: UIImage,
maxSide: CGFloat
) -> UIImage {
guard maxSide > 0,
image.size.width > 0,
image.size.height > 0 else {
return image
}
let ratio = maxSide / max(
image.size.width,
image.size.height
)
let outputSize = CGSize(
width: image.size.width * ratio,
height: image.size.height * ratio
)
let renderer = UIGraphicsImageRenderer(size: outputSize)
return renderer.image { _ in
image.draw(in: CGRect(origin: .zero, size: outputSize))
}
}
UIGraphicsImageRenderer 可以按指定尺寸创建图像,image(actions:) 返回绘制后的 UIImage。这部分 API 行为见 Apple 文档。
加载资源时生成并保存处理后的图片:
let toolbarIcon = UIImage(named: imageName).map {
resizedToolbarIcon(
$0,
maxSide: toolbarButtonImageSize
)
}
然后在原来的 ToolbarItem 中使用它:
if let toolbarIcon = toolbarIcon {
Button {
// action
} label: {
Label {
Text("Button label")
} icon: {
Image(uiImage: toolbarIcon)
.renderingMode(.template)
}
}
}
这里不再依赖 .resizable() 和 .frame(...) 缩小图标。示例中的 .template 适合随系统着色的单色图标;如果要保留图片原有颜色,可以改为 .original。
实际项目中应缓存生成的图片,避免每次 body 更新时重新绘制。资源加载失败时,上面的条件分支不会生成按钮,需要检查图片名称和资源所属的 bundle。
验证与注意事项
分别检查横屏和竖屏时的工具栏,以及展开后的溢出菜单,确认图标大小、菜单文字和按钮动作是否符合预期。
预先绘制可以控制传入图片的尺寸,但工具栏和菜单仍可能各自施加布局规则,因此不能保证两处图标的视觉尺寸完全相同。图片资源中的透明留白也会影响图标看起来的大小。
如果问题仍然存在,需要补充 iOS 版本、ToolbarItem 或 ToolbarItemGroup 的完整声明、placement,以及图片资源尺寸。若必须分别控制工具栏和菜单的展示,可以考虑显式创建 Menu,但这会把菜单内容及哪些操作进入菜单的管理交给应用,不会自动复现系统的溢出行为。
备注:内容仅供参考。
