2016-05-14 111 views
11

背景和问题描述

我做了一个垂直文本视图用于蒙古语。这是一个由三层视图组成的自定义文本视图:子视图UITextView,容器视图(旋转90度并翻转)以保存UITextView和父视图。 (有关更多背景信息,请参阅herehere)。UITextView的内容在调整大小后被额外的空间弄错了

只要视图位于最小和最大大小之间,该视图就会根据底层文本视图的内容大小增大大小。但是,在过去的几天里,我一直在努力修复一个错误,在这个错误中添加了额外的空间,并且内容向左移动(这将在底层文本视图的坐标上)。这可以在下图中看到。黄色视图是自定义文本视图(下视图控制器代码调用inputWindow。)

enter image description here

后我点击进入几次,以增加内容视图的大小,额外的空间将被添加。试图滚动视图什么都不做。 (在宽度达到最大值并且内容大小大于框架大小后,滚动功能才起作用)。就像内容处于正确位置之前,当它处于冻结位置时它正处于滚动中间。如果我插入另一个字符(如空格),则内容视图会将其自身更新为正确的位置。

问题

我需要改变什么?或者我如何手动强制底层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和底层文本视图尝试的setNeedsLayoutsetNeedsDisplay和各种组合。

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() 
} 

enter image description here

延迟

我一直在寻找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上获得。故事板布局是这样的:

enter image description 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() 
} 

测试

  1. 点击 “插入文本” 几次。 (请注意,由于实际应用使用镜像字体来补偿翻转的文本视图,所以文字向后。)
  2. 点击“新行”五次。
  3. 尝试滚动视图。

观察到内容放错了位置,并且视图不会滚动。

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

回答

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)) 
    } 
} 
+0

测试项目现在在Github上可用:https://github.com/suragch/TestVerticalTextView – Suragch

+0

我不确定在编辑之后发生了什么。 textview不再冻结。它只是滚动而不调整大小。如果有另一种方法来调整文本视图的大小,你可能会做些什么。 – Suragch

+0

@Suragch布局的小更新UIVerticalTextView的子目录似乎很有用。 – ZYiOS

0

我找到可接受的解决方案。它涉及

  1. 取消和
  2. (自定义的TextView本身内)自动布局更新前添加延迟。

在示例项目中给出了以下结果。

enter image description here

文本视图的内容更新其到正确的位置位置。

无自动布局

UIVerticalTextView I类注释掉自动布局约束线:

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界限),而不是故事板的自动布局限制。

因此,而不是汽车的布局,我在layoutSubviews设置textView.frame = rotationView.bounds

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这是行不通的。因此,如果没有在所有设备上进行测试,我无法知道我的延迟是否足够长。出于这个原因,我认为这个解决方案更像是一种破解而非真正的解决方案。

无论如何,我不能奖励自己,所以如果有人能想出更好的解决方案,我会很高兴听到它。