用点填充文本行中的剩余空白(多行文本)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 做到这一点:

  1. 使用这种方法查找最后一行中的文本:
  2. 使用let size = lastLineText.size(withAttributes: [.font: label.font]) ?? .zero
  3. 获取最后一行的大小
  4. let gap = label.frame.width - size.width
  5. let dotsCount = gap / dotWidth
  6. 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