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.
The description can be expanded and collapsed by applying a lineLimit
to the Text
.
nil
, which means, unlimited.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:
AllSurvey
should implement Identifiable
, if it doesn't already:struct AllSurvey: Identifiable {
let id: String
// ...
}
ForEach
can then iterate over the array of identifiable items, which avoids using an index into the array:// ForEach(0..<viewModel.allSurvey.count, id: \.self) { ind in
ForEach(viewModel.allSurvey) { survey in
// ...
}
surveyListCell
into a View
. The body of the function simply becomes the body
of the view:struct SurveyView: View {
let survey: AllSurvey
var body: some View {
VStack(alignment: .leading){
Text(survey.title ?? "N/A")
// ...
// etc.
}
.frame(maxWidth: .infinity, alignment: .leading)
}
}
ForEach
: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
variable to SurveyView
to hold the maximum number of lines to show:@State private var maxLines: Int? = 2
lineLimit
to the Text
showing the description:Text(survey.description ?? "N/A")
.multilineTextAlignment(.leading)
.lineLimit(maxLines)
// ...
nil
when the button to expand/collapse the text is tapped. The same button can be used for both actions, the label can change depending on the value of the state variable: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")
// ...
}
}
Text
and use a GeometryReader
to measure its height. If the full height is greater than the 2-line height then this means more text is available. This information is stored in the boolean state variable in .onAppear
. By applying the modifier .hidden()
, this background content stays hidden..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)
}
}