Как мне анимировать высоту вида вспомогательного ввода?
Я испытываю странное поведение при анимации высоты входного вспомогательного представления. Что я делаю неправильно?
Я создаю UIInputView
подкласс (InputView
) с одним подпредставлением. Высота InputView
И его intrinsicContentSize
контролируются подпредставлением. InputView
50 пикселей в высоту, когда isVisible
является true
и 0 пикселей в высоту, когда isVisible
ложно
import UIKit
class InputView: UIInputView {
private let someHeight: CGFloat = 50.0, zeroHeight: CGFloat = 0.0
private let subView = UIView()
private var hide: NSLayoutConstraint?, show: NSLayoutConstraint?
var isVisible: Bool {
get {
return show!.isActive
}
set {
// Always deactivate constraints before activating conflicting ones
if newValue == true {
hide?.isActive = false
show?.isActive = true
} else {
show?.isActive = false
hide?.isActive = true
}
}
}
// MARK: Sizing
override func sizeThatFits(_ size: CGSize) -> CGSize {
return CGSize(width: size.width, height: someHeight)
}
override var intrinsicContentSize: CGSize {
return CGSize.init(width: bounds.size.width, height: subView.bounds.size.height)
}
// MARK: Initializers
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect, inputViewStyle: UIInputViewStyle) {
super.init(frame: frame, inputViewStyle: inputViewStyle)
addSubview(subView)
subView.backgroundColor = UIColor.purple
translatesAutoresizingMaskIntoConstraints = false
subView.translatesAutoresizingMaskIntoConstraints = false
subView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true
subView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
subView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
subView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor).isActive = true
show = subView.heightAnchor.constraint(equalToConstant: someHeight)
hide = subView.heightAnchor.constraint(equalToConstant: zeroHeight)
hide?.isActive = true
}
}
Контроллер хост-вида переключает isVisible
в односекундном анимационном блоке при нажатии кнопки.
import UIKit
class MainViewController: UIViewController {
let testInputView = InputView.init(frame: .zero, inputViewStyle: .default)
@IBAction func button(_ sender: AnyObject) {
UIView.animate(withDuration: 1.0) {
let isVisible = self.testInputView.isVisible
self.testInputView.isVisible = !isVisible
self.testInputView.layoutIfNeeded()
}
}
override var canBecomeFirstResponder: Bool {
return true
}
override var inputAccessoryView: UIView? {
return testInputView
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
Я ожидаю, что вспомогательный вид ввода будет плавно расти от нижней части экрана, когда isVisible
установлен в true
и плавно сжиматься до кнопки на экране, когда isVisible
установлен в false
, Вместо этого наложение фона клавиатуры появляется на полную высоту 50 пикселей, как только isVisible
является true
и входное вспомогательное представление растет от центра его структуры.
При сжатии вспомогательный вид ввода мгновенно теряет часть высоты, прежде чем плавно продолжить анимацию.
Я создал демонстрационный проект представления вспомогательного ввода, который отображает это неожиданное поведение.
1 ответ
Это даст вам правильную анимацию:
UIView.animate(withDuration: 1.0) {
let isVisible = self.testInputView.isVisible
self.testInputView.isVisible = !isVisible
self.testInputView.superview?.superview?.layoutIfNeeded()
}
Тем не менее, никогда не рекомендуется называть суперпредставление, если Apple меняет дизайн. Так что может быть лучший ответ.
Вот что представляют суперпредставления:
print(testInputView.superview) // UIInputSetHostView
print(testInputView.superview?.superview) // UIInputSetContainerView
РЕДАКТИРОВАТЬ: ДОБАВЛЕНО БЕЗОПАСНОЕ РЕШЕНИЕ
Я не слишком знаком с UIInputView. Но один из способов решения этого вопроса без вызова суперпредставления состоит в том, чтобы анимировать только изменение высоты подпредставления:
Шаг 1:
Переместите isVisible за пределы блока анимации.
@IBAction func button(_ sender: AnyObject) {
let isVisible = self.testInputView.isVisible
self.testInputView.isVisible = !isVisible
UIView.animate(withDuration: 1.0) {
self.testInputView.layoutIfNeeded()
}
}
Шаг 2: Создайте новый метод в вашем InputView, который изменяет ограничение высоты InputView вместо intrinsicContentSize.
private func updateHeightConstraint(height: CGFloat) {
for constraint in constraints {
if constraint.firstAttribute == .height {
constraint.constant = height
}
}
self.layoutIfNeeded()
}
Шаг 3: И вызвать этот метод внутри сеттера.
if newValue == true {
updateHeightConstraint(height: someHeight)
hide?.isActive = false
show?.isActive = true
} else {
updateHeightConstraint(height: zeroHeight)
show?.isActive = false
hide?.isActive = true
}
Шаг 4: Наконец, некоторые изменения в init.
override init(frame: CGRect, inputViewStyle: UIInputViewStyle) {
super.init(frame: frame, inputViewStyle: inputViewStyle)
addSubview(subView)
backgroundColor = .clear
subView.backgroundColor = UIColor.purple
subView.translatesAutoresizingMaskIntoConstraints = false
subView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
subView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
subView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor).isActive = true
show = subView.heightAnchor.constraint(equalToConstant: someHeight)
hide = subView.heightAnchor.constraint(equalToConstant: zeroHeight)
hide?.isActive = true
}
Вывод: этот результат в InputView меняет свою высоту до анимации высоты фиолетового подпредставления. Единственным недостатком является UIInputView, который по умолчанию имеет своего рода серый фон и не может быть изменен на Очистить. Однако вы можете использовать тот же backgroundColor, что и VC.
Но если вместо этого вам следует использовать обычный UIView в качестве InputAccessoryView, то по умолчанию будет UIColor.clear. Чем первый "прыжок" не будет замечен.