【问题标题】:Pausing and resuming UIView animation with alpha doesn't work when user presses Home button当用户按下主页按钮时,使用 alpha 暂停和恢复 UIView 动画不起作用
【发布时间】:2025-12-13 21:15:02
【问题描述】:

问题:

  1. 当用户按下 Home 按钮并返回应用程序时,如何暂停嵌套 UIView 动画以动画化 alpha/不透明度?
  2. 我在下面的代码中遗漏了什么?谢谢。

背景:

我有一个简单的嵌套 UIView 动画,它在 1 分钟内从 alpha = 0alpha = 1 的不同时间缓慢调整视图的 alpha。

当用户按下 Home 按钮时,UIView 动画不会暂停,因此当应用程序恢复时,UIView 动画会快进到动画的最后,alpha 值会在应用程序恢复时立即从 0 变为 1。

所以,当用户按下设备的 Home 按钮并暂时挂起应用程序时,我尝试暂停 UIView 动画。

我有一个暂停动画 (pauseLayer) 然后重新开始动画 (resumeLayer) 的函数。从UIButton 调用它们时,这两个函数非常有效。它暂停alpha 并按预期恢复动画。 (但是,如果在动画暂停时按下 Home 按钮,当它恢复时,alpha 会立即从 0 变为 1。)

当我尝试在用户按下主页按钮时调用pauseLayer(接收到WillResignActive 通知),然后返回到应用程序(接收到WillEnterForeground 通知),动画不会暂停并且resume,而是在恢复应用时 alpha 立即从 0 变为 1。

它看起来应该有效,但它没有。


代码:

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var myView1: UIView!
    @IBOutlet weak var myView2: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        setNotifications()
        myAnimation()
    }

    @IBAction func myPauseButton(sender: UIButton) {
        let layer = self.view.layer
        pauseLayer(layer)
    }

    @IBAction func myResumeButton(sender: UIButton) {
        let layer = self.view.layer
        resumeLayer(layer)
    }

    func setNotifications() {
        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.WillEnterForeground(_:)), name: UIApplicationWillEnterForegroundNotification, object: nil)

        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.WillResignActive(_:)), name: UIApplicationWillResignActiveNotification, object: nil)
    }

    func WillEnterForeground(notification : NSNotification) {
        let layer = self.view.layer
        resumeLayer(layer)
    }

    func WillResignActive(notification : NSNotification) {
        let layer = self.view.layer
        pauseLayer(layer)
    }

    func pauseLayer(layer: CALayer) {
        let pausedTime: CFTimeInterval = layer.convertTime(CACurrentMediaTime(), fromLayer: nil)
        layer.speed = 0.0
        layer.timeOffset = pausedTime
    }

    func resumeLayer(layer: CALayer) {
        let pausedTime: CFTimeInterval = layer.timeOffset
        layer.speed = 1.0
        layer.timeOffset = 0.0
        layer.beginTime = 0.0
        let timeSincePause: CFTimeInterval = layer.convertTime(CACurrentMediaTime(), fromLayer: nil) - pausedTime
        layer.beginTime = timeSincePause
    }

    func myAnimation() {
        self.myView1.alpha = 0
        UIView.animateWithDuration(15, delay: 0, options: [.CurveEaseInOut], animations: {
            self.myView1.alpha = 0.1
            }, completion: {(finished: Bool) in
                UIView.animateWithDuration(15, delay: 0, options: [.CurveEaseInOut], animations: {
                    self.myView1.alpha = 0.2
                    }, completion: {(finished: Bool) in
                        UIView.animateWithDuration(30, delay: 0, options: [.CurveEaseInOut], animations: {
                            self.myView1.alpha = 1
                            }, completion: nil)
                })
        })

        self.myView2.alpha = 0
        UIView.animateWithDuration(15, delay: 0, options: [.CurveEaseInOut], animations: {
            self.myView2.alpha = 0.1
            }, completion: {(finished: Bool) in
                UIView.animateWithDuration(15, delay: 0, options: [.CurveEaseInOut], animations: {
                    self.myView2.alpha = 0.2
                    }, completion: {(finished: Bool) in
                        UIView.animateWithDuration(30, delay: 0, options: [.CurveEaseInOut], animations: {
                            self.myView2.alpha = 1
                            }, completion: nil)
                })
        })
    }

}

编辑:

如果我将if (finished) { 添加到每个部分,然后按主页按钮,然后返回应用程序,动画只会前进到下一部分并停止,不再进行。这样更好,但问题是 resumeLayer 似乎不起作用,所以动画仍然停止并且不会继续。

        self.myView2.alpha = 0
        UIView.animateWithDuration(15, delay: 0, options: [.CurveEaseInOut, .LayoutSubviews, .AllowUserInteraction, .BeginFromCurrentState], animations: {
            self.myView2.alpha = 0.1
            }, completion: {(finished: Bool) in
                if (finished) {
                UIView.animateWithDuration(15, delay: 0, options: [.CurveEaseInOut, .LayoutSubviews, .AllowUserInteraction, .BeginFromCurrentState], animations: {
                    self.myView2.alpha = 0.2
                    }, completion: {(finished: Bool) in
                        if (finished) {
                        UIView.animateWithDuration(30, delay: 0, options: [.CurveEaseInOut, .LayoutSubviews, .AllowUserInteraction, .BeginFromCurrentState], animations: {
                            self.myView2.alpha = 1
                            }, completion: nil)
                        }
                })
                }
        })

【问题讨论】:

  • 也许你应该使用 UIApplicationDidBecomeActive 而不是 UIApplicationWillEnterForeground
  • 不幸的是,@Calimari328 没有用,不过谢谢,我没有尝试。

标签: ios swift animation uiview uiviewanimation


【解决方案1】:

你不需要使用 UIView Animation 来完成一个简单的淡入淡出。

您可以使用 Timer 对象和一点数学来手动为其设置动画。

class ViewController: UIViewController {        
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = colorScheme.red

        NotificationCenter.default.addObserver(self, selector: #selector(self.didBecomeActive), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(self.didResignActive), name: NSNotification.Name.UIApplicationWillResignActive, object: nil)
    }

    func didBecomeActive() {
        if case min_t..<max_t = t {
            beginAnimation(at: t)
        }
    }

    func didResignActive() {
        _ = pauseAnimation()
    }


    var t = TimeInterval(0.0)
    let min_t = TimeInterval(0)
    let max_t = TimeInterval(100)
    let delta = TimeInterval(0.1)
    var timerObj: Timer?

    func timer(_ aTimer: Timer) {
        if t < max_t {
            // Using a parabola
            view.alpha = CGFloat(0.0001 * t * t)
        } else {
            _ = pauseAnimation()
        }
        t = t + 1
    }

    func beginAnimation(at t: TimeInterval = 0) {
        self.t = t
        timerObj = Timer.scheduledTimer(timeInterval: delta, target: self, selector: #selector(self.timer), userInfo: nil, repeats: true)
    }

    func pauseAnimation() -> TimeInterval {
        timerObj?.invalidate()
        return t
    }

    override func viewDidAppear(_ animated: Bool) {
        beginAnimation()
    }
}

但是,您的应用程序实际上可能更复杂,而您的示例只是一种简化。那么,这个怎么样?

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .red

        NotificationCenter.default.addObserver(self, selector: #selector(self.WillEnterForeground(notification:)), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(self.WillResignActive(notification:)), name: NSNotification.Name.UIApplicationWillResignActive, object: nil)
    }

    func WillEnterForeground(notification : NSNotification) {
        if let timegap = endDate?.timeIntervalSince(startDate)
            , timegap < duration {
            beginAnimation(with: timegap/duration)
        }
    }

    func WillResignActive(notification : NSNotification) {
        let layer = self.view.layer
        layer.removeAllAnimations()
    }

    var duration = TimeInterval(10)
    var percentComplete = 0.0
    var startDate: Date = Date()
    var endDate: Date?

    func beginAnimation(with percent: Double = 0.0) {
        view.alpha = CGFloat(percent)

        startDate = Date()
        UIView.animateKeyframes(withDuration: duration * (1 - percent)
            , delay: 0, options: UIViewKeyframeAnimationOptions.calculationModeLinear,
              animations: {
                if percent < 0.2 {
                    UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.2, animations: {
                        self.view.alpha = 0.1
                    })
                }
                if percent < 0.5 {
                    UIView.addKeyframe(withRelativeStartTime: 0.2, relativeDuration: 0.3, animations: {
                        self.view.alpha = 0.2
                    })
                }

                UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.7, animations: {
                    self.view.alpha = 1.0
                })
        }) { (b: Bool) in
            if b == false {
                self.endDate = Date()
                if let timegap = self.endDate?.timeIntervalSince(self.startDate)
                    , timegap < self.duration {
                    self.view.alpha = CGFloat(timegap / self.duration)
                }
            }
        }
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        beginAnimation()
    }

}

主要思想是获取动画开始前的时间和结束的时间。如果动画成功,完成将告诉我们 true,但如果用户按下主页按钮,完成块将告诉我们 false。如果它是假的,我们可以捕获日期并找出时间差并计算完成百分比。那么我们可以使用百分比来确定剩余时间。

希望对你有帮助

【讨论】:

  • 这给出了一些想法。谢谢。我希望保留UIView.animateWithDuration,因为关键帧动画有时会受到限制。原始方法在按下按钮时有效,但不幸的是,将应用程序置于后台会中断该方法。你是对的,有一个比这里放的更精细的动画。然而,这个简洁的版本在 alpha 动画方面引发了完全相同的问题。这里的简洁示例是为了保持一切简单易懂。我不明白的是,为什么动画在每个部分都没有完成的时候在后台快进?
最近更新 更多