mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
Animation fps improvements
This commit is contained in:
parent
7869790318
commit
812ad9b030
@ -10,6 +10,7 @@ swift_library(
|
|||||||
"-warnings-as-errors",
|
"-warnings-as-errors",
|
||||||
],
|
],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//submodules/Display"
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Display
|
||||||
|
|
||||||
#if targetEnvironment(simulator)
|
#if targetEnvironment(simulator)
|
||||||
@_silgen_name("UIAnimationDragCoefficient") func UIAnimationDragCoefficient() -> Float
|
@_silgen_name("UIAnimationDragCoefficient") func UIAnimationDragCoefficient() -> Float
|
||||||
@ -15,105 +16,31 @@ private extension UIView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private class CALayerAnimationDelegate: NSObject, CAAnimationDelegate {
|
|
||||||
private let keyPath: String?
|
|
||||||
var completion: ((Bool) -> Void)?
|
|
||||||
|
|
||||||
init(animation: CAAnimation, completion: ((Bool) -> Void)?) {
|
|
||||||
if let animation = animation as? CABasicAnimation {
|
|
||||||
self.keyPath = animation.keyPath
|
|
||||||
} else {
|
|
||||||
self.keyPath = nil
|
|
||||||
}
|
|
||||||
self.completion = completion
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
|
|
||||||
if let anim = anim as? CABasicAnimation {
|
|
||||||
if anim.keyPath != self.keyPath {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let completion = self.completion {
|
|
||||||
completion(flag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func makeSpringAnimation(keyPath: String) -> CASpringAnimation {
|
|
||||||
let springAnimation = CASpringAnimation(keyPath: keyPath)
|
|
||||||
springAnimation.mass = 3.0;
|
|
||||||
springAnimation.stiffness = 1000.0
|
|
||||||
springAnimation.damping = 500.0
|
|
||||||
springAnimation.duration = 0.5
|
|
||||||
springAnimation.timingFunction = CAMediaTimingFunction(name: .linear)
|
|
||||||
return springAnimation
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension CALayer {
|
private extension CALayer {
|
||||||
func makeAnimation(from: AnyObject?, to: AnyObject, keyPath: String, duration: Double, delay: Double, curve: Transition.Animation.Curve, removeOnCompletion: Bool, additive: Bool, completion: ((Bool) -> Void)? = nil) -> CAAnimation {
|
func animate(from: AnyObject, to: AnyObject, keyPath: String, duration: Double, delay: Double, curve: Transition.Animation.Curve, removeOnCompletion: Bool, additive: Bool, completion: ((Bool) -> Void)? = nil) {
|
||||||
|
let timingFunction: String
|
||||||
|
let mediaTimingFunction: CAMediaTimingFunction?
|
||||||
switch curve {
|
switch curve {
|
||||||
case .spring:
|
case .spring:
|
||||||
let animation = makeSpringAnimation(keyPath: keyPath)
|
timingFunction = kCAMediaTimingFunctionSpring
|
||||||
animation.fromValue = from
|
mediaTimingFunction = nil
|
||||||
animation.toValue = to
|
|
||||||
animation.isRemovedOnCompletion = removeOnCompletion
|
|
||||||
animation.fillMode = .forwards
|
|
||||||
if let completion = completion {
|
|
||||||
animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion)
|
|
||||||
}
|
|
||||||
|
|
||||||
let k = Float(UIView.animationDurationFactor)
|
|
||||||
var speed: Float = 1.0
|
|
||||||
if k != 0 && k != 1 {
|
|
||||||
speed = Float(1.0) / k
|
|
||||||
}
|
|
||||||
|
|
||||||
animation.speed = speed * Float(animation.duration / duration)
|
|
||||||
animation.isAdditive = additive
|
|
||||||
|
|
||||||
if !delay.isZero {
|
|
||||||
animation.beginTime = self.convertTime(CACurrentMediaTime(), from: nil) + delay * UIView.animationDurationFactor
|
|
||||||
animation.fillMode = .both
|
|
||||||
}
|
|
||||||
|
|
||||||
return animation
|
|
||||||
default:
|
default:
|
||||||
let k = Float(UIView.animationDurationFactor)
|
timingFunction = CAMediaTimingFunctionName.easeInEaseOut.rawValue
|
||||||
var speed: Float = 1.0
|
mediaTimingFunction = curve.asTimingFunction()
|
||||||
if k != 0 && k != 1 {
|
|
||||||
speed = Float(1.0) / k
|
|
||||||
}
|
|
||||||
|
|
||||||
let animation = CABasicAnimation(keyPath: keyPath)
|
|
||||||
if let from = from {
|
|
||||||
animation.fromValue = from
|
|
||||||
}
|
|
||||||
animation.toValue = to
|
|
||||||
animation.duration = duration
|
|
||||||
animation.timingFunction = curve.asTimingFunction()
|
|
||||||
animation.isRemovedOnCompletion = removeOnCompletion
|
|
||||||
animation.fillMode = .both
|
|
||||||
animation.speed = speed
|
|
||||||
animation.isAdditive = additive
|
|
||||||
if let completion = completion {
|
|
||||||
animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !delay.isZero {
|
|
||||||
animation.beginTime = self.convertTime(CACurrentMediaTime(), from: nil) + delay * UIView.animationDurationFactor
|
|
||||||
animation.fillMode = .both
|
|
||||||
}
|
|
||||||
|
|
||||||
return animation
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
self.animate(
|
||||||
func animate(from: AnyObject, to: AnyObject, keyPath: String, duration: Double, delay: Double, curve: Transition.Animation.Curve, removeOnCompletion: Bool, additive: Bool, completion: ((Bool) -> Void)? = nil) {
|
from: from,
|
||||||
let animation = self.makeAnimation(from: from, to: to, keyPath: keyPath, duration: duration, delay: delay, curve: curve, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion)
|
to: to,
|
||||||
self.add(animation, forKey: additive ? nil : keyPath)
|
keyPath: keyPath,
|
||||||
|
timingFunction: timingFunction,
|
||||||
|
duration: duration,
|
||||||
|
delay: delay,
|
||||||
|
mediaTimingFunction: mediaTimingFunction,
|
||||||
|
removeOnCompletion: removeOnCompletion,
|
||||||
|
additive: additive,
|
||||||
|
completion: completion
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,6 +62,14 @@ private final class FrameRangeContext {
|
|||||||
if self.animationCount != 0 {
|
if self.animationCount != 0 {
|
||||||
if self.displayLink == nil {
|
if self.displayLink == nil {
|
||||||
let displayLink = CADisplayLink(target: self, selector: #selector(self.displayEvent))
|
let displayLink = CADisplayLink(target: self, selector: #selector(self.displayEvent))
|
||||||
|
|
||||||
|
if #available(iOS 15.0, *) {
|
||||||
|
let maxFps = Float(UIScreen.main.maximumFramesPerSecond)
|
||||||
|
if maxFps > 61.0 {
|
||||||
|
displayLink.preferredFrameRateRange = CAFrameRateRange(minimum: 60.0, maximum: maxFps, preferred: maxFps)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.displayLink = displayLink
|
self.displayLink = displayLink
|
||||||
displayLink.add(to: .main, forMode: .common)
|
displayLink.add(to: .main, forMode: .common)
|
||||||
displayLink.isPaused = false
|
displayLink.isPaused = false
|
||||||
@ -95,9 +103,18 @@ public extension CAAnimation {
|
|||||||
|
|
||||||
private func adjustFrameRate(animation: CAAnimation) {
|
private func adjustFrameRate(animation: CAAnimation) {
|
||||||
if #available(iOS 15.0, *) {
|
if #available(iOS 15.0, *) {
|
||||||
|
if let animation = animation as? CABasicAnimation {
|
||||||
|
if animation.keyPath == "opacity" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
let maxFps = Float(UIScreen.main.maximumFramesPerSecond)
|
let maxFps = Float(UIScreen.main.maximumFramesPerSecond)
|
||||||
if maxFps > 61.0 {
|
if maxFps > 61.0 {
|
||||||
animation.preferredFrameRateRange = CAFrameRateRange(minimum: 60.0, maximum: maxFps, preferred: maxFps)
|
#if DEBUG
|
||||||
|
//let _ = frameRangeContext.add()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
animation.preferredFrameRateRange = CAFrameRateRange(minimum: 30.0, maximum: maxFps, preferred: maxFps)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -138,32 +155,64 @@ public extension CALayer {
|
|||||||
|
|
||||||
return animation
|
return animation
|
||||||
} else if timingFunction == kCAMediaTimingFunctionSpring {
|
} else if timingFunction == kCAMediaTimingFunctionSpring {
|
||||||
let animation = makeSpringAnimation(keyPath)
|
if duration == 0.5 {
|
||||||
animation.fromValue = from
|
let animation = makeSpringAnimation(keyPath)
|
||||||
animation.toValue = to
|
animation.fromValue = from
|
||||||
animation.isRemovedOnCompletion = removeOnCompletion
|
animation.toValue = to
|
||||||
animation.fillMode = .forwards
|
animation.isRemovedOnCompletion = removeOnCompletion
|
||||||
if let completion = completion {
|
animation.fillMode = .forwards
|
||||||
animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion)
|
if let completion = completion {
|
||||||
|
animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
let k = Float(UIView.animationDurationFactor())
|
||||||
|
var speed: Float = 1.0
|
||||||
|
if k != 0 && k != 1 {
|
||||||
|
speed = Float(1.0) / k
|
||||||
|
}
|
||||||
|
|
||||||
|
animation.speed = speed * Float(animation.duration / duration)
|
||||||
|
animation.isAdditive = additive
|
||||||
|
|
||||||
|
if !delay.isZero {
|
||||||
|
animation.beginTime = self.convertTime(CACurrentMediaTime(), from: nil) + delay * UIView.animationDurationFactor()
|
||||||
|
animation.fillMode = .both
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustFrameRate(animation: animation)
|
||||||
|
|
||||||
|
return animation
|
||||||
|
} else {
|
||||||
|
let k = Float(UIView.animationDurationFactor())
|
||||||
|
var speed: Float = 1.0
|
||||||
|
if k != 0 && k != 1 {
|
||||||
|
speed = Float(1.0) / k
|
||||||
|
}
|
||||||
|
|
||||||
|
let animation = CABasicAnimation(keyPath: keyPath)
|
||||||
|
animation.fromValue = from
|
||||||
|
animation.toValue = to
|
||||||
|
animation.duration = duration
|
||||||
|
|
||||||
|
animation.timingFunction = CAMediaTimingFunction(controlPoints: 0.380, 0.700, 0.125, 1.000)
|
||||||
|
|
||||||
|
animation.isRemovedOnCompletion = removeOnCompletion
|
||||||
|
animation.fillMode = .forwards
|
||||||
|
animation.speed = speed
|
||||||
|
animation.isAdditive = additive
|
||||||
|
if let completion = completion {
|
||||||
|
animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !delay.isZero {
|
||||||
|
animation.beginTime = self.convertTime(CACurrentMediaTime(), from: nil) + delay * UIView.animationDurationFactor()
|
||||||
|
animation.fillMode = .both
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustFrameRate(animation: animation)
|
||||||
|
|
||||||
|
return animation
|
||||||
}
|
}
|
||||||
|
|
||||||
let k = Float(UIView.animationDurationFactor())
|
|
||||||
var speed: Float = 1.0
|
|
||||||
if k != 0 && k != 1 {
|
|
||||||
speed = Float(1.0) / k
|
|
||||||
}
|
|
||||||
|
|
||||||
animation.speed = speed * Float(animation.duration / duration)
|
|
||||||
animation.isAdditive = additive
|
|
||||||
|
|
||||||
if !delay.isZero {
|
|
||||||
animation.beginTime = self.convertTime(CACurrentMediaTime(), from: nil) + delay * UIView.animationDurationFactor()
|
|
||||||
animation.fillMode = .both
|
|
||||||
}
|
|
||||||
|
|
||||||
adjustFrameRate(animation: animation)
|
|
||||||
|
|
||||||
return animation
|
|
||||||
} else {
|
} else {
|
||||||
let k = Float(UIView.animationDurationFactor())
|
let k = Float(UIView.animationDurationFactor())
|
||||||
var speed: Float = 1.0
|
var speed: Float = 1.0
|
||||||
@ -178,7 +227,13 @@ public extension CALayer {
|
|||||||
if let mediaTimingFunction = mediaTimingFunction {
|
if let mediaTimingFunction = mediaTimingFunction {
|
||||||
animation.timingFunction = mediaTimingFunction
|
animation.timingFunction = mediaTimingFunction
|
||||||
} else {
|
} else {
|
||||||
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName(rawValue: timingFunction))
|
switch timingFunction {
|
||||||
|
case CAMediaTimingFunctionName.linear.rawValue, CAMediaTimingFunctionName.easeIn.rawValue, CAMediaTimingFunctionName.easeOut.rawValue, CAMediaTimingFunctionName.easeInEaseOut.rawValue, CAMediaTimingFunctionName.default.rawValue:
|
||||||
|
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName(rawValue: timingFunction))
|
||||||
|
default:
|
||||||
|
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
animation.isRemovedOnCompletion = removeOnCompletion
|
animation.isRemovedOnCompletion = removeOnCompletion
|
||||||
animation.fillMode = .forwards
|
animation.fillMode = .forwards
|
||||||
|
@ -161,7 +161,7 @@ public final class PeerOnlineMarkerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if animated {
|
if animated {
|
||||||
let initialScale: CGFloat = strongSelf.iconNode.isHidden ? 0.0 : CGFloat((strongSelf.iconNode.value(forKeyPath: "layer.presentationLayer.transform.scale.x") as? NSNumber)?.floatValue ?? 1.0)
|
let initialScale: CGFloat = strongSelf.iconNode.isHidden ? 0.001 : CGFloat((strongSelf.iconNode.value(forKeyPath: "layer.presentationLayer.transform.scale.x") as? NSNumber)?.floatValue ?? 1.0)
|
||||||
let targetScale: CGFloat = online ? 1.0 : 0.0
|
let targetScale: CGFloat = online ? 1.0 : 0.0
|
||||||
if initialScale != targetScale {
|
if initialScale != targetScale {
|
||||||
strongSelf.iconNode.isHidden = false
|
strongSelf.iconNode.isHidden = false
|
||||||
|
@ -209,8 +209,64 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class AnimationSupportContext {
|
||||||
|
private let window: UIWindow
|
||||||
|
private let testView: UIView
|
||||||
|
private var animationCount: Int = 0
|
||||||
|
private var displayLink: CADisplayLink?
|
||||||
|
|
||||||
|
init(window: UIWindow) {
|
||||||
|
self.window = window
|
||||||
|
self.testView = UIView()
|
||||||
|
window.addSubview(self.testView)
|
||||||
|
self.testView.frame = CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0))
|
||||||
|
self.testView.backgroundColor = .black
|
||||||
|
}
|
||||||
|
|
||||||
|
func add() {
|
||||||
|
self.animationCount += 1
|
||||||
|
self.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
func remove() {
|
||||||
|
self.animationCount -= 1
|
||||||
|
if self.animationCount < 0 {
|
||||||
|
self.animationCount = 0
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
self.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func displayEvent() {
|
||||||
|
self.testView.frame = CGRect(origin: CGPoint(x: self.testView.frame.minX == 0.0 ? 1.0 : 0.0, y: 0.0), size: self.testView.bounds.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func update() {
|
||||||
|
if self.animationCount != 0 {
|
||||||
|
if self.displayLink == nil {
|
||||||
|
let displayLink = CADisplayLink(target: self, selector: #selector(self.displayEvent))
|
||||||
|
|
||||||
|
if #available(iOS 15.0, *) {
|
||||||
|
let maxFps = Float(UIScreen.main.maximumFramesPerSecond)
|
||||||
|
if maxFps > 61.0 {
|
||||||
|
displayLink.preferredFrameRateRange = CAFrameRateRange(minimum: 60.0, maximum: maxFps, preferred: maxFps)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.displayLink = displayLink
|
||||||
|
displayLink.add(to: .main, forMode: .common)
|
||||||
|
displayLink.isPaused = false
|
||||||
|
}
|
||||||
|
} else if let displayLink = self.displayLink {
|
||||||
|
self.displayLink = nil
|
||||||
|
displayLink.invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@objc(AppDelegate) class AppDelegate: UIResponder, UIApplicationDelegate, PKPushRegistryDelegate, UNUserNotificationCenterDelegate {
|
@objc(AppDelegate) class AppDelegate: UIResponder, UIApplicationDelegate, PKPushRegistryDelegate, UNUserNotificationCenterDelegate {
|
||||||
@objc var window: UIWindow?
|
@objc var window: UIWindow?
|
||||||
|
private var animationSupportContext: AnimationSupportContext?
|
||||||
var nativeWindow: (UIWindow & WindowHost)?
|
var nativeWindow: (UIWindow & WindowHost)?
|
||||||
var mainWindow: Window1!
|
var mainWindow: Window1!
|
||||||
private var dataImportSplash: LegacyDataImportSplash?
|
private var dataImportSplash: LegacyDataImportSplash?
|
||||||
@ -306,6 +362,9 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||||||
self.window = window
|
self.window = window
|
||||||
self.nativeWindow = window
|
self.nativeWindow = window
|
||||||
|
|
||||||
|
//self.animationSupportContext = AnimationSupportContext(window: window)
|
||||||
|
//self.animationSupportContext?.add()
|
||||||
|
|
||||||
let clearNotificationsManager = ClearNotificationsManager(getNotificationIds: { completion in
|
let clearNotificationsManager = ClearNotificationsManager(getNotificationIds: { completion in
|
||||||
if #available(iOS 10.0, *) {
|
if #available(iOS 10.0, *) {
|
||||||
UNUserNotificationCenter.current().getDeliveredNotifications(completionHandler: { notifications in
|
UNUserNotificationCenter.current().getDeliveredNotifications(completionHandler: { notifications in
|
||||||
|
@ -55,6 +55,7 @@ CABasicAnimation * _Nonnull makeSpringAnimationImpl(NSString * _Nonnull keyPath)
|
|||||||
springAnimation.damping = 500.0f;
|
springAnimation.damping = 500.0f;
|
||||||
springAnimation.duration = 0.5;
|
springAnimation.duration = 0.5;
|
||||||
springAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
|
springAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
|
||||||
|
|
||||||
return springAnimation;
|
return springAnimation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,7 +179,8 @@ static bool notyfyingShiftState = false;
|
|||||||
|
|
||||||
[RuntimeUtils swizzleInstanceMethodOfClass:[UIWindow class] currentSelector:@selector(initWithFrame:) newSelector:@selector(_65087dc8_initWithFrame:)];
|
[RuntimeUtils swizzleInstanceMethodOfClass:[UIWindow class] currentSelector:@selector(initWithFrame:) newSelector:@selector(_65087dc8_initWithFrame:)];
|
||||||
|
|
||||||
if (@available(iOS 15.0, *)) {
|
if (@available(iOS 16.0, *)) {
|
||||||
|
} else if (@available(iOS 15.0, *)) {
|
||||||
[RuntimeUtils swizzleInstanceMethodOfClass:[CADisplayLink class] currentSelector:@selector(setPreferredFrameRateRange:) newSelector:@selector(_65087dc8_setPreferredFrameRateRange:)];
|
[RuntimeUtils swizzleInstanceMethodOfClass:[CADisplayLink class] currentSelector:@selector(setPreferredFrameRateRange:) newSelector:@selector(_65087dc8_setPreferredFrameRateRange:)];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user