背景和问题描述
我做了一个垂直文本视图用于蒙古语。这是一个由三层视图组成的自定义文本视图:子视图UITextView
,容器视图(旋转90度并翻转)以保存UITextView
和父视图。 (有关更多背景信息,请参阅here和here)。UITextView的内容在调整大小后被额外的空间弄错了
只要视图位于最小和最大大小之间,该视图就会根据底层文本视图的内容大小增大大小。但是,在过去的几天里,我一直在努力修复一个错误,在这个错误中添加了额外的空间,并且内容向左移动(这将在底层文本视图的坐标上)。这可以在下图中看到。黄色视图是自定义文本视图(下视图控制器代码调用inputWindow
。)
后我点击进入几次,以增加内容视图的大小,额外的空间将被添加。试图滚动视图什么都不做。 (在宽度达到最大值并且内容大小大于框架大小后,滚动功能才起作用)。就像内容处于正确位置之前,当它处于冻结位置时它正处于滚动中间。如果我插入另一个字符(如空格),则内容视图会将其自身更新为正确的位置。
问题
我需要改变什么?或者我如何手动强制底层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 shell around 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()
中添加约束条件。没有明显的影响。
allowsNonContiguousLayout
这也没有影响。 (建议here。)
textView.layoutManager.allowsNonContiguousLayout = false
setNeedsLayout
我已经在inputWindow和底层文本视图尝试的setNeedsLayout
setNeedsDisplay
和各种组合。
inputWindow.setNeedsLayout()
inputWindow.underlyingTextView.setNeedsLayout()
甚至在dispatch_async
之内,以便它在下一个运行循环中运行。
dispatch_async(dispatch_get_main_queue()) {
self.inputWindow.setNeedsLayout()
}
sizeToFit
更新宽度约束后做的下一个运行循环sizeToFit
看着第一看好,但它仍然没有解决问题。有时内容会冻结,有时候它会滚动。它并不总是每次都在同一个地方冻结。
self.inputWindowWidthConstraint.constant = self.inputWindow.contentSize.width
dispatch_async(dispatch_get_main_queue()) {
self.inputWindow.underlyingTextView.sizeToFit()
}
延迟
我一直在寻找scheduling a delayed event,但这种感觉就像一个黑客攻击。
重复?
一个类似的冠冕堂皇的问题是UITextview gains an extra line when it should not。但是,它在Objective-C中,所以我不能说得很清楚。它也是6岁,没有答案。
This answer也提到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 here上获得。故事板布局是这样的:
黄色UIView
类是inputWindow
并应设置为UIVerticalTextView
。浅蓝色的视图是topContainerView
。下面的按钮取代键盘。
添加显示的自动布局约束。输入窗口的宽度约束为80,高度约束为150.
将插座和操作连接到下面的View Controller代码。这个视图控制器代码完全取代了我在上面原始示例中使用的视图控制器代码。
视图控制器
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()
}
测试
- 点击 “插入文本” 几次。 (请注意,由于实际应用使用镜像字体来补偿翻转的文本视图,所以文字向后。)
- 点击“新行”五次。
- 尝试滚动视图。
观察到内容放错了位置,并且视图不会滚动。
我需要做些什么来解决这个问题?
测试项目现在在Github上可用:https://github.com/suragch/TestVerticalTextView – Suragch
我不确定在编辑之后发生了什么。 textview不再冻结。它只是滚动而不调整大小。如果有另一种方法来调整文本视图的大小,你可能会做些什么。 – Suragch
@Suragch布局的小更新UIVerticalTextView的子目录似乎很有用。 – ZYiOS