You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的最小宽度,适配性更强:

  1. 添加计算属性获取最长文本的宽度:
private var maxMenuItemWidth: CGFloat {
    menuItems.map { item in
        let text = Text(item)
            .font(Constants.buttonFont)
            .fontWeight(.bold)
        return text.size().width
    }.max() ?? 0
}
  1. 修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 01:05:18