Это вторая часть статьи про анимации, зависящие от скролла. В предыдущей статье я подробно описал, как создаются такие анимации, и в качестве примера продемонстрировал слайдер с процентом завершения анимации. С полученным процентом можно сделать что угодно - наша фантазия безгранична, но в этой статье я ограничусь тремя примерами, которые могут вдохновить вас при написании кода:
Фейд
Изменение координат
Изменение размера
TLDR: Проект с примерами
Flashback
Возможно, вы не читали предыдущую статью и не хотели бы тратить время на её прочтение. Однако, обязательно прочтите данный абзац, так как здесь будет краткое изложение ключевых моментов предыдущей статьи.
Для начала, чтобы продемонстрировать примеры, нужно вспомнить, как выглядел код для получения процента завершения анимации. Это самая важная часть, и мне хотелось бы её напомнить. Весь код для анимации находится в делегатном методе UIScrollView - scrollViewDidScroll:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let currentContentOffsetY = scrollView.contentOffset.y
let scrollDiff = currentContentOffsetY - previousContentOffsetY
// Верхняя граница начала bounce эффекта
let bounceBorderContentOffsetY = -scrollView.contentInset.top
let contentMovesUp = scrollDiff > 0 && currentContentOffsetY > bounceBorderContentOffsetY
let contentMovesDown = scrollDiff < 0 && currentContentOffsetY < bounceBorderContentOffsetY
let currentConstraintConstant = animatedConstraint!.constant
var newConstraintConstant = currentConstraintConstant
if contentMovesUp {
// Уменьшаем константу констрэйнта
newConstraintConstant = max(currentConstraintConstant - scrollDiff, minConstraintConstant)
} else if contentMovesDown {
// Увеличиваем константу констрэйнта
newConstraintConstant = min(currentConstraintConstant - scrollDiff, maxConstraintConstant)
}
// Меняем высоту и запрещаем скролл, только в случае изменения константы
if newConstraintConstant != currentConstraintConstant {
animatedConstraint?.constant = newConstraintConstant
scrollView.contentOffset.y = previousContentOffsetY
}
// Процент завершения анимации
let animationCompletionPercent = (maxConstraintConstant - currentConstraintConstant) / (maxConstraintConstant - minConstraintConstant)
progressView.progress = Float(animationCompletionPercent)
previousContentOffsetY = scrollView.contentOffset.y
}
Расчёт процента находится в самом конце, он то нам и нужен:
// Процент завершения анимации
let animationCompletionPercent = (maxConstraintConstant - currentConstraintConstant) / (maxConstraintConstant - minConstraintConstant)
Фейд UIView
Здесь всё просто: альфа UIView - это процент завершения анимации:
// Процент завершения анимации
let animationCompletionPercent = (maxConstraintConstant - currentConstraintConstant) / (maxConstraintConstant - minConstraintConstant)
fadeView.alpha = animationCompletionPercent
Изменение координат UIView
Данный пример демонстрирует использование аффинных преобразований, которые позволяют реализовывать самые сложные анимации. Двухмерные аффинные преобразования осуществляются путем умножения вектора на матрицу 3x3, что можно представить в виде системы уравнений, решением которой станут новые координаты для каждой точки. Если вкратце, то с помощью аффинных преобразований UIView можно двигать, вращать и масштабировать как угодно. В этом примере я также воспользуюсь UIViewPropertyAnimator для анимирования UIView.
class CoordinatesChangeView: UIView {
var animationCompletionPercentage: Double = 0 {
didSet { animator.fractionComplete = animationCompletionPercentage }
}
private let side: CGFloat = 60
private lazy var animator = UIViewPropertyAnimator(duration: 0.1, curve: .easeIn) { [weak self] in
guard let self else { return }
self.imageView.transform = CGAffineTransform(translationX: self.side - self.bounds.width, y: 0)
}
}
Так настраивается анимация внутри CoordinatesChangeView. В самом UIViewController всё так же, как и в прошлом примере:
// Процент завершения анимации
let animationCompletionPercent = (maxConstraintConstant - currentConstraintConstant) / (maxConstraintConstant - minConstraintConstant)
coordinateChangeView.animationCompletionPercentage = animationCompletionPercent
Изменение размера UIView
В этом примере так же можно было использовать аффинные преобразования и анимировать изменения через UIViewPropertyAnimator, но я решил немного разнообразить пример и пользоваться не процентом, а самим констрейнтом:
private let minConstraintConstant: CGFloat = 50
private let maxConstraintConstant: CGFloat = 200
private var animatedConstraint: NSLayoutConstraint?
private var avatarHeightConstraint: NSLayoutConstraint?
private func setupAvatarView() {
view.addSubview(avatarImageView)
avatarHeightConstraint = avatarImageView.heightAnchor.constraint(equalToConstant: maxConstraintConstant)
NSLayoutConstraint.activate([
avatarHeightConstraint!,
avatarImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
avatarImageView.heightAnchor.constraint(equalTo: avatarImageView.widthAnchor, multiplier: 1.0),
avatarImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
])
}
В коде выше я сохраняю ещё одну константу и буду изменять её в методе scrollViewDidScroll:
// Меняем высоту и запрещаем скролл, только в случае изменения константы
if newConstraintConstant != currentConstraintConstant {
animatedConstraint?.constant = newConstraintConstant
scrollView.contentOffset.y = previousContentOffsetY
}
// Меняем высоту аватара, только в случае изменения константы
if newConstraintConstant != avatarHeightConstraint!.constant {
avatarHeightConstraint?.constant = currentConstraintConstant
}
Изменяю высоту так же, как и с коснтрейнтом таблицы - только в случае изменения значения. Мне не нужно вычислять процент, так как я меняю константу напрямую.
Заключение
Данная статья получилась небольшой, но, надеюсь, от этого не менее полезной. Я постарался реализовать все примеры разными способами, чтобы у вас было больше пищи для размышлений. Я надеюсь, что вы найдете здесь множество идей, которые помогут вам при написании кода. Кроме того, я бы хотел подчеркнуть, что важно не только владеть разными способами, но и уметь выбрать тот, который подходит именно для вашей задачи.
LiloFr
супер