Хотя визуально просматривать изменения позиции он не делает этого.Если вы проверите его фрейм во время анимации, фрейм всегда будет вашим конечным фреймом.Таким образом, вы можете нажать его там, где будет его конечное положение.(Это также предполагает, что вы включили взаимодействие с пользователем во время анимации, которую вы сделали).
Что вам нужно для вашего случая, так это вручную переместить ваше представление с таймером.Это займет немного больше усилий, но может быть легко выполнимо.Таймер должен срабатывать с некоторыми хорошими FPS, такими как 60, и каждый раз, когда он запускает, кадр должен обновляться до его интерполированной позиции.
Чтобы выполнить интерполяцию, вы можете просто сделать это по компоненту:
func interpolateRect(from: CGRect, to: CGRect, scale: CGFloat) -> CGRect {
return CGRect(x: from.minX + (to.minX - from.minX) * scale, y: from.minY + (to.minY - from.minY) * scale, width: from.width + (to.width - from.width) * scale, height: from.height + (to.height - from.height) * scale)
}
Масштаб обычно должен быть между 0 и 1. В большинстве случаев
Теперь вам нужно иметь метод для анимации с помощью таймера, например:
func animateFrame(to frame: CGRect, animationDuration duration: TimeInterval) {
self.animationStartFrame = tempView.frame // Assign new values
self.animationEndFrame = frame // Assign new values
self.animationStartTime = Date() // Assign new values
self.currentAnimationTimer.invalidate() // Remove current timer if any
self.currentAnimationTimer = Timer.scheduledTimer(withTimeInterval: 1.0/60.0, repeats: true) { timer in
let timeElapsed = Date().timeIntervalSince(self.animationStartTime)
let scale = timeElapsed/duration
self.tempView.frame = self.interpolateRect(from: self.animationStartFrame, to: self.animationEndFrame, scale: CGFloat(max(0.0, min(scale, 1.0))))
if(scale >= 1.0) { // Animation ended. Remove timer
timer.invalidate()
self.currentAnimationTimer = nil
}
}
}
Чтобы быть справедливым, этот код все еще может бытьсокращено, так как мы используем таймер с блоком:
func animateFrame(to frame: CGRect, animationDuration duration: TimeInterval) {
let startFrame = tempView.frame
let endFrame = frame
let animationStartTime = Date()
self.currentAnimationTimer.invalidate() // Remove current timer if any
self.currentAnimationTimer = Timer.scheduledTimer(withTimeInterval: 1.0/60.0, repeats: true) { timer in
let timeElapsed = Date().timeIntervalSince(animationStartTime)
let scale = timeElapsed/duration
self.tempView.frame = self.interpolateRect(from: startFrame, to: endFrame, scale: CGFloat(max(0.0, min(scale, 1.0))))
if(scale >= 1.0) { // Animation ended. Remove timer
timer.invalidate()
if(timer === self.currentAnimationTimer) {
self.currentAnimationTimer = nil // Remove reference only if this is the current timer
}
}
}
}
Таким образом, нам не нужно хранить столько значений в нашем классе, но хранить все внутри метода.
Это может бытьИнтересно упомянуть, как сделать смягчение.Хитрость заключается только в том, чтобы манипулировать нашим scale
.Рассмотрим что-то вроде этого:
func easeInScaleFromLinearScale(_ scale: CGFloat, factor: CGFloat = 0.2) -> CGFloat {
return pow(scale, 1.0+factor)
}
func easeOutScaleFromLinearScale(_ scale: CGFloat, factor: CGFloat = 0.2) -> CGFloat {
return pow(scale, 1.0/(1.0+factor))
}
Теперь все, что нам нужно сделать, это использовать его при интерполяции:
self.tempView.frame = self.interpolateRect(from: startFrame, to: endFrame, scale: easeInScaleFromLinearScale(CGFloat(max(0.0, min(scale, 1.0)))))
Изменение коэффициента изменит эффект.Чем выше фактор, тем выше эффект.А использование коэффициента в нуле означало бы линейную кривую.
С этим вы можете сделать практически любой тип анимации.Единственное правило - ваша функция должна следовать f(0) = 0
и f(1) = 1
.Это означает, что он начинается в начальной позиции и заканчивается в конечной позиции.
Хотя некоторые кривые немного сложнее.EaseInOut
может показаться простым, но это не так.Мы, вероятно, хотели бы реализовать что-то вроде sin
в диапазоне [-PI/2, PI/2]
.Затем сбалансируйте эту функцию с линейной функцией.Это одна из моих реализаций, которую я нашел:
return (sin((inputScale-0.5) * .pi)+1.0)/2.0 * (magnitude) + (inputScale * (1.0-magnitude))
Это поможет вам поиграть с каким-нибудь "онлайн-графическим калькулятором", чтобы найти ваши уравнения и затем преобразовать результаты в ваши функции.