在 UITextView 周围创建 CGRect - 错误的高度 [英] Creating a CGRect around a UITextView - Wrong Height

查看:28
本文介绍了在 UITextView 周围创建 CGRect - 错误的高度的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在 UITextview 的左侧创建一个动态列,该列与每个段落的高度相匹配.出于某种原因,我在获取范围的正确高度时遇到了问题.我正在使用:

let test = textView.firstRect(for:models.first!.range)

当您继续输入时,它会落后一行.示例:

2 行

3 行

有什么想法吗?

解决方案

这是文档可以使用一些帮助的示例...

来自

您没有矩形.使用调试视图层次结构:

很明显,您有两个矩形.

所以,func firstRect(for range: UITextRange) ->CGRect 实际上返回包含范围所需的矩形集中的第一个矩形.

要获取文本范围(例如段落)的实际高度,您需要使用:

let rects = selectionRects(for: textRange)

然后遍历返回的 UITextSelectionRect 对象数组.

<小时>

有各种不同的方法来实现这一点,但这里有一个简单的快速循环示例,循环选择矩形并求和它们的高度:

<代码>////ParagraphMarkerViewController.swift////由 Don Mag 于 2019 年 6 月 17 日创建.//导入 UIKit扩展 UITextView {func boundingFrame(ofTextRange range: Range?) ->CG矩形?{守卫让范围=范围其他{返回零}让长度 = range.upperBound.encodedOffset-range.lowerBound.encodedOffset警卫让 start = position(from: beginOfDocument, offset: range.lowerBound.encodedOffset),让结束 = 位置(从:开始,偏移:长度),让 txtRange = textRange(从:开始,到:结束)否则{返回零}//我们现在有一个 UITextRange,因此获取该范围的选择矩形让 rects = selectionRects(for: txtRange)//初始化我们的返回矩形var returnRect = CGRect.zero//对于每个选择矩形对于矩形中的 thisSelRect {//如果是第一个,只需设置返回矩形如果 thisSelRect == rects.first {returnRect = thisSelRect.rect} 别的 {//忽略宽度为零的选择矩形如果 thisSelRect.rect.size.width >0 {//我们只关心顶部(最小origin.y)和//高度的总和returnRect.origin.y = min(returnRect.origin.y, thisSelRect.rect.origin.y)returnRect.size.height += thisSelRect.rect.size.height}}}返回返回矩形}}类 ParagraphMarkerViewController: UIViewController, UITextViewDelegate {var theTextView: UITextView = {让 v = UITextView()v.translatesAutoresizingMaskIntoConstraints = falsev.backgroundColor = .yellowv.font = UIFont.systemFont(ofSize: 17.0)返回 v}()var 段落标记:[UIView] = [UIView]()让颜色:[UIColor] = [.红色的,.绿色,.蓝色,.青色,.橘子,]覆盖 func viewDidLoad() {super.viewDidLoad()view.addSubview(theTextView)NSLayoutConstraint.activate([theTextView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, 常量: 60.0),theTextView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -60.0),theTextView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 80.0),theTextView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, 常量: -20.0),])theTextView.delegate = self//从一些示例文本开始theTextView.text = "这是一行."+"\n\n" +在两个嵌入的换行符之后,此文本将换行."+"\n\n" +这是另一个段落.它应该有足够的文本在此 textView 中换行到多行.当您输入新文本时,段落标记应相应调整."}覆盖 func viewDidAppear(_ 动画:布尔){super.viewDidAppear(动画)//更新 viewDidAppear 上的标记更新段落标记()}func textViewDidChange(_ textView: UITextView) {//编辑文本视图时更新标记更新段落标记()}@objc func updateParagraphMarkers() ->空白 {//清除上一个段落标记视图段落标记.forEach {$0.removeFromSuperview()}//重置 paraMarkers 数组段落标记.removeAll()//可能不需要,但这将确保文本容器已更新theTextView.layoutManager.ensureLayout(for: theTextView.textContainer)//确保我们有一些文本守卫 let str = theTextView.text else { return }//获取完整范围让 textRange = str.startIndex..

结果:

I am creating a dynamic column to the left of a UITextview that matches the height of each paragraph. For some reason I have a problem getting the correct height of the ranges. I am using:

let test = textView.firstRect(for: models.first!.range)

It’s a single line behind as you keep typing. Examples:

2 Lines

3 Lines

Any ideas what’s wrong?

解决方案

This is an example where the docs could use a little help...

From https://developer.apple.com/documentation/uikit/uitextinput/1614570-firstrect:

Return Value

The first rectangle in a range of text. You might use this rectangle to draw a correction rectangle. The "first" in the name refers the rectangle enclosing the first line when the range encompasses multiple lines of text.

Which is not, in fact, exactly right.

For example, if you select the text:

You don't have a rectangle. Using Debug View Hierarchy:

It's clear that you have two rectangles.

So, func firstRect(for range: UITextRange) -> CGRect actually returns the first rectangle from the set of rectangles needed to contain the range.

To get the actual height of the range of text (the paragraph, for example), you'll need to use:

let rects = selectionRects(for: textRange)

and then loop through the returned array of UITextSelectionRect objects.


Edit:

There are various different approaches to accomplish this, but here is a quick simple example of looping through selection rects and summing their heights:

//
//  ParagraphMarkerViewController.swift
//
//  Created by Don Mag on 6/17/19.
//

import UIKit

extension UITextView {

    func boundingFrame(ofTextRange range: Range<String.Index>?) -> CGRect? {

        guard let range = range else { return nil }
        let length = range.upperBound.encodedOffset-range.lowerBound.encodedOffset
        guard
            let start = position(from: beginningOfDocument, offset: range.lowerBound.encodedOffset),
            let end = position(from: start, offset: length),
            let txtRange = textRange(from: start, to: end)
            else { return nil }

        // we now have a UITextRange, so get the selection rects for that range
        let rects = selectionRects(for: txtRange)

        // init our return rect
        var returnRect = CGRect.zero

        // for each selection rectangle
        for thisSelRect in rects {

            // if it's the first one, just set the return rect
            if thisSelRect == rects.first {
                returnRect = thisSelRect.rect
            } else {
                // ignore selection rects with a width of Zero
                if thisSelRect.rect.size.width > 0 {
                    // we only care about the top (the minimum origin.y) and the
                    // sum of the heights
                    returnRect.origin.y = min(returnRect.origin.y, thisSelRect.rect.origin.y)
                    returnRect.size.height += thisSelRect.rect.size.height
                }
            }

        }
        return returnRect
    }

}

class ParagraphMarkerViewController: UIViewController, UITextViewDelegate {

    var theTextView: UITextView = {
        let v = UITextView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.backgroundColor = .yellow
        v.font = UIFont.systemFont(ofSize: 17.0)
        return v
    }()

    var paragraphMarkers: [UIView] = [UIView]()

    let colors: [UIColor] = [
        .red,
        .green,
        .blue,
        .cyan,
        .orange,
    ]

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(theTextView)

        NSLayoutConstraint.activate([

            theTextView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 60.0),
            theTextView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -60.0),
            theTextView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 80.0),
            theTextView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20.0),

            ])

        theTextView.delegate = self

        // start with some example text
        theTextView.text = "This is a single line." +
        "\n\n" +
        "After two embedded newline chars, this text will wrap." +
        "\n\n" +
        "Here is another paragraph. It should be enough text to wrap to multiple lines in this textView. As you enter new text, the paragraph marks should adjust accordingly."

    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        // update markers on viewDidAppear
        updateParagraphMarkers()
    }

    func textViewDidChange(_ textView: UITextView) {
        // update markers when text view is edited
        updateParagraphMarkers()
    }

    @objc func updateParagraphMarkers() -> Void {

        // clear previous paragraph marker views
        paragraphMarkers.forEach {
            $0.removeFromSuperview()
        }

        // reset paraMarkers array
        paragraphMarkers.removeAll()

        // probably not needed, but this will make sure the the text container has updated
        theTextView.layoutManager.ensureLayout(for: theTextView.textContainer)

        // make sure we have some text
        guard let str = theTextView.text else { return }

        // get the full range
        let textRange = str.startIndex..<str.endIndex

        // we want to enumerate by paragraphs
        let opts:NSString.EnumerationOptions = .byParagraphs

        var i = 0

        str.enumerateSubstrings(in: textRange, options: opts) {
            (substring, substringRange, enclosingRange, _) in

            // get the bounding rect for the sub-rects in each paragraph
            if let boundRect = self.theTextView.boundingFrame(ofTextRange: enclosingRange) {

                // create a UIView
                let v = UIView()

                // give it a background color from our array of colors
                v.backgroundColor = self.colors[i % self.colors.count]

                // init the frame
                v.frame = boundRect

                // needs to be offset from the top of the text view
                v.frame.origin.y += self.theTextView.frame.origin.y

                // position it 48-pts to the left of the text view
                v.frame.origin.x = self.theTextView.frame.origin.x - 48

                // give it a width of 40-pts
                v.frame.size.width = 40

                // add it to the view
                self.view.addSubview(v)

                // save a reference to this UIView in our array of markers
                self.paragraphMarkers.append(v)

                i += 1

            }
        }

    }

}

Result:

这篇关于在 UITextView 周围创建 CGRect - 错误的高度的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆