Фон и описание проблемы
Я сделал вертикальный текст, который будет использоваться с монгольским. Это пользовательский текстовый вид, состоящий из трех уровней просмотров: дочерний UITextView
, вид контейнера (который повернут на 90 градусов и перевернулся), чтобы удерживать 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
}
}
}
}
Пользовательский вертикальный текстовый вид
Этот пользовательский вид в основном оболочка вокруг 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
}
}
Что я пробовал
Многие из идей, которые я пробовал, пришли из Как я могу настроить UITextView на его содержимое? В частности, я пробовал:
Установка кадра вместо автоматического макета
В режиме пользовательского вида layoutSubviews()
я сделал
textView.frame = rotationView.bounds
и я не добавил ограничений в setup()
. Не было заметного эффекта.
allowsNonContiguousLayout
Это также не имело никакого эффекта. (Предлагается здесь.)
textView.layoutManager.allowsNonContiguousLayout = false
setNeedsLayout
Я пробовал различные комбинации setNeedsLayout
и setNeedsDisplay
в inputWindow и в основном текстовом представлении.
inputWindow.setNeedsLayout()
inputWindow.underlyingTextView.setNeedsLayout()
даже внутри a 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()
}
Задержка
Я смотрел планирование замедленного события, но это похоже на взлома.
A Дубликат?
Аналогичный звуковой вопрос - 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.
Подключите выходы и действия к коду 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()
}
Test
- Несколько раз нажмите "вставить текст". (Обратите внимание, что текст обратный, потому что фактическое приложение использует зеркальный шрифт для компенсации перевернутого текстового вида.)
- Нажмите "новая строка" пять раз.
- Попробуйте прокрутить представление.
Обратите внимание, что содержимое неуместно и что просмотр не будет прокручиваться.
Что мне нужно сделать, чтобы исправить эту проблему?