Я создаю динамический столбец слева от UITextview, который соответствует высоте каждого абзаца. По какой-то причине у меня проблема с правильной высотой диапазонов. Я использую:
let test = textView.firstRect(for: models.first!.range)
Это одна строка позади, пока вы продолжаете печатать. Примеры:
Есть идеи, что не так?
Используя приведенный ниже код, вы получите правильный размер содержимого текстового представления.
let newSize = self.textView.sizeThatFits(CGSize(width: self.textView.frame.width, height: CGFloat.greatestFiniteMagnitude))
print("\(newSize.height)")
Измените высоту динамического столбца в соответствии с этой высотой. Если вы хотите изменить высоту столбца, пока пользователь печатает, сделайте это в методе UITextViewDelegate
textViewDidChange
.
Надеюсь это поможет.
@TheTiger Верно! Мне нужно получить высоту каждого абзаца, поэтому нужно использовать firstRect
или что-то подобное!
Это пример, когда документы могут немного помочь...
Из 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 - есть разные способы выполнить то, что вы пытаетесь сделать ... Посмотрите изменения в моем ответе для простого примера, который должен помочь вам.
Вау, это потрясающе. 2 коротких вопроса: 1. По какой причине я должен удалить все в массиве, у меня, возможно, может быть 100 абзацев, может ли это быть проблемой или анализ текста происходит довольно быстро? 2. Я собираюсь иметь кнопки в каждом представлении «абзаца». Зная все вышеперечисленное, не могли бы вы дать мне несколько быстрых советов о том, как мне связать абзацы с компонентами бокового вида. Мне нужно, чтобы у них был идентификатор, который ссылается на каждый абзац? Итак, если абзац был удален, представление/кнопка будет знать, что удалить?
@cookie.mink - если у вас, возможно, будет «100 абзацев», у вас есть еще много работы. Скорее всего, в каждый момент времени у вас будет отображаться только несколько абзацев, поэтому синтаксический анализ всего невидимого текста будет очень неэффективным. Вам нужно разработать логику для кэширования невидимого текста... Добавление и манипулирование сотнями подвидов, вероятно, невозможно, поэтому вы можете создать свой собственный элемент управления и нарисовать прямоугольники самостоятельно... Возможно поместите их в просмотр таблицы и синхронизация прокрутки... но все это выходит далеко за рамки вашего первоначального вопроса.
Он просит
height of the each paragraph
, а не высоту целогоtextView
.