SwiftUI菜单按钮标题长度变化时调整尺寸卡顿,如何解决?
SwiftUI菜单切换文本尺寸适配延迟问题解决
问题描述
我编写了SwiftUI菜单代码,点击菜单项时标题数组索引会更新,文本标题能正常切换,但文本调整尺寸时存在延迟,会短暂显示"Last Year..."后才完成正确尺寸适配。
原代码
import SwiftUI struct ContentView: View { private enum Constants { static let arrowIcon = Image(systemName: "chevron.down") static let background = Color.blue static let buttonFont = Font.system(size: 14.0) } @State private var menuIndex = 0 private let menuItems = ["This year", "Last Year"] private var menuTitle: String { guard menuItems.indices.contains(menuIndex) else { return "" } return menuItems[menuIndex] } // MARK: - Views var body: some View { makeMenu() } // MARK: - Buttons private func menuItemTapped(title: String) { guard let index = menuItems.firstIndex(of: title) else { return } menuIndex = index } // MARK: - Factory @ViewBuilder private func makeMenu() -> some View { Menu { ForEach(menuItems, id: \.self) { title in Button(title, action: { menuItemTapped(title: title) }) } } label: { Text("\(menuTitle) \(Constants.arrowIcon)") .font(Constants.buttonFont) .fontWeight(.bold) } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
问题原因
这是SwiftUI Menu组件的默认布局行为导致的:切换标签时,组件会先尝试复用当前的布局空间,当新文本长度与原有文本差异较大时,布局重新计算的过程会产生滞后,从而出现临时文本截断的现象。
解决方案
方案1:设置固定最小宽度
给Menu的label设置足够的最小宽度,提前预留出最长文本所需的空间,避免动态调整布局的延迟:
@ViewBuilder private func makeMenu() -> some View { Menu { ForEach(menuItems, id: \.self) { title in Button(title, action: { menuItemTapped(title: title) }) } } label: { Text("\(menuTitle) \(Constants.arrowIcon)") .font(Constants.buttonFont) .fontWeight(.bold) .frame(minWidth: 100) // 根据实际文本长度调整数值 } }
方案2:动态计算最长文本宽度
通过计算所有菜单项文本的宽度,精准设置label的最小宽度,适配性更强:
- 添加计算属性获取最长文本的宽度:
private var maxMenuItemWidth: CGFloat { menuItems.map { item in let text = Text(item) .font(Constants.buttonFont) .fontWeight(.bold) return text.size().width }.max() ?? 0 }
- 修改label的布局,预留箭头的额外空间:
@ViewBuilder private func makeMenu() -> some View { Menu { ForEach(menuItems, id: \.self) { title in Button(title, action: { menuItemTapped(title: title) }) } } label: { HStack(spacing: 4) { Text(menuTitle) .font(Constants.buttonFont) .fontWeight(.bold) Constants.arrowIcon } .frame(minWidth: maxMenuItemWidth + 20) // +20为箭头预留空间 } }
方案3:拆分文本与箭头布局
将文本和箭头分开用HStack布局,避免合并文本导致的布局计算冲突,配合固定/动态宽度使用,效果更稳定:
@ViewBuilder private func makeMenu() -> some View { Menu { ForEach(menuItems, id: \.self) { title in Button(title, action: { menuItemTapped(title: title) }) } } label: { HStack(spacing: 4) { Text(menuTitle) .font(Constants.buttonFont) .fontWeight(.bold) Constants.arrowIcon } .frame(minWidth: 100) } }
内容的提问来源于stack exchange,提问作者Kex
相关产品推荐
相关产品推荐

