I have a reusable struct called ExpandableText that lets me shorten the text I give it (so if I limit it to two lines, it adds a read more and expands) but when the text variable changes on a different view (so the text is edited) it recognizes a change but doesn't update the variable. When I put an onChange in the ExpandableText view and print the newValue
and the text
value, newValue
updates to the change from the other view but the text
value remains the old value. I have tried to make text a state variable and onChange assign it to the newValue, but that causes onChange to not even be called. I've also messed with but can't get @Binding var text: String
to work with the shrinkText initial value that is being set in the init. From what I've read that seems to be the best option but I keep getting the error that I can't assign Binding<String>
to string
in that _shrinkText init.
import SwiftUI
struct ExpandableText: View {
@State private var expanded: Bool = false
@State private var truncated: Bool = false
@State private var textChanged: Bool = false
@State private var shrinkText: String
private var text: String
let font: UIFont
let lineLimit: Int
private var moreLessText: String {
if !truncated {
return ""
} else {
return self.expanded ? "Read Less" : "Read More"
}
}
init(_ text: String, lineLimit: Int, font: UIFont = UIFont(name: "Manrope-Regular", size: 16)!) {
self.text = text
self.lineLimit = lineLimit
_shrinkText = State(wrappedValue: text)
self.font = font
}
var body: some View {
VStack(alignment: .leading) {
Text(self.expanded ? text : shrinkText)
.allowsTightening(true)
.fixedSize(horizontal: false, vertical: true)
.font(Font.custom("Manrope-Regular", size: 16))
.dynamicTypeSize(...DynamicTypeSize.large)
if truncated {
Button {
withAnimation(.easeInOut) {
expanded.toggle()
}
} label: {
Text(moreLessText)
.font(Font.custom("Manrope-SemiBold", size: 16))
.dynamicTypeSize(...DynamicTypeSize.large)
}
}
}
.lineLimit(expanded ? nil : lineLimit)
.onChange(of: text) { newValue in
print("text value: \(text)")
print("new value: \(newValue)")
}
.background(
Text(text).lineLimit(lineLimit)
.background(GeometryReader { visibleTextGeometry in
Color.purple.onAppear() {
let size = CGSize(width: visibleTextGeometry.size.width, height: .greatestFiniteMagnitude)
let attributes:[NSAttributedString.Key:Any] = [NSAttributedString.Key.font: font]
var low = 0
var heigh = shrinkText.count
var mid = heigh
while ((heigh - low) > 1) {
let attributedText = NSAttributedString(string: shrinkText + moreLessText, attributes: attributes)
let boundingRect = attributedText.boundingRect(with: size, options: NSStringDrawingOptions.usesLineFragmentOrigin, context: nil)
if boundingRect.size.height > visibleTextGeometry.size.height {
truncated = true
heigh = mid
mid = (heigh + low)/2
} else {
if mid == text.count {
break
} else {
low = mid
mid = (low + heigh)/2
}
}
shrinkText = String("\(text.prefix(mid))...")
}
}
})
.hidden()
)
.font(Font.custom("Manrope-Regular", size: 16)).dynamicTypeSize(...DynamicTypeSize.large)
}
}
UPDATED INIT:
struct ExpandableText: View {
@State private var expanded: Bool = false
@State private var truncated: Bool = false
@State private var textChanged: Bool = false
@State private var shrinkText: String
@Binding var text: String
let font: UIFont
let lineLimit: Int
private var moreLessText: String {
if !truncated {
return ""
} else {
return self.expanded ? "Read Less" : "Read More"
}
}
init(_ text: Binding<String>, lineLimit: Int, font: UIFont = UIFont(name: "Manrope-Regular", size: 16)!) {
self._text = text
self.lineLimit = lineLimit
self._shrinkText = text // <-- error: Cannot assign value of type 'Binding<String>' to type 'State<String>'
self.font = font
}
...
}