我制作了一个垂直文本视图,用于蒙古语。它是一个自定义文本视图,由三层视图组成:子UITextView
,容器视图(旋转90度并翻转)以保存UITextView
和父视图。 (有关更多背景信息,请参阅here和here。)
视图根据基础文本视图的内容大小增加其大小,只要它在最小和最大大小之间。但是,在过去的几天里,我一直在努力修复一个错误,即添加了一个额外的空间并且内容向左移动(这将在底层文本视图的坐标上)。这可以在下图中观察到。黄色视图是自定义文本视图(在下面的视图控制器代码中称为inputWindow
。)
点击几次以增加内容视图的大小后,会添加一个额外的空格。试图滚动视图什么也没做。 (在宽度达到最大值并且内容大小大于帧大小之后滚动确实有效。)当内容在被放置到正确位置之前被冻结到位时,就好像内容处于滚动的中间位置。如果我插入另一个字符(如空格),则内容视图会将其自身更新到正确的位置。
我需要改变什么?或者,如何手动强制基础UITextView
在正确的位置显示其内容视图?
我试图删除所有无关的代码,只留下View Controller和Custom Vertical 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()
}
测试
观察内容放错地方并且视图不会滚动。
我需要做些什么来解决这个问题?
答案 0 :(得分:1)
是否有可能给我们一个示例项目(在github上)?
您可以稍微更改一下 UIVerticalTextView 文件的代码进行测试:
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()
if self.textView.text.isEmpty == false {
self.textView.scrollRangeToVisible(NSMakeRange(0, 1))
}
}
答案 1 :(得分:0)
我找到了一个可接受的解决方案。它涉及
在示例项目中,这会得到以下结果。
文本视图的内容将其位置更新为正确的位置。
在UIVerticalTextView
类中,我注释掉了自动布局约束行:
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])
请注意,这些是自定义视图中的自动布局约束(用于将textView
框架固定到rotationView
边界),而不是故事板中的自动布局约束。
因此,我在textView.frame = rotationView.bounds
:
layoutSubviews
,而不是自动布局
override func layoutSubviews() {
super.layoutSubviews()
// ...
textView.frame = rotationView.bounds
}
在宽度增加之后,我在调用setNeedsLayout
之前添加了100毫秒的延迟。
private func increaseInputWindowSizeIfNeeded() {
// ...
// width
if inputWindow.contentSize.width > inputWindow.frame.width &&
// ...
} else {
self.inputWindowWidthConstraint.constant = self.inputWindow.contentSize.width
// ********** Added delay here *********
let delay = 0.1
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC)))
dispatch_after(time, dispatch_get_main_queue()) {
self.inputWindow.setNeedsLayout()
}
// *************************************
}
}
设置delay = 0.1
在模拟器中有效,但如果我设置delay = 0.05
则不起作用。因此,如果不在所有设备上进行测试,我无法知道延迟是否足够长。出于这个原因,我认为这个解决方案更像是一个黑客而不是一个真正的解决方案。
无论如何,我无法将赏金奖励给自己,所以如果有人能提出更好的解决方案,我会很高兴听到它。