UIView.animate seems to freeze other view updates - ios

I have a UIView that is a countdown clock, which I animate with UIView.animate. There is also a running timer that shows the time on a UILabel, when the timer is fired. When the game is in transition (during the count down), the UILabel stops changing. I've put a "print" statement into the timer function and it shows up as expected.
I've been searching for hints about maybe the UIVIew.animate freezes other views, but I see multiple apps moving views at the same time. Maybe I'm supposed to put things on different thread?
#objc func timerUpdate () {
if gameIsRunning {
let endTime = Date.init()
let difference = endTime.timeIntervalSince(gameClock)
let formattedString = String(format: "%3.1f",kDefaultGameClock - difference)
timerLabel.text = "\(formattedString)"
}
}
private func countDownFrom (x: Int) {
if x != 0 && x > 0 {
UIView.animate(withDuration: 1.0,
delay: 0.0,
usingSpringWithDamping: 0.5,
initialSpringVelocity:1.0,
options: .curveEaseOut,
animations: {
self.successLabel.text = "\(x)"
self.successLabel.transform = self.successLabel.transform.scaledBy(x: 4.0, y: 4.0)
}, completion: {_ in
self.successLabel.transform = self.successLabel.transform.scaledBy(x: 0.250, y: 0.250)
if (x - 1 > 0) {
self.countDownFrom(x: x - 1)
} else {
self.successLabel.text = kDefaultGoodGame
self.successLabel.isHidden = true
self.startTheGame()
self.updateScreen()
}
})
}
}

When I tried your code in an empty project, I was able to update both labels.
If timerLabel isn't being updated, it could be a thread issue. UI updates should be on the main thread.
DispatchQueue.main.async {
self.timerLabel.text = "\(formattedString)"
}
For what it's worth, here is what I have working in an empty project.
#IBOutlet weak var timerLabel: UILabel!
#IBOutlet weak var successLabel: UILabel!
let kDefaultGameClock = 30.0
var gameIsRunning = false
var gameClock = Date()
override func viewDidLoad() {
super.viewDidLoad()
start()
}
func start() {
gameIsRunning = true
countDownFrom(x: 30)
Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(timerUpdate), userInfo: nil, repeats: true)
}
#objc func timerUpdate() {
if gameIsRunning {
let endTime = Date.init()
let difference = endTime.timeIntervalSince(gameClock)
let formattedString = String(format: "%3.1f", kDefaultGameClock - difference)
timerLabel.text = "\(formattedString)" // Could be a main thread issue here?
}
}
private func countDownFrom(x: Int) {
if x != 0 && x > 0 {
UIView.animate(withDuration: 1.0,
delay: 0.0,
usingSpringWithDamping: 0.5,
initialSpringVelocity:1.0,
options: .curveEaseOut,
animations: {
self.successLabel.text = "\(x)"
self.successLabel.transform = self.successLabel.transform.scaledBy(x: 4.0, y: 4.0)
}, completion: {_ in
self.successLabel.transform = self.successLabel.transform.scaledBy(x: 0.250, y: 0.250)
if (x - 1 > 0) {
self.countDownFrom(x: x - 1)
} else {
self.successLabel.text = ""
self.successLabel.isHidden = true
//self.startTheGame()
//self.updateScreen()
}
})
}
}
A little off topic, but you might want to declare your Timer as a variable to invalidate it once you're done updating the timerLabel.

Related

iOS Spin UIImageView with deceleration motion at random position

I trying to create a spin wheel which rotate on tap and it rotates for certain period of time and stops at some random circular angle.
import UIKit
class MasterViewController: UIViewController {
lazy var imageView: UIImageView = {
let bounds = self.view.bounds
let v = UIImageView()
v.backgroundColor = .red
v.frame = CGRect(x: 0, y: 0,
width: bounds.width - 100,
height: bounds.width - 100)
v.center = self.view.center
return v
}()
lazy var subView: UIView = {
let v = UIView()
v.backgroundColor = .black
v.frame = CGRect(x: 0, y: 0,
width: 30,
height: 30)
return v
}()
var dateTouchesEnded: Date?
var dateTouchesStarted: Date?
var deltaAngle = CGFloat(0)
var startTransform: CGAffineTransform?
var touchPointStart: CGPoint?
override func viewDidLoad() {
super.viewDidLoad()
self.imageView.addSubview(self.subView)
self.view.addSubview(self.imageView)
self.imageView.isUserInteractionEnabled = true
self.setupGesture()
}
private func setupGesture() {
let gesture = UITapGestureRecognizer(target: self,
action: #selector(handleGesture(_:)))
self.view.addGestureRecognizer(gesture)
}
#objc
func handleGesture(_ sender: UITapGestureRecognizer) {
var timeDelta = 1.0
let _ = Timer.scheduledTimer(withTimeInterval: 0.2,
repeats: true) { (timer) in
if timeDelta < 0 {
timer.invalidate()
} else {
timeDelta -= 0.03
self.spinImage(timeDelta: timeDelta)
}
}
}
func spinImage(timeDelta: Double) {
print("TIME DELTA:", timeDelta)
let direction: Double = 1
let rotation: Double = 1
UIView.animate(withDuration: 5,
delay: 0,
options: .curveEaseOut,
animations: {
let transform = self.imageView.transform.rotated(
by: CGFloat(direction) * CGFloat(rotation) * CGFloat(Double.pi)
)
self.imageView.transform = transform
}, completion: nil)
}
}
I tried via above code, but always stops on initial position
i.e. the initial and final transform is same.
I want it to be random at every time.
One thing you can do is make a counter with a random number within a specified range. When the time fires, call spinImage then decrement the counter. Keep doing that until the counter reaches zero. This random number will give you some variability so that you don't wind up with the same result every time.
#objc func handleGesture(_ sender: UITapGestureRecognizer) {
var counter = Int.random(in: 30...33)
let _ = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { (timer) in
if counter < 0 {
timer.invalidate()
} else {
counter -= 1
self.spinImage()
}
}
}
In spinImage, instead of rotating by CGFloat.pi, rotate by by CGFloat.pi / 2 so that you have four possible outcomes instead of two.
func spinImage() {
UIView.animate(withDuration: 2.5,
delay: 0,
options: .curveEaseOut,
animations: {
let transform = self.imageView.transform.rotated(
by: CGFloat.pi / 2
)
self.imageView.transform = transform
}, completion: nil)
}
You may want to mess around with the counter values, the timer interval, and the animation duration to get the effect that you want. The values I chose here are somewhat arbitrary.

Stop the autoscroll in scrollview on user interaction swift

I using the pagecontroller in my application.
it is working fine every thing it will scroll automatically for every 3 sec time and current position also changed based on user custom swipe.
But I having one issue here when user manually swipe the scrollview on that time I want to stop the auto scroll.
Please find with the my following code.
var slides:[Slide] = [];
var offSet: CGFloat = 0
override func viewDidLoad() {
super.viewDidLoad()
slides = createSlides()
setupSlideScrollView(slides: slides)
pageControl.numberOfPages = slides.count
pageControl.currentPage = 0
view.bringSubview(toFront: pageControl)
let timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(autoScroll), userInfo: nil, repeats: true)
}
#objc func autoScroll() {
let totalPossibleOffset = CGFloat(slides.count - 1) * self.view.bounds.size.width
if offSet == totalPossibleOffset {
offSet = 0 // come back to the first image after the last image
}
else {
offSet += self.view.bounds.size.width
}
DispatchQueue.main.async() {
UIView.animate(withDuration: 0.3, delay: 0, options: UIViewAnimationOptions.curveLinear, animations: {
self.scrollView.contentOffset.x = CGFloat(self.offSet)
}, completion: nil)
}
}
Add scroll view delegate and use scrollViewDidEndDragging methods when it calls just reset your timer so after 3 sec start again it working as it is.
The code is untested, You should check with current index with your total number slides count
Create one global index like below and change your conditions. And you need to manage this index while swipe function as well and Invalidate your timer while swipe. Your timer object should be in global
timer.invalidate()
var currentPage = 0
#objc func autoScroll() {
let totalPossibleOffset = 0
if currentPage == slides.count {
currentPage = 0 // come back to the first image after the last image
totalPossibleOffset = 0
}
else {
currentPage = currentPage + 1
totalPossibleOffset = currentPage * self.view.bounds.size.width
}
DispatchQueue.main.async() {
UIView.animate(withDuration: 0.3, delay: 0, options: UIViewAnimationOptions.curveLinear, animations: {
self.scrollView.contentOffset.x = CGFloat(totalPossibleOffset)
}, completion: nil)
}
}

Move scrollview automatically with page controller

Hi I am using the PageController in my application.
I have inserted all the data on scrollView.
What I want do is, I want to move the scrollview automatically like page control with specific time.
When the scrollView reaches to last page it again recall all pages speedy and start from first onwards.
I want to load/get first page very smoothly after last page completion.
Find my code with following.
var slides:[Slide] = [];
var offSet: CGFloat = 0
override func viewDidLoad() {
super.viewDidLoad()
slides = createSlides()
setupSlideScrollView(slides: slides)
pageControl.numberOfPages = slides.count
pageControl.currentPage = 0
view.bringSubview(toFront: pageControl)
let timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(autoScroll), userInfo: nil, repeats: true)
}
#objc func autoScroll() {
let totalPossibleOffset = CGFloat(slides.count - 1) * self.view.bounds.size.width
if offSet == totalPossibleOffset {
offSet = 0 // come back to the first image after the last image
}
else {
offSet += self.view.bounds.size.width
}
DispatchQueue.main.async() {
UIView.animate(withDuration: 0.3, delay: 0, options: UIViewAnimationOptions.curveLinear, animations: {
self.scrollView.contentOffset.x = CGFloat(self.offSet)
}, completion: nil)
}
}
#objc func autoScroll() {
let totalPossibleOffset = CGFloat(slides.count - 1) * self.view.bounds.size.width
if offSet == totalPossibleOffset {
offSet = 0 // come back to the first image after the last image
}
else {
offSet += self.view.bounds.size.width
}
DispatchQueue.main.async() {
UIView.animate(withDuration: 0.1, delay: 0, options: UIView.AnimationOptions.curveLinear, animations: {
self.scrollView.scroll(topOffset:offSet, animated: true)
}, completion: nil)
}
}
extension UIScrollView {
// Bonus: Scroll
func scroll(topOffset:Float, animated: Bool) {
let ofSetValue = CGPoint(x: topOffset, y: 0)
setContentOffset(ofSetValue, animated: animated)
}
}
Use the following code working fine.
#objc func autoScroll() {
let totalPossibleOffset = CGFloat(slides.count - 1) * self.view.bounds.size.width
if offSet == totalPossibleOffset {
offSet = 0 // come back to the first image after the last image
}
else {
offSet += self.view.bounds.size.width
}
DispatchQueue.main.async() {
UIView.animate(withDuration: 0.0, delay: 0, options: UIViewAnimationOptions.curveLinear, animations: {
self.scrollView.contentOffset.x = CGFloat(self.offSet)
}, completion: nil)
}
}

Reset UIProgressView and begin animating immediately with Swift 3

I have a progress view like the one Snapchat and Instagram Stories have. My content changes after the progress view reaches to the end or when tapped on a button.
I'm reseting the progress view when content changes. Everything works as expected while there is no intervention. But when tapped on next button the progress view doesn't start again until the other loop executes.
You can see the video here quickly.
I came across this question while I was researching, I have the same scenario but I couldn't apply the principle as a newbie with swift 3.
Here is my code, any help would be highly appreciated:
func startTimer(){
self.timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(myVievController.nextItem), userInfo: nil, repeats: false)
}
func updateProgressBar() {
self.progressView.setProgress(1.0, animated: false)
UIView.animate(withDuration: 2.8, animations: {() -> Void in
self.progressView.layoutIfNeeded()
}, completion: { finished in
if finished {
self.progressView.setProgress(0, animated: false)
}
})
}
func nextItem(){
// ...
self.updateUI(item: self.myCurrentItem)
// ...
}
func updateUI(item:MyItem){
self.updateProgressBar()
self.timer.invalidate()
self.startTimer()
// Clear old item and fill new values etc...
}
#IBAction func nextButtonPressed(_ sender: UIButton) {
self.nextItem()
}
Could also do it with a subclass:
class HelloWorld: UIProgressView {
func startProgressing(duration: TimeInterval, resetProgress: Bool, completion: #escaping (Void) -> Void) {
stopProgressing()
// Reset to 0
progress = 0.0
layoutIfNeeded()
// Set the 'destination' progress
progress = 1.0
// Animate the progress
UIView.animate(withDuration: duration, animations: {
self.layoutIfNeeded()
}) { finished in
// Remove this guard-block, if you want the completion to be called all the time - even when the progression was interrupted
guard finished else { return }
if resetProgress { self.progress = 0.0 }
completion()
}
}
func stopProgressing() {
// Because the 'track' layer has animations on it, we'll try to remove them
layer.sublayers?.forEach { $0.removeAllAnimations() }
}
}
This can be used by calling -startProgressing() when ever you need to start the progressView again from the start. The completion is called when it has finished the animation, so you can change the views etc.
An example of use:
progressView.startProgressing(duration: 5.0, resetProgress: true) {
// Set the next things you need... change to a next item etc.
}
You probably need something like this to update your progress bar: self.progressView.setProgress(0, animated: false)
func updateProgressBar() {
DispatchQueue.main.async {
self.progressView.setProgress(0.1, animated: false)
}
if self.progressView.Progress == 1.0 {
self.timer.invalidate()
} else {
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(myVievController.updateProgressBar), userInfo: nil, repeats: false)
}
}
Then you can invalidate the timer and stop the update when you are at 100%
Example
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var progressView: UIProgressView!
var timer : Timer?
var timerCount = 0
var imageArray : Array<UIImage> = []
// MARK: - Lifecycle -
override func viewDidLoad() {
super.viewDidLoad()
// create gradient images
buildImageArray(duration: 3, highlightPercentage: 10.0, mainColor: .green, highlightColor: .yellow, imageWidth: Double(progressView.frame.width))
// set progressView to first image
progressView.progressImage = imageArray[0]
// set progressView progress
progressView.progress = 0.7
// schedule timer
if self.timer == nil {
self.timer = Timer.scheduledTimer(timeInterval: 1/60, target: self, selector: #selector(updateGradient), userInfo: nil, repeats: true)
RunLoop.main.add(self.timer!, forMode: .common)
}
}
func buildImageArray(duration: Int, highlightPercentage: Double, mainColor: UIColor, highlightColor: UIColor, imageWidth: Double){
let keyFrames = duration * 60
for i in 0..<keyFrames {
// Drawing code
let frame = CGRect(x: 0.0, y: 0.0, width: imageWidth, height: 1.0)
let layer = CAGradientLayer()
layer.frame = frame
layer.startPoint = CGPoint(x: 0.0, y: 0.5)
layer.endPoint = CGPoint(x: 1.0, y: 0.5)
var colors : [UIColor] = []
for n in 0..<keyFrames {
colors.append(mainColor)
}
let highlightKeyFrames : Int = Int(floor(Double(keyFrames) * (highlightPercentage/100)))
// 300 * .3 = 90 kf
for x in 0..<highlightKeyFrames {
let p = i+x
if p < keyFrames {
colors[p] = highlightColor
}
}
layer.colors = colors.map { $0.cgColor }
layer.bounds = frame
let image = UIImage.imageWithLayer(layer: layer)
imageArray.append(image)
}
}
// updateGradient
#objc func updateGradient(){
// crop image to match progress
let newWidth = self.progressView.frame.width * CGFloat(progressView.progress)
let progressImage = imageArray[timerCount].crop(rect: CGRect(x: 0, y: 0, width: newWidth, height: 1))
progressView.progressImage = progressImage
// increment timer
timerCount = timerCount + 1
if timerCount >= imageArray.count {
timerCount = 0
}
}
}
extension UIImage {
class func imageWithLayer(layer: CALayer) -> UIImage {
UIGraphicsBeginImageContextWithOptions(layer.bounds.size, layer.isOpaque, 0.0)
layer.render(in: UIGraphicsGetCurrentContext()!)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return img!
}
func crop( rect: CGRect) -> UIImage {
var rect = rect
rect.origin.x*=self.scale
rect.origin.y*=self.scale
rect.size.width*=self.scale
rect.size.height*=self.scale
let imageRef = self.cgImage!.cropping(to: rect)
let image = UIImage(cgImage: imageRef!, scale: self.scale, orientation: self.imageOrientation)
return image
}
}

Pausing and resuming UIView animation with alpha doesn't work when user presses Home button

Question:
How can I pause a nested UIView animation that animates alpha/opacity when the user presses the Home button and returns back to the app?
What have I missed in the code below? Thank you.
Background:
I have a simple nested UIView animation that adjusts the alpha of a view slowly at different times from alpha = 0 to alpha = 1 over 1 minute.
When a user presses the Home button, the UIView animation does not pause, so when the app resumes, the UIView animation fasts forward to the very end of the animation, the alpha changes instantly from 0 to 1 on resuming the app.
So, I’m trying to pause the UIView animation when the user presses the device Home button and temporarily suspends the app.
I have a function that pauses the animation (pauseLayer) and then recommences the animation (resumeLayer). These two functions work great when calling them from a UIButton. It pauses the alpha and resumes the animation as expected. (However, if the Home button is pressed while the animation is paused, when it resumes the alpha changes instantly from 0 to 1.)
When I try calling the pauseLayer when the user presses the Home button (receiving the WillResignActive notification), and then returns back to the app (receiving the WillEnterForeground notification), the animation doesn’t pause and resume, instead the alpha changes instantly from 0 to 1 on resuming the app.
It would seem it should work, but it doesn’t.
Code:
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)
})
})
}
}
EDIT:
If I add if (finished) { to each section, then press the Home button, then return to the app, the animation only progresses to the next section and stops, no further. This is better, but the issue then is that the resumeLayer doesn’t seem to work, so the animation remains stopped and doesn’t continue.
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)
}
})
}
})
You do not need to use UIView Animation to accomplish a simple fade.
You could use an Timer object and a little Math to manually animate it.
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()
}
}
However, there is a possibility that your application is actually more complicated and your example is a simplification. Well, how about this?
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()
}
}
The main idea is to get the time before the animation starts and the time when it ends. The completion will tell us true if the animation was successful, but if the user presses the home button the completion block will tell us false. If it is false we can capture the date and find out the time difference and calculate the percent of completion. then we can use the percentage to determine the amount of time left.
Hope that helps

Resources