Как мне анимировать высоту вида вспомогательного ввода?

Я испытываю странное поведение при анимации высоты входного вспомогательного представления. Что я делаю неправильно?

Я создаю 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. Чем первый "прыжок" не будет замечен.

Другие вопросы по тегам