调整大小后,UITextView的内容会被多余的空间放错位置

Sur*_*gch 11 uitextview ios swift mongolian-vertical-script

背景和问题的描述

我制作了一个垂直文本视图,用于蒙古语.它是一个自定义文本视图,由三层视图组成:子UITextView视图,容器视图(旋转90度并翻转)以保存UITextView和父视图.(有关更多背景信息,请参阅此处此处.)

只要介于最小和最大大小之间,视图就会根据基础文本视图的内容大小增加其大小.但是,在过去的几天里,我一直在努力修复一个错误,即添加了一个额外的空间并且内容向左移动(这将在底层文本视图的坐标上).这可以在下图中观察到.黄色视图是自定义文本视图(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
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

自定义垂直文本视图

这个自定义视图基本上是一个围绕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
    }

}
Run Code Online (Sandbox Code Playgroud)

我试过了什么

我尝试过的很多想法来自于如何根据内容调整UITextView的大小?具体来说,我试过:

设置框架而不是自动布局

在自定义视图layoutSubviews()方法中我做了

textView.frame = rotationView.bounds
Run Code Online (Sandbox Code Playgroud)

我没有添加约束setup().没有明显的效果.

allowsNonContiguousLayout

这也没有效果.(建议在这里.)

textView.layoutManager.allowsNonContiguousLayout = false
Run Code Online (Sandbox Code Playgroud)

setNeedsLayout

我已经试过的各种组合setNeedsLayout,并setNeedsDisplay在inputWindow和基础文本视图.

inputWindow.setNeedsLayout()
inputWindow.underlyingTextView.setNeedsLayout()
Run Code Online (Sandbox Code Playgroud)

甚至在一个内部,dispatch_async以便它在下一个运行循环中运行.

dispatch_async(dispatch_get_main_queue()) {
    self.inputWindow.setNeedsLayout()
}
Run Code Online (Sandbox Code Playgroud)

sizeToFit

sizeToFit在更新宽度约束之后执行下一个运行循环首先看起来很有希望,但它仍然没有解决问题.有时内容会冻结,而在其他时候它会滚动.它并不总是每次都在同一个地方冻结.

self.inputWindowWidthConstraint.constant = self.inputWindow.contentSize.width
dispatch_async(dispatch_get_main_queue()) {
    self.inputWindow.underlyingTextView.sizeToFit()
}
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

延迟

我一直在考虑安排延迟事件,但这感觉就像一个黑客.

重复?

一个类似的声音问题是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
Run Code Online (Sandbox Code Playgroud)

没有明显的效果.

更新:基本可重复项目

为了使这个问题尽可能重现,我做了一个独立的项目.它可以在Github上找到.故事板布局如下所示:

在此输入图像描述

黄色UIView类是inputWindow,应该设置为UIVerticalTextView.浅蓝色的视图是topContainerView.下面的按钮取代了键盘.

添加显示的autolayout约束.输入窗口的宽度约束为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
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

UIVerticalTextView

使用与UIVerticalTextView原始示例中相同的代码,但添加以下两种方法.

func insertMongolText(unicode: String) {
    textView.insertText(unicode)
}

func deleteBackward() {
    textView.deleteBackward()
}
Run Code Online (Sandbox Code Playgroud)

测试

  1. 点击"插入文字"几次.(请注意,文本是向后的,因为实际的应用程序使用镜像字体来补偿翻转的文本视图.)
  2. 点击"新行"五次.
  3. 尝试滚动视图.

观察内容放错位置并且视图不会滚动.

我需要做些什么来解决这个问题?

ZYi*_*iOS 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))
    }
}
Run Code Online (Sandbox Code Playgroud)