在 SwiftUI 中停用文本每一行的文本换行

Disable text wrapping for each single line of a Text in SwiftUI

提问人:Elektronenhirn 提问时间:10/5/2023 最后编辑:Elektronenhirn 更新时间:10/13/2023 访问量:135

问:

我想在带有元素的小部件中显示一个由多行组成的长字符串。我不知道行的数量或最大行长。不应换行,以便文本元素中的每一行实际上都应该是新行。Text

通缉行为:

enter image description here

默认行为:

Text("1foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo\n2foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo\n3foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo")

enter image description here

将 a 添加到 不起作用,因为该限制适用于整个文本。lineLimitText

拆分字符串并为每行创建一个带有 a 的新元素对我不起作用,因为它会导致小部件中的垂直重叠,因为我不知道小部件中适合多少行。到目前为止,我还没有找到一种解决方案,在固定高度和宽度的帮助下将许多元素剪裁。TextlinieLimitTextGeometryReader

下面是一个显示重叠的示例(缺少第 1 行和第 9 行):enter image description here

import SwiftUI
import WidgetKit

struct ExampleWidgetView: View {
    var body: some View {
        VStack(alignment: .leading, spacing: 3) {
            Text("1foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo")
            Text("2foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo")
            Text("3foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo")
            Text("4foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo")
            Text("5foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo")
            Text("6foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo")
            Text("7foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo")
            Text("8foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo")
            Text("9foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo")
        }
    }
}

struct ExampleWidgetView_Previews: PreviewProvider {
    static var previews: some View {
        ExampleWidgetView()
            .containerBackground(.clear, for: .widget)
            .previewContext(WidgetPreviewContext(family: .systemMedium))
    }
}

使用水平,因此只是隐藏对于有限宽度来说太多的字符也不起作用,因为 ScrollViews 不能在小部件中使用。ScrollView

swift 字符串 swiftui 文本

评论

3赞 Sweeper 10/5/2023
“因为它会导致小部件中的垂直重叠,因为我不知道小部件中适合多少行” 听起来这是您应该解决的问题。请举一个最小的可重复的例子,说明它如何“导致垂直重叠”。
0赞 Elektronenhirn 10/9/2023
我加了一个例子。字符串行数的不确定性是我无法改变的。

答:

2赞 Benzy Neez 10/11/2023 #1

我想说的是,在换行符上拆分字符串,然后在这些子字符串上使用是正确的方法。.lineLimit(1)

你没有解释你希望它如何在有很多线条时工作,但我猜你希望它从第 1 行开始,任何不适合垂直空间的线条都不会显示。

所以这里有一个以这种方式工作的版本。文本以叠加形式显示在清晰的背景上,因为这样可以更好地控制垂直对齐方式。如有必要,改性剂允许小程度的收缩。当线条很长或线条很多时,会使用这种方法。如果你想允许更多的内容被压缩到显示器中,那么你可以使这个因素更小。.minimumScaleFactor

struct ExampleWidgetView: View {

    let veryLongStringWithLineBreaks = """
        1foofoofoofoo foofoofoofoofoo foofoofoofoofoo foofoofoofoofoo
        2foofoofoofoo foofoofoofoofoo foofoofoofoofoo foofoofoofoofoo
        3foofoofoofoo foofoofoofoofoo foofoofoofoofoo foofoofoofoofoo
        4foofoofoofoo foofoofoofoofoo foofoofoofoofoo foofoofoofoofoo
        5foofoofoofoo foofoofoofoofoo foofoofoofoofoo foofoofoofoofoo
        6foofoofoofoo foofoofoofoofoo foofoofoofoofoo foofoofoofoofoo
        7foofoofoofoo foofoofoofoofoo foofoofoofoofoo foofoofoofoofoo
        8foofoofoofoo foofoofoofoofoo foofoofoofoofoo foofoofoofoofoo
        9foofoofoofoo foofoofoofoofoo foofoofoofoofoo foofoofoofoofoo
        """

    var body: some View {
        Color.clear
            .overlay(alignment: .top) {
                VStack(alignment: .leading, spacing: 3) {
                    ForEach(veryLongStringWithLineBreaks.split(separator: "\n"), id: \.self) { line in
                        Text(line)
                            .lineLimit(1)
                            .allowsTightening(true)
                            .minimumScaleFactor(0.8)
                    }
                }
            }
    }
}

Widget


编辑跟进您的评论后,可以通过使用 a 读取可用高度并将内容裁剪到此高度,在底部强制执行边距。喜欢这个:GeometryReader

var body: some View {
    GeometryReader { proxy in
        Group {
            VStack(alignment: .leading, spacing: 3) {
                ForEach(veryLongStringWithLineBreaks.split(separator: "\n"), id: \.self) { line in
                    Text(line)
                        .lineLimit(1)
                        .allowsTightening(true)
                        .minimumScaleFactor(0.8)
                }
            }
            .frame(maxHeight: .infinity)
        }
        .frame(height: proxy.size.height, alignment: .top)
        .clipped()
    }
}

Clipped

您会注意到 嵌套在 .这样一来,当只有几行时,文本行会显示在中间,而不是被推到顶部。VStackGroup

如果您也不喜欢尖锐的截止,那么一种选择是用渐变覆盖底部区域,以产生淡出效果。喜欢这个:

var body: some View {
    GeometryReader { proxy in
        Group {
            // content as above
        }
        .frame(height: proxy.size.height + 10, alignment: .top)
        .clipped()
        .overlay(alignment: .bottom) {
            Rectangle()
                .fill(
                    LinearGradient(
                        colors: [.clear, Color(UIColor.secondarySystemBackground)],
                        startPoint: .top,
                        endPoint: .bottom
                    )
                )
                .frame(height: 50)
        }
    }
}

FadeOut

但是,渐变始终存在,因此,如果行数正好可以填充垂直空间,则最后一行可能会在渐变下方。


编辑2这是为您提供的另一种解决方法。您可以计算适合多少行,并且只显示这个数字。A 可用于提供可用高度,但您可能需要估计线高度。但是,此估计值可以定义为 ,以便它自动适应不同的文本大小设置。此外,您可能需要通过不包括此修饰符来禁用为您提供的自动缩放。GeometryReaderScaledMetric.minimalScaleFactor

喜欢这个:

@ScaledMetric(relativeTo: .body) private var estimatedLineHeight: CGFloat = 20.3
private let spacing: CGFloat = 3

var body: some View {
    GeometryReader { proxy in
        VStack(alignment: .leading, spacing: spacing) {
            ForEach(
                Array(veryLongStringWithLineBreaks.split(separator: "\n").enumerated()),
                id: \.offset
            ) { index, line in
                if (CGFloat(index + 1) * estimatedLineHeight) + (CGFloat(index) * spacing) < proxy.size.height {
                    Text(line)
                        .lineLimit(1)
                        .allowsTightening(true)
                }
            }
        }
        .frame(maxHeight: .infinity)
    }
}

LimitedLines

评论

0赞 Elektronenhirn 10/12/2023
但这并不能解决重叠问题,对吧?没有办法在底部边缘保持与其他边缘相同的填充?
0赞 Benzy Neez 10/13/2023
答案已更新(再次)。