Создание CGRect вокруг UITextView — неправильная высота

Я создаю динамический столбец слева от UITextview, который соответствует высоте каждого абзаца. По какой-то причине у меня проблема с правильной высотой диапазонов. Я использую:

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

Это одна строка позади, пока вы продолжаете печатать. Примеры:

2 линии Создание CGRect вокруг UITextView — неправильная высота

3 линии Создание CGRect вокруг UITextView — неправильная высота

Есть идеи, что не так?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
2 981
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Используя приведенный ниже код, вы получите правильный размер содержимого текстового представления.

let newSize = self.textView.sizeThatFits(CGSize(width: self.textView.frame.width, height: CGFloat.greatestFiniteMagnitude))
        print("\(newSize.height)")

Измените высоту динамического столбца в соответствии с этой высотой. Если вы хотите изменить высоту столбца, пока пользователь печатает, сделайте это в методе UITextViewDelegatetextViewDidChange.

Надеюсь это поможет.

Он просит height of the each paragraph, а не высоту целого textView.

TheTiger 13.06.2019 13:45

@TheTiger Верно! Мне нужно получить высоту каждого абзаца, поэтому нужно использовать firstRect или что-то подобное!

cookie.mink 13.06.2019 13:48
Ответ принят как подходящий

Это пример, когда документы могут немного помочь...

Из 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.

Что, на самом деле, не совсем правильно.

Например, если вы выделяете текст:

У вас нет Прямоугольник. Использование иерархии представлений отладки:

Понятно, что у вас есть прямоугольники два.

Итак, func firstRect(for range: UITextRange) -> CGRect фактически возвращает первый прямоугольник из набор прямоугольников, необходимого для содержания диапазона.

Чтобы получить фактическую высоту диапазона текста (например, абзаца), вам нужно использовать:

let rects = selectionRects(for: textRange)

а затем перебрать возвращенный массив объектов UITextSelectionRect.


Редактировать:

Для этого существуют различные подходы, но вот быстрый простой пример перебора прямоугольников выбора и суммирования их высот:

//
//  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

            }
        }

    }

}

Результат:

Хорошо! Как мне перебрать возвращенный массив и объединить их?

cookie.mink 15.06.2019 04:54

@cookie.mink - есть разные способы выполнить то, что вы пытаетесь сделать ... Посмотрите изменения в моем ответе для простого примера, который должен помочь вам.

DonMag 17.06.2019 15:51

Вау, это потрясающе. 2 коротких вопроса: 1. По какой причине я должен удалить все в массиве, у меня, возможно, может быть 100 абзацев, может ли это быть проблемой или анализ текста происходит довольно быстро? 2. Я собираюсь иметь кнопки в каждом представлении «абзаца». Зная все вышеперечисленное, не могли бы вы дать мне несколько быстрых советов о том, как мне связать абзацы с компонентами бокового вида. Мне нужно, чтобы у них был идентификатор, который ссылается на каждый абзац? Итак, если абзац был удален, представление/кнопка будет знать, что удалить?

cookie.mink 17.06.2019 16:19

@cookie.mink - если у вас, возможно, будет «100 абзацев», у вас есть еще много работы. Скорее всего, в каждый момент времени у вас будет отображаться только несколько абзацев, поэтому синтаксический анализ всего невидимого текста будет очень неэффективным. Вам нужно разработать логику для кэширования невидимого текста... Добавление и манипулирование сотнями подвидов, вероятно, невозможно, поэтому вы можете создать свой собственный элемент управления и нарисовать прямоугольники самостоятельно... Возможно поместите их в просмотр таблицы и синхронизация прокрутки... но все это выходит далеко за рамки вашего первоначального вопроса.

DonMag 17.06.2019 17:17

Другие вопросы по теме