Предыстория и описание проблемы
Я сделал вертикальный вид текста для использования с монгольским языком. Это пользовательское текстовое представление, состоящее из трех слоев представлений: дочернего UITextView
, контейнерного представления (которое повернуто на 90 градусов и перевернуто) для хранения UITextView
и родительского представления. (См. здесь и здесь для получения дополнительной справочной информации.)
Представление увеличивает свой размер в соответствии с размером содержимого базового текстового представления, если оно находится между минимальным и максимальным размером. Однако в течение последних нескольких дней я изо всех сил пытался исправить ошибку, из-за которой добавлялся дополнительный пробел, а содержимое смещалось влево (это было бы вверх по координатам базового текстового представления). Это можно наблюдать на следующем изображении. Желтое представление — это пользовательское текстовое представление (называемое inputWindow
в приведенном ниже коде контроллера представления).
После того, как я несколько раз нажму Enter, чтобы увеличить размер представления контента, будет добавлено дополнительное пространство. Попытка прокрутить представление ничего не дает. (Прокрутка работает после того, как ширина достигает своего максимума, а размер содержимого больше, чем размер кадра.) Это как если бы содержимое было в середине прокрутки, когда оно застыло на месте, прежде чем его можно было бы поместить в правильное положение. Если я вставлю другой символ (например, пробел), то представление содержимого обновится до правильной позиции.
Вопрос
Что мне нужно изменить? Или как вручную заставить базовый UITextView
отображать свое содержимое в правильном месте?
Код
Я попытался вырезать весь посторонний код и просто оставить соответствующие части как для контроллера представления, так и для пользовательского вертикального текстового представления. Если есть что-то еще, что я должен включить, дайте мне знать.
Контроллер просмотра
Контроллер представления обновляет ограничения размера пользовательского текстового представления при изменении размера представления содержимого.
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()
. Заметного эффекта не было.
позволяетNonContiguousLayout
Это тоже не повлияло. (Предлагается здесь.)
textView.layoutManager.allowsNonContiguousLayout = false
setNeedsLayout
Я пробовал различные комбинации setNeedsLayout
и setNeedsDisplay
в inputWindow и базовом текстовом представлении.
inputWindow.setNeedsLayout()
inputWindow.underlyingTextView.setNeedsLayout()
даже внутри dispatch_async
, чтобы он запускался в следующем цикле выполнения.
dispatch_async(dispatch_get_main_queue()) {
self.inputWindow.setNeedsLayout()
}
Размер по размеру
Выполнение sizeToFit
в следующем цикле выполнения после обновления ограничения ширины поначалу выглядело многообещающе, но это все еще не решило проблему. Иногда содержимое зависало, а иногда его можно было прокручивать. Он не всегда замерзает в одном и том же месте каждый раз.
self.inputWindowWidthConstraint.constant = self.inputWindow.contentSize.width
dispatch_async(dispatch_get_main_queue()) {
self.inputWindow.underlyingTextView.sizeToFit()
}
Задержка
Я искал планирование отложенного события, но это похоже на взлом.
Дубликат?
Аналогично звучит вопрос: 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()
}
Тест
- Нажмите «вставить текст» несколько раз. (Обратите внимание, что текст перевернут, потому что в реальном приложении используется зеркальный шрифт, чтобы компенсировать перевернутое представление текста.)
- Нажмите «новая строка» пять раз.
- Попробуйте прокрутить вид.
Обратите внимание, что содержимое неуместно и что представление не будет прокручиваться.
Что мне нужно сделать, чтобы решить эту проблему?