用点填充文本行中的剩余空白(多行文本)iOS
Fill remaining whitespace in text line with dots (multiline text) iOS
我想用虚线填充最后一行的剩余空白。它应该从最后一个单词的末尾开始,一直持续到该行的末尾。这对 SwiftUI 甚至 UIKit 来说可能吗?
我有:
我需要的:
struct ContentView: View {
var body: some View {
let fontSize = UIFont.preferredFont(forTextStyle: .headline).lineHeight
let text = "Whosebug Whosebug Whosebug Whosebug Whosebug Whosebug Whosebug Whosebug"
HStack(alignment: .lastTextBaseline, spacing: .zero) {
HStack(alignment: .top, spacing: .zero) {
Circle()
.foregroundColor(.green)
.frame(width: 6, height: 6)
.frame(height: fontSize, alignment: .center)
ZStack(alignment: .bottom) {
HStack(alignment: .lastTextBaseline, spacing: .zero) {
Text("")
.font(.headline)
.padding(.leading, 5)
Spacer(minLength: 10)
.overlay(Line(), alignment: .bottom)
}
HStack(alignment: .lastTextBaseline, spacing: .zero) {
Text(text)
.font(.headline)
.padding(.leading, 5)
Spacer(minLength: 10)
}
}
}
}
}
}
struct Line: View {
var width: CGFloat = 1
var color = Color.gray
var body: some View {
LineShape(width: width)
.stroke(style: StrokeStyle(lineWidth: 3, dash: [3]))
.foregroundColor(color)
.frame(height: width)
}
}
private struct LineShape: Shape {
var width: CGFloat
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint())
path.addLine(to: CGPoint(x: rect.width, y: .zero))
return path
}
}
你可以用 UIKit 做到这一点:
- 使用这种方法查找最后一行中的文本:
- 使用
let size = lastLineText.size(withAttributes: [.font: label.font]) ?? .zero
获取最后一行的大小
let gap = label.frame.width - size.width
let dotsCount = gap / dotWidth
let resultText = sourceText + String(repeating: " .", count: dotsCount)
这是我的解决方案,虽然有点笨拙,但更简单:向文本添加白色突出显示,使其覆盖虚线。
我们可以使用 NSAttributedString
添加高亮显示。 SwiftUI 默认不支持这个,所以我们需要使用 UIViewRepresentable
。在这里,基于 :
struct HighlightedText: View {
var text: String
@State private var height: CGFloat = .zero
private var fontStyle: UIFont.TextStyle = .body
init(_ text: String) { self.text = text }
var body: some View {
InternalHighlightedText(text: text, dynamicHeight: $height, fontStyle: fontStyle)
.frame(minHeight: height) /// allow text wrapping
.fixedSize(horizontal: false, vertical: true) /// preserve the Text sizing
}
struct InternalHighlightedText: UIViewRepresentable {
var text: String
@Binding var dynamicHeight: CGFloat
var fontStyle: UIFont.TextStyle
func makeUIView(context: Context) -> UILabel {
let label = UILabel()
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
label.font = UIFont.preferredFont(forTextStyle: fontStyle)
return label
}
func updateUIView(_ uiView: UILabel, context: Context) {
let attributedText = NSAttributedString(string: text, attributes: [.backgroundColor: UIColor.systemBackground])
uiView.attributedText = attributedText /// set white background color here
uiView.font = UIFont.preferredFont(forTextStyle: fontStyle)
DispatchQueue.main.async {
dynamicHeight = uiView.sizeThatFits(CGSize(width: uiView.bounds.width, height: CGFloat.greatestFiniteMagnitude)).height
}
}
}
/// enable .font modifier
func font(_ fontStyle: UIFont.TextStyle) -> HighlightedText {
var view = self
view.fontStyle = fontStyle
return view
}
}
然后,只需将 Text(text)
替换为 HighlightedText(text)
。
struct ContentView: View {
var body: some View {
let fontSize = UIFont.preferredFont(forTextStyle: .headline).lineHeight
let text = "Whosebug Whosebug Whosebug Whosebug Whosebug Whosebug Whosebug Whosebug"
HStack(alignment: .lastTextBaseline, spacing: .zero) {
HStack(alignment: .top, spacing: .zero) {
Circle()
.foregroundColor(.green)
.frame(width: 6, height: 6)
.frame(height: fontSize, alignment: .center)
ZStack(alignment: .bottom) {
HStack(alignment: .lastTextBaseline, spacing: .zero) {
Text("")
.font(.headline)
.padding(.leading, 5)
Spacer(minLength: 10)
.overlay(Line(), alignment: .bottom)
}
HStack(alignment: .lastTextBaseline, spacing: .zero) {
HighlightedText(text) /// here!
.font(.headline)
.padding(.leading, 5)
Spacer(minLength: 10)
}
}
}
}
}
}
struct Line: View {
var width: CGFloat = 1
var color = Color.gray
var body: some View {
LineShape(width: width)
.stroke(style: StrokeStyle(lineWidth: 3, dash: [3]))
.foregroundColor(color)
.frame(height: width)
}
}
private struct LineShape: Shape {
var width: CGFloat
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint())
path.addLine(to: CGPoint(x: rect.width, y: .zero))
return path
}
}
Before
After
我想用虚线填充最后一行的剩余空白。它应该从最后一个单词的末尾开始,一直持续到该行的末尾。这对 SwiftUI 甚至 UIKit 来说可能吗?
我有:
我需要的:
struct ContentView: View {
var body: some View {
let fontSize = UIFont.preferredFont(forTextStyle: .headline).lineHeight
let text = "Whosebug Whosebug Whosebug Whosebug Whosebug Whosebug Whosebug Whosebug"
HStack(alignment: .lastTextBaseline, spacing: .zero) {
HStack(alignment: .top, spacing: .zero) {
Circle()
.foregroundColor(.green)
.frame(width: 6, height: 6)
.frame(height: fontSize, alignment: .center)
ZStack(alignment: .bottom) {
HStack(alignment: .lastTextBaseline, spacing: .zero) {
Text("")
.font(.headline)
.padding(.leading, 5)
Spacer(minLength: 10)
.overlay(Line(), alignment: .bottom)
}
HStack(alignment: .lastTextBaseline, spacing: .zero) {
Text(text)
.font(.headline)
.padding(.leading, 5)
Spacer(minLength: 10)
}
}
}
}
}
}
struct Line: View {
var width: CGFloat = 1
var color = Color.gray
var body: some View {
LineShape(width: width)
.stroke(style: StrokeStyle(lineWidth: 3, dash: [3]))
.foregroundColor(color)
.frame(height: width)
}
}
private struct LineShape: Shape {
var width: CGFloat
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint())
path.addLine(to: CGPoint(x: rect.width, y: .zero))
return path
}
}
你可以用 UIKit 做到这一点:
- 使用这种方法查找最后一行中的文本:
- 使用
let size = lastLineText.size(withAttributes: [.font: label.font]) ?? .zero
获取最后一行的大小
let gap = label.frame.width - size.width
let dotsCount = gap / dotWidth
let resultText = sourceText + String(repeating: " .", count: dotsCount)
这是我的解决方案,虽然有点笨拙,但更简单:向文本添加白色突出显示,使其覆盖虚线。
我们可以使用 NSAttributedString
添加高亮显示。 SwiftUI 默认不支持这个,所以我们需要使用 UIViewRepresentable
。在这里,基于
struct HighlightedText: View {
var text: String
@State private var height: CGFloat = .zero
private var fontStyle: UIFont.TextStyle = .body
init(_ text: String) { self.text = text }
var body: some View {
InternalHighlightedText(text: text, dynamicHeight: $height, fontStyle: fontStyle)
.frame(minHeight: height) /// allow text wrapping
.fixedSize(horizontal: false, vertical: true) /// preserve the Text sizing
}
struct InternalHighlightedText: UIViewRepresentable {
var text: String
@Binding var dynamicHeight: CGFloat
var fontStyle: UIFont.TextStyle
func makeUIView(context: Context) -> UILabel {
let label = UILabel()
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
label.font = UIFont.preferredFont(forTextStyle: fontStyle)
return label
}
func updateUIView(_ uiView: UILabel, context: Context) {
let attributedText = NSAttributedString(string: text, attributes: [.backgroundColor: UIColor.systemBackground])
uiView.attributedText = attributedText /// set white background color here
uiView.font = UIFont.preferredFont(forTextStyle: fontStyle)
DispatchQueue.main.async {
dynamicHeight = uiView.sizeThatFits(CGSize(width: uiView.bounds.width, height: CGFloat.greatestFiniteMagnitude)).height
}
}
}
/// enable .font modifier
func font(_ fontStyle: UIFont.TextStyle) -> HighlightedText {
var view = self
view.fontStyle = fontStyle
return view
}
}
然后,只需将 Text(text)
替换为 HighlightedText(text)
。
struct ContentView: View {
var body: some View {
let fontSize = UIFont.preferredFont(forTextStyle: .headline).lineHeight
let text = "Whosebug Whosebug Whosebug Whosebug Whosebug Whosebug Whosebug Whosebug"
HStack(alignment: .lastTextBaseline, spacing: .zero) {
HStack(alignment: .top, spacing: .zero) {
Circle()
.foregroundColor(.green)
.frame(width: 6, height: 6)
.frame(height: fontSize, alignment: .center)
ZStack(alignment: .bottom) {
HStack(alignment: .lastTextBaseline, spacing: .zero) {
Text("")
.font(.headline)
.padding(.leading, 5)
Spacer(minLength: 10)
.overlay(Line(), alignment: .bottom)
}
HStack(alignment: .lastTextBaseline, spacing: .zero) {
HighlightedText(text) /// here!
.font(.headline)
.padding(.leading, 5)
Spacer(minLength: 10)
}
}
}
}
}
}
struct Line: View {
var width: CGFloat = 1
var color = Color.gray
var body: some View {
LineShape(width: width)
.stroke(style: StrokeStyle(lineWidth: 3, dash: [3]))
.foregroundColor(color)
.frame(height: width)
}
}
private struct LineShape: Shape {
var width: CGFloat
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint())
path.addLine(to: CGPoint(x: rect.width, y: .zero))
return path
}
}
Before | After |
---|---|