swiftlistswiftuitextline

How to show Text more, less for list view cell in SwiftUi


code: with this code i am showing whole survey.description but i want initially description to be 2 lines but if i click on more it has to show remaining lines and need less if click on less it again need to go to 2 lines.

if the description is not more then 2 lines i dont want even "More" as well.. how to do this

please guide me

struct SurveyListView: View {
@Environment(\.dismiss) var dismiss
@StateObject private var viewModel = SurveyViewModel()

@State private var selectedDataID: String?

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 {
    
    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)
        
            .padding(.horizontal)
            .padding(.top, 1)
        HStack{
            Spacer()
            Text("More")
                .font(.calibriRegular(with: 15))
                .padding(.horizontal)
                .foregroundStyle(.green)
                .onTapGesture {
                    
                }
        }
        .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)
}
}

o/p: here if no description also More has to hide and initially need to show 2 lines and click on more then need to show remaining.

enter image description here


Solution

  • The description can be expanded and collapsed by applying a lineLimit to the Text.

    Whether or not the button to expand/collapse the text is shown should depend on whether the expanded text takes more space (that is, requires more height) than the 2-line text. A GeometryReader can be used to determine this.

    Before adding this functionality, I would suggest a little re-factoring:

    struct AllSurvey: Identifiable {
        let id: String
        // ...
    }
    
    // ForEach(0..<viewModel.allSurvey.count, id: \.self) { ind in
    ForEach(viewModel.allSurvey) { survey in
        // ...
    }
    
    struct SurveyView: View {
        let survey: AllSurvey
    
        var body: some View {
            VStack(alignment: .leading){
                Text(survey.title ?? "N/A")
                    // ...
    
                // etc.
            }
            .frame(maxWidth: .infinity, alignment: .leading)
        }
    }
    
    Button {
        // selectedDataID = viewModel.allSurvey[ind].id
        selectedDataID = survey.id
        gotoQuestions = true
    } label: {
        // surveyListCell(survey: viewModel.allSurvey[ind])
        SurveyView(survey: survey)
            .foregroundStyle(.black)
    }
    

    Now for the new functionality:

    @State private var maxLines: Int? = 2
    
    Text(survey.description ?? "N/A")
        .multilineTextAlignment(.leading)
        .lineLimit(maxLines)
        // ...
    
    Text(maxLines == 2 ? "More" : "Less")
        .font(.calibriRegular(with: 15))
        .padding(.horizontal)
        .foregroundStyle(.green)
        .onTapGesture {
            withAnimation { maxLines = maxLines == 2 ? nil : 2 }
        }
    
    @State private var hasMoreDescription = false
    
    HStack{
        Spacer()
        if hasMoreDescription {
            Text(maxLines == 2 ? "More" : "Less")
                // ...
        }
    }
    
    .background {
        GeometryReader { outer in
            Text(survey.description ?? "")
                .fixedSize(horizontal: false, vertical: true)
                .overlay {
                    GeometryReader { proxy in
                        Color.clear
                            .onAppear {
                                hasMoreDescription = proxy.size.height > outer.size.height
                            }
                    }
                }
                .hidden()
        }
    }
    

    Here is the fully updated view SurveyView:

    struct SurveyView: View {
        let survey: AllSurvey
        @State private var maxLines: Int? = 2
        @State private var hasMoreDescription = false
    
        var body: some View {
            VStack(alignment: .leading){
                Text(survey.title ?? "N/A")
                    // ...
    
                Text(survey.description ?? "N/A")
                    .multilineTextAlignment(.leading)
                    .lineLimit(maxLines)
                    .background {
                        GeometryReader { outer in
                            Text(survey.description ?? "")
                                .fixedSize(horizontal: false, vertical: true)
                                .overlay {
                                    GeometryReader { proxy in
                                        Color.clear
                                            .onAppear {
                                                hasMoreDescription = proxy.size.height > outer.size.height
                                            }
                                    }
                                }
                                .hidden()
                        }
                    }
                    .font(.calibriRegular(with: 16))
                    .foregroundStyle(.gray)
                    .padding(.horizontal)
                    .padding(.top, 1)
                HStack{
                    Spacer()
                    if hasMoreDescription {
                        Text(maxLines == 2 ? "More" : "Less")
                            .font(.calibriRegular(with: 15))
                            .padding(.horizontal)
                            .foregroundStyle(.green)
                            .onTapGesture {
                                withAnimation { maxLines = maxLines == 2 ? nil : 2 }
                            }
                    }
                }
                .padding(.bottom, 5)
    
                HStack(spacing: 0){
                    Text("Published On:")
                        // ...
                    Text(survey.publishedOn ?? "N/A")
                        // ...
                    Spacer()
                }
            }
            .frame(maxWidth: .infinity, alignment: .leading)
        }
    }
    

    Animation