查找句子中文本的词框

问题描述 投票:0回答:2

我想找到一个单词在句子中的位置,以便为该单词设置框架。例如:enter image description here

当前是否可以在SwiftUI中做到这一点

我写了一些代码,这就是我得到的

import SwiftUI

struct ContentView: View {

    @State var text = "I usually get ----- around nine o'clock every morning"
    @State var rects = [CGRect.zero]

    var body: some View {
        ZStack {
            TextView(text: $text, rects: $rects)
                .overlay(
                    ForEach(0..<self.rects.count, id: \.self) { index in
                        RoundedRectangle(cornerRadius: 6)
                            .frame(width: self.rects[index].size.width, height: self.rects[index].size.height)
                            .position(self.rects[index].origin)
                            .foregroundColor(Color.red)
                    }
            )

        }
    }
}

struct TextView: UIViewRepresentable {

    @Binding var text: String
    @Binding var rects: [CGRect]

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeUIView(context: Context) -> UITextView {

        let textView = UITextView()
        textView.delegate = context.coordinator
        textView.isEditable = true
        textView.isUserInteractionEnabled = true
        textView.font = UIFont.systemFont(ofSize: 24)
        DispatchQueue.main.async {
            let ranges = self.searchRanges(in: textView.text)
            self.rects = self.viewRects(for: ranges, textView: textView)
        }
        return textView
    }

    func updateUIView(_ uiView: UITextView, context: Context) {
        uiView.text = text
    }

    class Coordinator : NSObject, UITextViewDelegate {

        var parent: TextView

        init(_ uiTextView: TextView) {
            self.parent = uiTextView
        }

        func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
            return true
        }

        func textViewDidChange(_ textView: UITextView) {
            self.parent.text = textView.text
        }
    }

    func searchRanges(in text: String) -> [Range<String.Index>] {
        var ranges = [Range<String.Index>]()
        var searchRange = text.startIndex ..< text.endIndex
        var range = text.range(
            of: "-----",
            options: .caseInsensitive,
            range: searchRange,
            locale: nil
        )
        while let findedRange = range {
            ranges.append(findedRange)
            searchRange = findedRange.upperBound ..< text.endIndex
            range = text.range(
                of: "-----",
                options: .caseInsensitive,
                range: searchRange,
                locale: nil
            )
        }

        return ranges
    }

    func viewRects(for rnges: [Range<String.Index>], textView: UITextView) -> [CGRect] {
        var rects = [CGRect]()
        for range in rnges {
            let upperBound = range.upperBound.utf16Offset(in: textView.text)
            let lowerBound = range.lowerBound.utf16Offset(in: textView.text)
            let length = upperBound - lowerBound

            if let start = textView.position(from: textView.beginningOfDocument, offset: lowerBound),
                let end = textView.position(from: start, offset: length),
                let txtRange = textView.textRange(from: start, to: end) {
                let rect = textView.firstRect(for: txtRange)
                rects.append(rect)
            }
        }
        return rects
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

enter image description here

带有故事板的UIKit上的相同代码

enter image description here

swiftui swiftui-text
2个回答
1
投票

您可以通过分割文本,在文本上循环,然后使用overlay框住所需单词来实现此目的。

请参见此示例:

struct HighlightView: View {
    var words: [FramableWord] = []

    struct FramableWord: Identifiable {
        let id = UUID()
        let text: String
        let isFramed: Bool
    }

    func frame(word: String, in text: String) -> [FramableWord] {
        return text.split(separator: " ").map(String.init).map {
            FramableWord(text: $0, isFramed: $0 == word)
        }
    }

    init() {
        words = frame(word: "up", in: "I get up at 9")
    }

    var body: some View {
        HStack(spacing: 2) {
            ForEach(words) { word -> AnyView in
                if word.isFramed {
                    return AnyView(
                        Text(word.text)
                            .padding(2)
                            .overlay(RoundedRectangle(cornerRadius: 2).stroke(Color.blue, lineWidth: 2))
                    )
                }

                return AnyView(Text(word.text))
            }
        }
    }
}

结果:

example


0
投票

由于我们没有UITextView.firstRect(for:),因此我们无法直接测试您的代码。但是,有两件事很明显:

  1. 您需要在alignment: .topLeading中将ZStack与.overlay()一起使用。一旦您在.overlay()中有多个值,并且默认情况下,SwiftUI从中心布局,您的ForEach将不会始终作为rects的根。
  2. 您需要使用.offset()而不是.position()。位置用于框架的中心。
  3. 
    struct ContentView: View {
        var rects: [CGRect] = [
            .zero,
            CGRect(x: 10, y: 10, width: 10, height: 50),
            CGRect(x: 20, y: 20, width: 10, height: 50),
            CGRect(x: 30, y: 30, width: 10, height: 50)
        ]
    
        func size(for rect: CGRect) -> CGSize {
            CGSize(width: rect.minX, height: rect.minY)
        }
    
        var body: some View {
            Color.blue.opacity(0.125)
                .frame(width: 200, height: 400)
                .overlay(
                    ZStack(alignment: .topLeading) {
                        ForEach(0..<self.rects.count, id: \.self) { idx in
                            Color.red
                                .frame(width: self.rects[idx].width, height: self.rects[idx].height)
                                .offset(self.size(for: self.rects[idx]))
                        }
                    }
            )
        }
    }
    

结果:

Result

© www.soinside.com 2019 - 2024. All rights reserved.