在 UILabel 中生成的文本后添加自定义视图

Add a custom view following generated text in UILabel

提问人:smeshko 提问时间:11/18/2023 更新时间:11/21/2023 访问量:60

问:

我正在生成文本,想象一下 ChatGPT 如何批量生成文本。我想要一个跟随该文本的自定义视图(如光标/插入符号)。我不介意使用 、 或其他任何东西。UILabelUITextFieldUITextView

我试过使用和以下代码UITextView

override func caretRect(for position: UITextPosition) -> CGRect {
    let originalRect = super.caretRect(for: position)
    return CGRect(x: originalRect.origin.x, y: -3, width: 18, height: 36)
}

override func draw(_ rect: CGRect) {
    super.draw(rect)
    let caretRect = self.caretRect(for: self.selectedTextRange?.end ?? self.endOfDocument)
    customCursorImage?.draw(in: caretRect)
}

但它的工作并不那么顺利,我不知道如何隐藏系统光标。

我考虑过找到文本末尾并在那里绘制图像,但我找不到任何 API 来做到这一点。CGPoint

寻找任何和所有的想法。

iOS版 斯威夫特

评论

0赞 DonMag 11/18/2023
仅提供这些代码片段的帮助非常困难。我们需要看看你在做什么来“生成文本”。查看最小可重现示例
0赞 Cristik 11/20/2023
@DonMag这是一个“如何做”的问题,而“如何做”的问题不需要代码。提问者提供了他们的一些尝试,这一事实表明了他们的研究努力,这总是一件好事。
0赞 DonMag 11/21/2023
@Cristik - 我仍然说,如果没有一个完整的例子来展示 OP 到目前为止所做的事情并展示什么“不起作用”,就很难提供帮助。或者,至少用一个最小的可重现的例子来帮助它要容易得多

答:

0赞 DonMag 11/21/2023 #1

我们可以使用文本视图来查找当前文本末尾的位置。selectionRects(for:)

这是一个简单的例子......

class ViewController: UIViewController {
    
    let sampleStr: String = "UILabel: \n\nA label can contain an arbitrary amount of text, but UILabel may shrink, wrap, or truncate the text, depending on the size of the bounding rectangle and properties you set. You can control the font, text color, alignment, highlighting, and shadowing of the text in the label."
    
    let theTextView = UITextView()
    
    let fakeCaretView = UIView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .systemYellow
        
        // text view properties
        theTextView.isEditable = false
        theTextView.font = .systemFont(ofSize: 20.0, weight: .regular)
        
        theTextView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(theTextView)
        
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            theTextView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
            theTextView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            theTextView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            theTextView.heightAnchor.constraint(equalToConstant: 320.0),
        ])
        
        // let's add a button
        let b1 = UIButton()
        b1.setTitle("Do It", for: [])
        b1.backgroundColor = .systemBlue
        b1.setTitleColor(.white, for: .normal)
        b1.setTitleColor(.lightGray, for: .highlighted)
        
        b1.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(b1)
        
        // put the button below the text view
        NSLayoutConstraint.activate([
            b1.topAnchor.constraint(equalTo: theTextView.bottomAnchor, constant: 20.0),
            b1.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
            b1.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
        ])
        
        // actions for button
        b1.addTarget(self, action: #selector(doIt(_:)), for: .touchUpInside)
        
        // a "caret / vertical line" view
        let lh: CGFloat = theTextView.font?.lineHeight ?? 8.0
        
        fakeCaretView.frame = .init(origin: theTextView.frame.origin, size: .init(width: 4.0, height: lh))
        fakeCaretView.backgroundColor = .red
        view.addSubview(fakeCaretView)
    }
    
    @objc func doIt(_ sender: Any?) {
        var activeStrs = sampleStr.components(separatedBy: " ")
        
        // set the text view's text to the first word from the string
        var curStr: String = activeStrs.removeFirst()
        self.theTextView.text = curStr
        
        // get the "end position" and move the fakeCaretView into place
        if let p = self.findPos() {
            self.fakeCaretView.frame.origin = .init(x: self.theTextView.frame.origin.x + p.x,
                                            y: self.theTextView.frame.origin.y + p.y)
        }
        
        // repeating timer to add words one-by-one to the text view
        Timer.scheduledTimer(withTimeInterval: 0.25, repeats: true, block: { t in
            if activeStrs.isEmpty {
                t.invalidate()
            } else {
                // get the next word
                let s = activeStrs.removeFirst()
                // append it to the string
                curStr += " " + s
                self.theTextView.text = curStr
                // get the "end position" and move the fakeCaretView into place
                if let p = self.findPos() {
                    self.fakeCaretView.frame.origin = .init(x: self.theTextView.frame.origin.x + p.x,
                                                    y: self.theTextView.frame.origin.y + p.y)
                }
            }
        })
    }
    
    func findPos() -> CGPoint? {
        
        // get textRange for entire textView string
        guard let r = theTextView.textRange(from: theTextView.beginningOfDocument, to: theTextView.endOfDocument)
        else { return nil }
        
        // get the selectionRects
        let rects = theTextView.selectionRects(for: r)
        
        // get the last one
        guard let lr = rects.last
        else { return nil }
        
        return .init(x: lr.rect.maxX, y: lr.rect.minY)
        
    }
    
}

笔记:

  • 我不知道你是想逐个字符、逐个单词、逐串还是逐段......本金是一样的。
  • 您需要添加逻辑来处理太长而无法放入文本视图的文本。可能自动滚动它?
  • 这只是示例代码!! ...它不打算,也不应该被视为“生产就绪”。