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

SwiftUI列表视图单元格实现文本展开/收起功能

SwiftUI实现描述文本展开/收起功能

需求回顾

  • 默认仅显示2行描述文本
  • 点击“更多”展开全部内容,点击“收起”恢复为2行
  • 当描述内容不足2行或无描述时,自动隐藏“更多/收起”按钮

实现代码

首先添加用于检测文本高度的PreferenceKey:

struct TextHeightPreferenceKey: PreferenceKey {
    static var defaultValue: CGFloat = 0
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }
}

然后修改SurveyListView的代码,重点调整surveyListCell实现:

struct SurveyListView: View {
    @Environment(\.dismiss) var dismiss
    @StateObject private var viewModel = SurveyViewModel()
    
    @State private var selectedDataID: String?
    @State private var gotoQuestions = false // 补充原代码缺失的状态变量
    
    var body: some View {
        ZStack {
            VStack() {
                ScrollView{
                    ForEach(0..<viewModel.allSurvey.count, id: \.self) { ind in
                        Button {
                            selectedDataID = viewModel.allSurvey[ind].id
                            gotoQuestions = true
                        } label: {
                            surveyListCell(survey: viewModel.allSurvey[ind])
                                .foregroundStyle(.black)
                        }
                    }
                    .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
                    
                    .navigationDestination(isPresented: $gotoQuestions) {
                        SurveyQuestionsView(id: selectedDataID ?? "")
                            .toolbar(.hidden, for: .navigationBar)
                    }
                }
                Spacer()
            }
        }
        .onAppear{
            viewModel.fetchSurveyList { status in
            }
        }
    }
    
    @ViewBuilder func surveyListCell(survey: AllSurvey) -> some View {
        @State private var isExpanded = false
        @State private var textHeight: CGFloat = 0
        // 计算2行文本的预估高度(行高*2 + 行间距)
        let twoLineHeight = (UIFont(name: "Calibri", size: 16)?.lineHeight ?? 20) * 2 + 4
        
        VStack(alignment: .leading){
            Text(survey.title ?? "N/A")
                .font(.calibriRegular(with: 18))
                .padding(.horizontal)
                .padding(.bottom, 3)
            
            Text(survey.description ?? "N/A")
                .font(.calibriRegular(with: 16))
                .foregroundStyle(.gray)
                .lineLimit(isExpanded ? nil : 2)
                .padding(.horizontal)
                .padding(.top, 1)
                // 通过GeometryReader获取文本实际高度
                .background(
                    GeometryReader { proxy in
                        Color.clear
                            .preference(key: TextHeightPreferenceKey.self, value: proxy.size.height)
                    }
                )
                .onPreferenceChange(TextHeightPreferenceKey.self) { height in
                    textHeight = height
                }
            
            // 仅当文本高度超过2行预估高度时显示按钮
            if textHeight > twoLineHeight {
                HStack{
                    Spacer()
                    Text(isExpanded ? "收起" : "更多")
                        .font(.calibriRegular(with: 15))
                        .padding(.horizontal)
                        .foregroundStyle(.green)
                        .onTapGesture {
                            isExpanded.toggle()
                        }
                }
                .padding(.bottom, 5)
            }
            
            HStack(spacing: 0){
                Text("Published On:")
                    .font(.calibriRegular(with: 14))
                    .padding(.horizontal)
                    .foregroundStyle(.gray)
                Text(survey.publishedOn ?? "N/A")
                    .font(.calibriRegular(with: 14))
                    .foregroundStyle(.gray)
                    .padding(.horizontal)
                Spacer()
            }
        }
        .frame(maxWidth: .infinity, alignment: .leading)
    }
}

// 保留原项目的字体扩展
extension Font {
    static func calibriRegular(with size: CGFloat) -> Font {
        return Font.custom("Calibri", size: size)
    }
}

关键说明

  1. 文本高度检测:通过GeometryReader和自定义PreferenceKey获取文本实际渲染高度,与预估的2行文本高度对比,动态控制按钮显示
  2. 独立状态管理:每个cell使用独立的isExpanded状态变量,避免多个cell状态互相干扰
  3. 行数控制:利用lineLimit修饰符,收起时限制为2行,展开时设为nil显示全部内容

内容的提问来源于stack exchange,提问作者Swift

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 10:46:00