Paused animation finishes when an app is in background - ios

I have a view that im moving using this
func r() {
movel.constant = self.view.bounds.width - self.move.bounds.width
move.setNeedsLayout()
UIView.animate(withDuration: time, delay: 0, options:[], animations: {
self.move.superview?.layoutIfNeeded()
}, completion: {(finished: Bool) -> Void in
if (finished == true){
self.timerand()
self.l()
}else{
self.view.layoutIfNeeded()
}
})
}
And I'm using these functions to pause and resume it
func movestop() {
pausedmovetime = self.move.layer.convertTime(CACurrentMediaTime(), from: nil)
self.move.layer.speed = 0.0
self.move.layer.timeOffset = pausedmovetime
}
func movestart() {
self.move.layer.speed = 1.0
self.move.layer.timeOffset = 0.0
self.move.layer.beginTime = 0.0
let timeSincePause = self.move.layer.convertTime(CACurrentMediaTime(), from: nil) - pausedmovetime
self.move.layer.beginTime = timeSincePause
}
The app works fine but when I go out of the app I call the movestop() but when I come back it is at its final position and the completion block doesnt get called

Related

Back button starts a UIViewPropertyAnimator?

I have a ViewController with a camera for recording videos. On top there is a spinning circle to indicate that the video is being recorded. This is setup like so:
class CameraViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
private var animator: UIViewPropertyAnimator?
#objc func handleTap(_ gesture:UITapGestureRecognizer) {
if animator == nil {
createAnimation()
}
startRecording()
}
private func createAnimation() {
animator = UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 4, delay: 0, options: [.curveLinear,.allowUserInteraction], animations: {
UIView.animateKeyframes(withDuration: 4, delay: 0, animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1.0 / 3.0) {
self.recordingSpinner.transform = .init(rotationAngle: .pi * 2 * 1 / 3)
}
UIView.addKeyframe(withRelativeStartTime: 1.0 / 3.0, relativeDuration: 1.0 / 3.0) {
self.recordingSpinner.transform = .init(rotationAngle: .pi * 2 * 2 / 3)
}
UIView.addKeyframe(withRelativeStartTime: 2.0 / 3.0, relativeDuration: 1.0 / 3.0) {
self.recordingSpinner.transform = .identity
}
})
}, completion: { [weak self] _ in
self?.createAnimation()
})
}
func startRecording() {
if movieOutput.isRecording == false {
animator?.startAnimation()
let connection = movieOutput.connection(with: AVMediaType.video)
if (connection?.isVideoOrientationSupported)! {
connection?.videoOrientation = currentVideoOrientation()
}
if (connection?.isVideoStabilizationSupported)! {
connection?.preferredVideoStabilizationMode = AVCaptureVideoStabilizationMode.auto
}
let device = activeInput.device
if (device.isSmoothAutoFocusSupported) {
do {
try device.lockForConfiguration()
device.isSmoothAutoFocusEnabled = false
device.unlockForConfiguration()
} catch {
print("Error setting configuration: \(error)")
}
}
let outputFileName = NSUUID().uuidString
let outputFilePath = (NSTemporaryDirectory() as NSString).appendingPathComponent((outputFileName as NSString).appendingPathExtension("mov")!)
movieOutput.startRecording(to: URL(fileURLWithPath: outputFilePath), recordingDelegate: self)
}
else {
stopRecording()
}
}
func stopRecording() {
if movieOutput.isRecording == true {
animator?.pauseAnimation()
movieOutput.stopRecording()
}
}
#IBAction func unwindToCamera(sender: UIStoryboardSegue) {
}
...
}
extension CameraViewController: AVCaptureFileOutputRecordingDelegate{
func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
if (error != nil) {
print("Error recording movie: \(error!.localizedDescription)")
} else {
self.footageURL = outputFileURL as URL
//print(self.videoRecorded!)
self.performSegue(withIdentifier: "TrimFootage_Segue", sender: nil)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
if segue.identifier == "TrimFootage_Segue" {
let controller = segue.destination as! TrimFootageViewController
controller.footageURL = self.footageURL
}
}
}
So it create an animator if it doesn't exist and then calls startRecording which starts the animation. then stopRecording stops it. Then when the video finishes recording to an output file, it segues to a newView controller. When I press back on that view controller it uses an unwind segue - unwindToCameraWithSender:
When I unwind and come back to the camera, the video is not recording, but the animation is playing. What could have caused this animation to start again? How can I prevent this?
I think the animation only being paused is the reason. In the stopRecording() method try
animator?.stopAnimation(true)
instead of
animator?.pauseAnimation()
So one thing I did to get around this, but not fix it is using the UIDynamicAnimator like so:
#objc func handleTap(_ gesture:UITapGestureRecognizer) {
startRecording()
if let rotate = rotate{
animator.removeBehavior(rotate)
self.rotate = nil
} else {
rotate = UIDynamicItemBehavior(items: [self.recordingSpinner])
rotate?.allowsRotation = true
rotate?.angularResistance = 0
rotate?.addAngularVelocity(1, for: self.recordingSpinner)
animator.addBehavior(rotate!)
}
}
taken from this answer: Proper way to stop an infinitely rotating image? and how does one implement removeAllAnimations?
Interestingly it seems not to start the rotation when I perform the segue though I'm not sure why. If anyone has thoughts on why I would love to hear them

When is the destination view controller initialized during a segue?

I'm attempting to write a protocol and a custom UIStoryboardSegue class that will allow me to easily implement custom transitions in my UIViewControllers:
public protocol TransitionController
{
var transitionDurationIn: CFTimeInterval { get }
var transitionDurationOut: CFTimeInterval { get }
func prepareTransitionIn()
func prepareTransitionOut()
func performTransitionIn(finished: #escaping () -> Void)
func performTransitionOut(finished: #escaping () -> Void)
}
class JFTransitionControllerSegue: UIStoryboardSegue {
override func perform() {
let defaultTransitionDuration : CFTimeInterval = 1.5
if let dvc = self.destination as? TransitionController {
dvc.prepareTransitionIn()
}
else {
// Default transition
self.destination.view.alpha = 0
}
if let svc = self.source as? TransitionController {
svc.prepareTransitionOut()
svc.performTransitionOut(){ () in
if let dvc = self.destination as? TransitionController {
dvc.performTransitionIn(){ () in
self.source.present(self.destination, animated: false, completion: nil)
}
}
else {
// Default transition for the destination controller
UIView.animate(withDuration: defaultTransitionDuration, animations: {
self.destination.view.alpha = 1
}) { (Finished) in
self.source.present(self.destination, animated: false, completion: nil)
}
}
}
}
else
{
// Default transition for the source controller
UIView.animate(withDuration: defaultTransitionDuration, animations: {
self.source.view.alpha = 0
}) { (Finished) in
if let dvc = self.destination as? TransitionController {
dvc.performTransitionIn(){ () in
self.source.present(self.destination, animated: false, completion: nil)
}
}
else {
// Default transition for the destination controller
UIView.animate(withDuration: defaultTransitionDuration, animations: {
self.destination.view.alpha = 1
}) { (Finished) in
self.source.present(self.destination, animated: false, completion: nil)
}
}
}
}
}
}
class TestController: UIViewController, TransitionController {
#IBOutlet weak var form_username: UITextField!
// MARK: - TransitionController Protocol
var transitionDurationIn : CFTimeInterval {return 1.0}
var transitionDurationOut : CFTimeInterval {return 1.0}
func prepareTransitionIn()
{
//self.view.alpha = 0 // no fade in if you uncomment
form_username.alpha = 0 // nil
}
func prepareTransitionOut()
{
self.view.alpha = 1 // works
}
func performTransitionIn(finished: #escaping () -> Void)
{
UIView.animate(withDuration: self.transitionDurationIn, animations: {
//self.view.alpha = 1 // no fade in if you uncomment
self.form_username.alpha = 1 // nil, crashes
}) { (Finished) in
finished()
}
}
func performTransitionOut(finished: #escaping () -> Void)
{
UIView.animate(withDuration: self.transitionDurationOut, animations: {
self.view.alpha = 0 // fades out correctly
}) { (Finished) in
finished()
}
}
}
Basically, you just implement the protocol in any UIViewController you want, then make a segue of class JFTransitionControllerSegue. In the performTransitionIn function, you can just do something like UIView.animate and change the alpha or whatever you like. The problem I'm having is that the destination segue simply pops up instead of transitioning in properly. From what I can tell while debugging it isn't fully initialized - IBOutlet variables are nil, but the controller itself isn't. Is this a bad design pattern, or am I just missing something simple?
A view controller being initialised is one event. It's view being loaded is another.
The view property of a view controller is loaded lazily and the outlets are built and connected at that point. That's why viewDidLoad() is a thing.
If you want the view to be ready for you, you can call loadViewIfNeeded() on the view controller first.
Misdiagnosed the problem...the destination controller was loaded, but I had forgotten to add the destination controller's view to the window in the Segue class:
class JFTransitionControllerSegue: UIStoryboardSegue {
override func perform() {
let defaultTransitionDuration : CFTimeInterval = 1.5
if let dvc = self.destination as? TransitionController {
dvc.prepareTransitionIn()
}
else {
// Default transition
self.destination.view.alpha = 0
}
if let svc = self.source as? TransitionController {
svc.prepareTransitionOut()
svc.performTransitionOut(){ () in
UIApplication.shared.keyWindow?.insertSubview(self.destination.view, aboveSubview: self.source.view)
if let dvc = self.destination as? TransitionController {
dvc.performTransitionIn(){ () in
self.source.present(self.destination, animated: false, completion: nil)
}
}
else {
// Default transition for the destination controller
UIView.animate(withDuration: defaultTransitionDuration, animations: {
self.destination.view.alpha = 1
}) { (Finished) in
self.source.present(self.destination, animated: false, completion: nil)
}
}
}
}
else
{
// Default transition for the source controller
UIView.animate(withDuration: defaultTransitionDuration, animations: {
self.source.view.alpha = 0
}) { (Finished) in
UIApplication.shared.keyWindow?.insertSubview(self.destination.view, aboveSubview: self.source.view)
if let dvc = self.destination as? TransitionController {
dvc.performTransitionIn(){ () in
self.source.present(self.destination, animated: false, completion: nil)
}
}
else {
// Default transition for the destination controller
UIView.animate(withDuration: defaultTransitionDuration, animations: {
self.destination.view.alpha = 1
}) { (Finished) in
self.source.present(self.destination, animated: false, completion: nil)
}
}
}
}
}
}

Unbalanced calls to begin/end appearance transitions with segmented control

So, I have a segmented control to switch between to view controllers.
However, often, as soon as I switch, I get this message:
Unbalanced calls to begin/end appearance transitions
Plus, after I get this, sometimes it happens that every object on the view disappear.
Here's the code:
func segmentValueChanged(sender: AnyObject) {
if sender.selectedIndex == 0 {
let newController = controllers.newController1
let oldController = childViewControllers.last as UIViewController!
self.container.frame.size.height = newController.view.frame.height
oldController.willMoveToParentViewController(nil)
addChildViewController(newController)
transitionFromViewController(oldController, toViewController: newController, duration: 0.9, options: .CurveLinear, animations:{ () -> Void in
// nothing needed here
}, completion: { (finished) -> Void in
oldController.removeFromParentViewController()
newController.didMoveToParentViewController(self)
})
} else if sender.selectedIndex == 1 {
let newController = controllers.newController2
let oldController = childViewControllers.last as UIViewController!
oldController.willMoveToParentViewController(nil)
addChildViewController(newController)
transitionFromViewController(oldController, toViewController: newController, duration: 0.9, options: .CurveLinear, animations:{ () -> Void in
// nothing needed here
}, completion: { (finished) -> Void in
oldController.removeFromParentViewController()
newController.didMoveToParentViewController(self)
})
}
}
How can I solve this?
This line is backwards:
self.container.frame.size.height = newController.view.frame.height
It is newController's view's frame that you need to set!
Moreover, you are failing to put newController.view into the interface. That is why you end up without an interface.

How to set same speed to UIView animation

I want to set speed to my UISlider, to make it move more smoothly.
Here is how I am trying to animate it :
UIView.animateWithDuration(6, delay: 0.0, options: UIViewAnimationOptions.TransitionCurlUp, animations: { () -> Void in
self.videoSlider.setValue(time, animated: true)
if self.videoSlider.value == self.videoSlider.maximumValue {
self.playerLayer.stop()
self.playBigButton.hidden = false
self.pauseButton.hidden = true
self.playButton.hidden = false
}
}, completion: { (finished: Bool) -> Void in
})
But it moves with a different speed on the beginning, middle and end.
Try giving your animation the option of .CurveLinear, like this:
UIView.animateWithDuration(6,
delay: 0.0,
options: [UIViewAnimationOptions.TransitionCurlUp, .CurveLinear],
animations: { () -> Void in
self.videoSlider.setValue(time, animated: true)
if self.videoSlider.value == self.videoSlider.maximumValue {
self.playerLayer.stop()
self.playBigButton.hidden = false
self.pauseButton.hidden = true
self.playButton.hidden = false
}
}, completion: { (finished: Bool) -> Void in
})

Create a UIProgressView to go from 0.0 (empty) to 1.0 (full) in 3 seconds

Basically I want to create a UIProgressView to go from 0.0 (empty) to 1.0 (full) in 3 seconds. Could anyone point me in the right direction for using NSTimer in swift with UIProgressView?
If anyone's interested here is a Swift 5 working version :
extension UIProgressView {
#available(iOS 10.0, *)
func setAnimatedProgress(progress: Float = 1, duration: Float = 1, completion: (() -> ())? = nil) {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { (timer) in
DispatchQueue.main.async {
let current = self.progress
self.setProgress(current+(1/duration), animated: true)
}
if self.progress >= progress {
timer.invalidate()
if completion != nil {
completion!()
}
}
}
}
}
Usage :
// Will fill the progress bar in 70 seconds
self.progressBar.setAnimatedProgress(duration: 70) {
print("Done!")
}
I created my own solution after searching for one and coming across this question.
extension UIProgressView {
func setAnimatedProgress(progress: Float,
duration: NSTimeInterval = 1,
delay: NSTimeInterval = 0,
completion: () -> ()
) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
sleep(UInt32(delay))
dispatch_async(dispatch_get_main_queue()) {
self.layer.speed = Float(pow(duration, -1))
self.setProgress(progress, animated: true)
}
sleep(UInt32(duration))
dispatch_async(dispatch_get_main_queue()) {
self.layer.speed = 1
completion()
}
}
}
}
Declare the properties:
var time = 0.0
var timer: NSTimer
Initialize the timer:
timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector:Selector("setProgress"), userInfo: nil, repeats: true)
Implement the setProgress function:
func setProgress() {
time += 0.1
dispatch_async(dispatch_get_main_queue(), {
progressView.progress = time / 3
})
if time >= 3 {
timer.invalidate()
}
}
(I'm not 100% sure if the dispatch block is necessary, to make sure the UI is updated in the main thread. Feel free to remove this if it isn't necessary.)

Resources