围绕 UITextView 创建 CGRect - 高度错误
Creating a CGRect around a UITextView - Wrong Height
我正在 UITextview 的左侧创建一个与每个段落的高度相匹配的动态列。出于某种原因,我在获取范围的正确高度时遇到了问题。我正在使用:
let test = textView.firstRect(for: models.first!.range)
当你继续打字时,后面只有一行。示例:
2 行
3 行
有什么想法吗?
使用下面的代码,您将获得文本视图的正确内容大小。
let newSize = self.textView.sizeThatFits(CGSize(width: self.textView.frame.width, height: CGFloat.greatestFiniteMagnitude))
print("\(newSize.height)")
根据这个高度改变动态栏的高度。如果您想在用户键入时更改列高,请在 UITextViewDelegate
方法 textViewDidChange
.
中执行此操作
希望对您有所帮助。
这是一个文档可以提供一点帮助的示例...
来自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.
事实上,这并不完全正确。
例如,如果您 select 文本:
您没有矩形。使用调试视图层次结构:
很明显你有两个个矩形。
所以,func firstRect(for range: UITextRange) -> CGRect
实际上 returns 第一个矩形 来自 一组矩形 需要包含范围。
要获取文本范围(例如段落)的实际高度,您需要使用:
let rects = selectionRects(for: textRange)
然后遍历返回的 UITextSelectionRect
个对象数组。
编辑:
有多种不同的方法可以实现这一点,但这里有一个循环遍历 selection 矩形并求和它们的高度的快速简单示例:
//
// 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 {
[=11=].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
}
}
}
}
结果:
我正在 UITextview 的左侧创建一个与每个段落的高度相匹配的动态列。出于某种原因,我在获取范围的正确高度时遇到了问题。我正在使用:
let test = textView.firstRect(for: models.first!.range)
当你继续打字时,后面只有一行。示例:
2 行
3 行
有什么想法吗?
使用下面的代码,您将获得文本视图的正确内容大小。
let newSize = self.textView.sizeThatFits(CGSize(width: self.textView.frame.width, height: CGFloat.greatestFiniteMagnitude))
print("\(newSize.height)")
根据这个高度改变动态栏的高度。如果您想在用户键入时更改列高,请在 UITextViewDelegate
方法 textViewDidChange
.
希望对您有所帮助。
这是一个文档可以提供一点帮助的示例...
来自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.
事实上,这并不完全正确。
例如,如果您 select 文本:
您没有矩形。使用调试视图层次结构:
很明显你有两个个矩形。
所以,func firstRect(for range: UITextRange) -> CGRect
实际上 returns 第一个矩形 来自 一组矩形 需要包含范围。
要获取文本范围(例如段落)的实际高度,您需要使用:
let rects = selectionRects(for: textRange)
然后遍历返回的 UITextSelectionRect
个对象数组。
编辑:
有多种不同的方法可以实现这一点,但这里有一个循环遍历 selection 矩形并求和它们的高度的快速简单示例:
//
// 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 {
[=11=].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
}
}
}
}
结果: