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

SwiftUI中如何用.lineLimit实现文本展开/收起并适配文本长度

SwiftUI实现文本展开/收起(自动隐藏多余按钮)

你的代码目前有两个核心问题:

  • 把整个文本区域做成了按钮,点击任意位置都会触发展开/收起,体验不符合预期
  • 不管文本实际长度是否超过限制行数,按钮(整个文本区域)都会存在,做不到「长度不足时隐藏按钮」的需求

下面是符合需求的实现方案,能自动判断文本是否需要截断,只在需要时显示「展开/收起」按钮,点击按钮才会切换状态:

完整实现代码

import SwiftUI

// 用于获取文本渲染高度的PreferenceKey
private struct TextHeightPreferenceKey: PreferenceKey {
    static var defaultValue: CGFloat = 0
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = max(value, nextValue())
    }
}

struct ExpandableText: View {
    let text: String
    let lineLimit: Int
    let font: Font
    
    @State private var isExpanded = false
    @State private var shouldShowToggle = false
    @State private var truncatedHeight: CGFloat = 0
    @State private var fullHeight: CGFloat = 0
    
    init(text: String, lineLimit: Int = 5, font: Font = .system(size: 15)) {
        self.text = text
        self.lineLimit = lineLimit
        self.font = font
    }
    
    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            Text(text)
                .font(font)
                .foregroundColor(.white)
                .multilineTextAlignment(.leading)
                .lineLimit(isExpanded ? nil : lineLimit)
                .background(
                    // 计算限制行数时的文本高度
                    Text(text)
                        .font(font)
                        .lineLimit(lineLimit)
                        .background(GeometryReader { proxy in
                            Color.clear.preference(key: TextHeightPreferenceKey.self, value: proxy.size.height)
                        })
                        .hidden()
                )
                .background(
                    // 计算文本完整显示的高度
                    Text(text)
                        .font(font)
                        .background(GeometryReader { proxy in
                            Color.clear.preference(key: TextHeightPreferenceKey.self, value: proxy.size.height)
                        })
                        .hidden()
                )
                .onPreferenceChange(TextHeightPreferenceKey.self) { heights in
                    // 第一次获取到的是限制行数的高度,第二次是完整高度
                    if truncatedHeight == 0 {
                        truncatedHeight = heights
                    } else {
                        fullHeight = heights
                        // 当完整高度大于限制高度时,显示切换按钮(加1避免精度误差)
                        shouldShowToggle = fullHeight > truncatedHeight + 1
                    }
                }
            
            // 只在需要时显示展开/收起按钮
            if shouldShowToggle {
                Button(action: {
                    withAnimation(.easeInOut) {
                        isExpanded.toggle()
                    }
                }) {
                    Text(isExpanded ? "收起" : "展开")
                        .font(.system(size: 14, weight: .medium))
                        .foregroundColor(.white)
                }
                .padding(.leading, 8)
            }
        }
        .padding()
        .background(Color.pink)
        .cornerRadius(30)
        .padding()
    }
}

// 测试用View
struct Testing: View {
    var body: some View {
        VStack(spacing: 20) {
            // 长文本(会显示展开/收起)
            ExpandableText(text: "When it comes to chicken there just isn’t anything more delicious than a juicy, crusty piece of finger-licking good fried chicken. It might seem intimidating to fry your own chicken, but it’s actually pretty straightforward and it puts grocery store and fast food fried chicken to shame.  If you have a thermometer for the oil and a timer, you can produce fail-proof fried chicken.  If you’ve ever wanted to make your own fried chicken, now is the time to try!")
            
            // 短文本(不会显示按钮)
            ExpandableText(text: "这是一段很短的测试文本,长度不会超过5行。")
        }
    }
}

关键逻辑说明

  1. 文本高度判断:通过PreferenceKey和GeometryReader分别获取文本「限制行数时的高度」和「完整显示的高度」,对比两者判断文本是否需要截断
  2. 按钮显示逻辑:只有当完整高度大于限制高度时,才显示「展开/收起」按钮
  3. 交互优化:把按钮和文本分开,只有点击按钮才会切换状态,同时加入动画让展开/收起更流畅
  4. 可复用性:封装成ExpandableText组件,方便在项目中重复使用,可自定义行数、字体等参数

内容的提问来源于stack exchange,提问作者Dr.dia

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 08:07:32