问题的背景和描述
我制作了一个垂直文本视图以用于蒙古语。它是一个自定义文本视图,由三层视图组成:子UITextView
视图、用于容纳 的容器视图(旋转 90 度并翻转)UITextView
和父视图。(有关更多背景信息,请参见此处和此处。)
视图根据底层文本视图的内容大小增加其大小,只要它在最小和最大大小之间。然而,在过去的几天里,我一直在努力修复一个错误,即添加了额外的空间并且内容向左移动(这将在底层文本视图的坐标上)。这可以在下图中观察到。黄色视图是自定义文本视图(inputWindow
在下面的视图控制器代码中调用。)
在我点击 enter 几次以增加内容视图的大小后,添加了一个额外的空间。试图滚动视图什么也没做。(在宽度达到最大值并且内容大小大于框架大小后,滚动确实有效。)就好像内容处于滚动中间时它被冻结在适当的位置之前它可以放置在正确的位置。如果我插入另一个字符(如空格),则内容视图会自行更新到正确的位置。
问题
我需要改变什么?或者我如何手动强制底层证券UITextView
在正确的位置显示其内容视图?
代码
我试图删除所有无关的代码,只保留视图控制器和自定义垂直 TextView 的相关部分。如果还有什么我应该包括的,请告诉我。
视图控制器
当内容视图大小发生变化时,视图控制器会更新自定义文本视图的大小约束。
import UIKit
class TempViewController: UIViewController, KeyboardDelegate {
let minimumInputWindowSize = CGSize(width: 80, height: 150)
let inputWindowSizeIncrement: CGFloat = 50
// MARK:- Outlets
@IBOutlet weak var inputWindow: UIVerticalTextView!
@IBOutlet weak var topContainerView: UIView!
@IBOutlet weak var keyboardContainer: KeyboardController!
@IBOutlet weak var inputWindowHeightConstraint: NSLayoutConstraint!
@IBOutlet weak var inputWindowWidthConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// get rid of space at beginning of textview
self.automaticallyAdjustsScrollViewInsets = false
// setup keyboard
keyboardContainer.delegate = self
inputWindow.underlyingTextView.inputView = UIView()
inputWindow.underlyingTextView.becomeFirstResponder()
}
// KeyboardDelegate protocol
func keyWasTapped(character: String) {
inputWindow.insertMongolText(character) // code omitted for brevity
increaseInputWindowSizeIfNeeded()
}
func keyBackspace() {
inputWindow.deleteBackward() // code omitted for brevity
decreaseInputWindowSizeIfNeeded()
}
private func increaseInputWindowSizeIfNeeded() {
if inputWindow.frame.size == topContainerView.frame.size {
return
}
// width
if inputWindow.contentSize.width > inputWindow.frame.width &&
inputWindow.frame.width < topContainerView.frame.size.width {
if inputWindow.contentSize.width > topContainerView.frame.size.width {
//inputWindow.scrollEnabled = true
inputWindowWidthConstraint.constant = topContainerView.frame.size.width
} else {
self.inputWindowWidthConstraint.constant = self.inputWindow.contentSize.width
}
}
// height
if inputWindow.contentSize.width > inputWindow.contentSize.height {
if inputWindow.frame.height < topContainerView.frame.height {
if inputWindow.frame.height + inputWindowSizeIncrement < topContainerView.frame.height {
// increase height by increment unit
inputWindowHeightConstraint.constant = inputWindow.frame.height + inputWindowSizeIncrement
} else {
inputWindowHeightConstraint.constant = topContainerView.frame.height
}
}
}
}
private func decreaseInputWindowSizeIfNeeded() {
if inputWindow.frame.size == minimumInputWindowSize {
return
}
// width
if inputWindow.contentSize.width < inputWindow.frame.width &&
inputWindow.frame.width > minimumInputWindowSize.width {
if inputWindow.contentSize.width < minimumInputWindowSize.width {
inputWindowWidthConstraint.constant = minimumInputWindowSize.width
} else {
inputWindowWidthConstraint.constant = inputWindow.contentSize.width
}
}
// height
if (2 * inputWindow.contentSize.width) <= inputWindow.contentSize.height && inputWindow.contentSize.width < topContainerView.frame.width {
// got too high, make it shorter
if minimumInputWindowSize.height < inputWindow.contentSize.height - inputWindowSizeIncrement {
inputWindowHeightConstraint.constant = inputWindow.contentSize.height - inputWindowSizeIncrement
} else {
// Bump down to min height
inputWindowHeightConstraint.constant = minimumInputWindowSize.height
}
}
}
}
自定义垂直文本视图
这个自定义视图基本上是一个围绕 a 的外壳UITextView
,允许旋转和翻转以正确查看传统的蒙古语。
import UIKit
@IBDesignable class UIVerticalTextView: UIView {
var textView = UITextView()
let rotationView = UIView()
var underlyingTextView: UITextView {
get {
return textView
}
set {
textView = newValue
}
}
var contentSize: CGSize {
get {
// height and width are swapped because underlying view is rotated 90 degrees
return CGSize(width: textView.contentSize.height, height: textView.contentSize.width)
}
set {
textView.contentSize = CGSize(width: newValue.height, height: newValue.width)
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect){
super.init(frame: frame)
self.setup()
}
override func awakeFromNib() {
super.awakeFromNib()
self.setup()
}
func setup() {
textView.backgroundColor = UIColor.yellowColor()
self.textView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(rotationView)
rotationView.addSubview(textView)
// add constraints to pin TextView to rotation view edges.
let leadingConstraint = NSLayoutConstraint(item: self.textView, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: rotationView, attribute: NSLayoutAttribute.Leading, multiplier: 1.0, constant: 0)
let trailingConstraint = NSLayoutConstraint(item: self.textView, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: rotationView, attribute: NSLayoutAttribute.Trailing, multiplier: 1.0, constant: 0)
let topConstraint = NSLayoutConstraint(item: self.textView, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: rotationView, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: self.textView, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: rotationView, attribute: NSLayoutAttribute.Bottom, multiplier: 1.0, constant: 0)
rotationView.addConstraints([leadingConstraint, trailingConstraint, topConstraint, bottomConstraint])
}
override func layoutSubviews() {
super.layoutSubviews()
rotationView.transform = CGAffineTransformIdentity
rotationView.frame = CGRect(origin: CGPointZero, size: CGSize(width: self.bounds.height, height: self.bounds.width))
rotationView.userInteractionEnabled = true
rotationView.transform = translateRotateFlip()
}
func translateRotateFlip() -> CGAffineTransform {
var transform = CGAffineTransformIdentity
// translate to new center
transform = CGAffineTransformTranslate(transform, (self.bounds.width / 2)-(self.bounds.height / 2), (self.bounds.height / 2)-(self.bounds.width / 2))
// rotate counterclockwise around center
transform = CGAffineTransformRotate(transform, CGFloat(-M_PI_2))
// flip vertically
transform = CGAffineTransformScale(transform, -1, 1)
return transform
}
}
我试过的
我尝试过的许多想法都来自How do I size a UITextView to its content? 具体来说,我尝试过:
设置框架而不是自动布局
在layoutSubviews()
我做的自定义视图方法中
textView.frame = rotationView.bounds
而且我没有在setup()
. 没有明显的效果。
允许非连续布局
这也没有效果。(建议在这里。)
textView.layoutManager.allowsNonContiguousLayout = false
设置需求布局
setNeedsLayout
我已经尝试了 inputWindow和setNeedsDisplay
底层文本视图的各种组合。
inputWindow.setNeedsLayout()
inputWindow.underlyingTextView.setNeedsLayout()
甚至在 a 内部,dispatch_async
以便它在下一个运行循环中运行。
dispatch_async(dispatch_get_main_queue()) {
self.inputWindow.setNeedsLayout()
}
尺寸适合
在更新宽度约束后执行sizeToFit
下一个运行循环起初看起来很有希望,但它仍然没有解决问题。有时内容会冻结,有时会滚动。它并不总是每次都冻结在同一个地方。
self.inputWindowWidthConstraint.constant = self.inputWindow.contentSize.width
dispatch_async(dispatch_get_main_queue()) {
self.inputWindow.underlyingTextView.sizeToFit()
}
延迟
我一直在考虑安排一个延迟的事件,但这感觉就像一个黑客。
重复?
一个类似的问题是UITextview 在不应该的时候获得了额外的一行。但是,它在 Objective-C 中,所以我不能说得很好。它也是6岁没有答案。
这个答案还提到了 iPhone 6+ 上的额外空间(我上面的测试图像是 iPhone 6,而不是 6+)。但是,我想我尝试了该答案中的建议。也就是说,我做到了
var _f = self.inputWindow.underlyingTextView.frame
_f.size.height = self.inputWindow.underlyingTextView.contentSize.height
self.inputWindow.underlyingTextView.frame = _f
没有明显的效果。
更新:一个基本的可重现项目
为了使这个问题尽可能重现,我做了一个独立的项目。它可以在 Github 上找到。故事板布局如下所示:
黄色UIView
类是inputWindow
并且应该设置为UIVerticalTextView
。浅蓝色视图是topContainerView
. 下面的按钮取代了键盘。
添加显示的自动布局约束。输入窗口的宽度约束为 80,高度约束为 150。
将出口和操作连接到下面的视图控制器代码。此视图控制器代码完全替换了我在上面的原始示例中使用的视图控制器代码。
视图控制器
import UIKit
class ViewController: UIViewController {
let minimumInputWindowSize = CGSize(width: 80, height: 150)
let inputWindowSizeIncrement: CGFloat = 50
// MARK:- Outlets
@IBOutlet weak var inputWindow: UIVerticalTextView!
@IBOutlet weak var topContainerView: UIView!
//@IBOutlet weak var keyboardContainer: KeyboardController!
@IBOutlet weak var inputWindowHeightConstraint: NSLayoutConstraint!
@IBOutlet weak var inputWindowWidthConstraint: NSLayoutConstraint!
@IBAction func enterTextButtonTapped(sender: UIButton) {
inputWindow.insertMongolText("a")
increaseInputWindowSizeIfNeeded()
}
@IBAction func newLineButtonTapped(sender: UIButton) {
inputWindow.insertMongolText("\n")
increaseInputWindowSizeIfNeeded()
}
@IBAction func deleteBackwardsButtonTapped(sender: UIButton) {
inputWindow.deleteBackward()
decreaseInputWindowSizeIfNeeded()
}
override func viewDidLoad() {
super.viewDidLoad()
// get rid of space at beginning of textview
self.automaticallyAdjustsScrollViewInsets = false
// hide system keyboard but show cursor
inputWindow.underlyingTextView.inputView = UIView()
inputWindow.underlyingTextView.becomeFirstResponder()
}
private func increaseInputWindowSizeIfNeeded() {
if inputWindow.frame.size == topContainerView.frame.size {
return
}
// width
if inputWindow.contentSize.width > inputWindow.frame.width &&
inputWindow.frame.width < topContainerView.frame.size.width {
if inputWindow.contentSize.width > topContainerView.frame.size.width {
//inputWindow.scrollEnabled = true
inputWindowWidthConstraint.constant = topContainerView.frame.size.width
} else {
self.inputWindowWidthConstraint.constant = self.inputWindow.contentSize.width
}
}
// height
if inputWindow.contentSize.width > inputWindow.contentSize.height {
if inputWindow.frame.height < topContainerView.frame.height {
if inputWindow.frame.height + inputWindowSizeIncrement < topContainerView.frame.height {
// increase height by increment unit
inputWindowHeightConstraint.constant = inputWindow.frame.height + inputWindowSizeIncrement
} else {
inputWindowHeightConstraint.constant = topContainerView.frame.height
}
}
}
}
private func decreaseInputWindowSizeIfNeeded() {
if inputWindow.frame.size == minimumInputWindowSize {
return
}
// width
if inputWindow.contentSize.width < inputWindow.frame.width &&
inputWindow.frame.width > minimumInputWindowSize.width {
if inputWindow.contentSize.width < minimumInputWindowSize.width {
inputWindowWidthConstraint.constant = minimumInputWindowSize.width
} else {
inputWindowWidthConstraint.constant = inputWindow.contentSize.width
}
}
// height
if (2 * inputWindow.contentSize.width) <= inputWindow.contentSize.height && inputWindow.contentSize.width < topContainerView.frame.width {
// got too high, make it shorter
if minimumInputWindowSize.height < inputWindow.contentSize.height - inputWindowSizeIncrement {
inputWindowHeightConstraint.constant = inputWindow.contentSize.height - inputWindowSizeIncrement
} else {
// Bump down to min height
inputWindowHeightConstraint.constant = minimumInputWindowSize.height
}
}
}
}
UIVerticalTextView
使用与原始示例中相同的代码,UIVerticalTextView
但添加了以下两种方法。
func insertMongolText(unicode: String) {
textView.insertText(unicode)
}
func deleteBackward() {
textView.deleteBackward()
}
测试
- 点击“插入文本”几次。(请注意,文本是向后的,因为实际的应用程序使用镜像字体来补偿翻转的文本视图。)
- 点击“新行”五次。
- 尝试滚动视图。
观察内容放错位置并且视图不会滚动。
我需要做什么来解决这个问题?