struct LongText: View {
/* Indicates whether the user want to see all the text or not. */
@State private var expanded: Bool = false
/* Indicates whether the text has been truncated in its display. */
@State private var truncated: Bool = false
private var text: String
init(_ text: String) {
self.text = text
}
private func determineTruncation(_ geometry: GeometryProxy) {
// Calculate the bounding box we'd need to render the
// text given the width from the GeometryReader.
let total = self.text.boundingRect(
with: CGSize(
width: geometry.size.width,
height: .greatestFiniteMagnitude
),
options: .usesLineFragmentOrigin,
attributes: [.font: UIFont.systemFont(ofSize: 16)],
context: nil
)
if total.size.height > geometry.size.height {
self.truncated = true
}
}
var body: some View {
VStack(alignment: .leading, spacing: 10) {
Text(self.text)
.font(.system(size: 16))
.lineLimit(self.expanded ? nil : 3)
// see https://swiftui-lab.com/geometryreader-to-the-rescue/,
// and https://swiftui-lab.com/communicating-with-the-view-tree-part-1/
.background(GeometryReader { geometry in
Color.clear.onAppear {
self.determineTruncation(geometry)
}
})
if self.truncated {
self.toggleButton
}
}
}
var toggleButton: some View {
Button(action: { self.expanded.toggle() }) {
Text(self.expanded ? "Show less" : "Show more")
.font(.caption)
}
}
}
struct LongText: View {
/* Indicates whether the user want to see all the text or not. */
@State private var expanded: Bool = false
/* Indicates whether the text has been truncated in its display. */
@State private var truncated: Bool = false
private var text: String
var lineLimit = 3
init(_ text: String) {
self.text = text
}
var body: some View {
VStack(alignment: .leading) {
// Render the real text (which might or might not be limited)
Text(text)
.lineLimit(expanded ? nil : lineLimit)
.background(
// Render the limited text and measure its size
Text(text).lineLimit(lineLimit)
.background(GeometryReader { displayedGeometry in
// Create a ZStack with unbounded height to allow the inner Text as much
// height as it likes, but no extra width.
ZStack {
// Render the text without restrictions and measure its size
Text(self.text)
.background(GeometryReader { fullGeometry in
// And compare the two
Color.clear.onAppear {
self.truncated = fullGeometry.size.height > displayedGeometry.size.height
}
})
}
.frame(height: .greatestFiniteMagnitude)
})
.hidden() // Hide the background
)
if truncated { toggleButton }
}
}
var toggleButton: some View {
Button(action: { self.expanded.toggle() }) {
Text(self.expanded ? "Show less" : "Show more")
.font(.caption)
}
}
}
struct ContentView: View {
let longString = "This is very long text designed to create enough wrapping to force a More button to appear. Just a little more should push it over the edge and get us to one more line."
var body: some View {
VStack {
Text("BEFORE TEXT")
LongText(longString).font(.largeTitle)
LongText(longString).font(.caption)
Text("AFTER TEXT")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct ExpandableText: View {
let text: String
let lineLimit: Int
@State private var isExpanded: Bool = false
@State private var isTruncated: Bool? = nil
var body: some View {
VStack(alignment: .leading, spacing: 10) {
Text(text)
.lineLimit(isExpanded ? nil : lineLimit)
.background(calculateTruncation(text: text))
if isTruncated == true {
button
}
}
.multilineTextAlignment(.leading)
// Re-calculate isTruncated for the new text
.onChange(of: text, perform: { _ in isTruncated = nil })
}
func calculateTruncation(text: String) -> some View {
// Select the view that fits in the background of the line-limited text.
ViewThatFits(in: .vertical) {
Text(text)
.hidden()
.onAppear {
// If the whole text fits, then isTruncated is set to false and no button is shown.
guard isTruncated == nil else { return }
isTruncated = false
}
Color.clear
.hidden()
.onAppear {
// If the whole text does not fit, Color.clear is selected,
// isTruncated is set to true and button is shown.
guard isTruncated == nil else { return }
isTruncated = true
}
}
}
var button: some View {
Button(isExpanded ? "Show less" : "Show more") {
isExpanded.toggle()
}
}
}
4条答案
按热度按时间cwtwac6a1#
您可以使用
GeometryReader
来确定文本字段的宽度,然后将其与有关字体的信息一起使用,以计算显示整个文本所需的边界矩形的大小。如果该高度超过文本视图,则我们知道文本已被截断。这就是它在长文本和短文本中的样子:
希望这个有用。
gtlvzcf82#
基于bhuemer的出色工作,这个版本尊重SwiftUI的本地字体,而不是要求硬编码的UIFont。而不是使用String布局阅读“完整”文本的大小,这将呈现文本三次:一次是真实的,一次有行限制,一次没有行限制。然后它使用两个GR来比较最后两个。
下面显示了周围视图的行为。注意,这种方法支持
LongText(...).font(.largeTitle)
,就像普通的Text一样。laawzig23#
在iOS 16+中,可以通过使用
ViewThatFits
设置为行限制文本的隐藏背景来实现这一点。此方法还考虑更改时的系统字体大小。
hfyxw5xn4#
下面是一个例子: