Completion Block for 360 Degree Rotation Animate With Duration - ios

Hi I am trying to get this to complete the 360 turn on an infinite loop by using the completion recalling the function. Everything seems to be working but the function is not called again a second time. I tried many types of completion formats which all print it is called however the actual recalling of the function is not working. Am I missing something? Thanks
func rotateBackground()
{
UIView.animateWithDuration(5, delay: 0, options: .CurveLinear, animations: {
self.backgroundImage.transform = CGAffineTransformMakeRotation((180.0 * CGFloat(M_PI)) / 180.0)
}, completion: { (value: Bool) -> Void in
self.rotateBackground()
}
)
}

Instead of calling the method again in your completion block, try adding ".Repeat" into your animation options.

Related

How to update UI after delay in swift

DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5) { animations() }
I make changes with UI (in main thread) and I need to delay further animations for a while and then execute them (synchronously). Code above works, but with "This application is modifying the autolayout engine from a background thread after the engine was accessed from the main thread. This can lead to engine corruption and weird crashes" error.
Thanks for help!
You can make a delay like this
UIView.animate(withDuration: 0.8, delay: 0.5, options: [], animations: {
/// animations here
}, completion: nil)

Calling swift animation from completion event?

I am trying to call an animation event on a FBSDKLoginButton in swift. I can tell the animation is being call, because the elements that are changing alpha values function correctly. For some reason though, the button will not move in the same call.
If I touch another button, calling the exact same function, the button then moves. Not sure why calling a function from a completion event vs. another location would affect whether or not the object actually moves?!
Under the BTN_Animate, that where the call works. After the
LocalProfile.sharedInstance.updateFromFacebook(updateFromFacebook_Complete:
call you can see the same function, and the argument being passed is assumed true.
#IBAction func BTN_Animate(_ sender: Any) {
animateScreen(loggedIn: true) //Call that works
}
public func loginButton(_ BTN_facebookLogin: FBSDKLoginButton!, didCompleteWith result: FBSDKLoginManagerLoginResult!, error: Error!) {
print("> Attempting Login from Main View Controller")
if ((error) != nil)
{
print("Error: ")
// Process error
print(error)
}
else if result.isCancelled {
// Handle cancellations
print("Request cancelled")
print(result)
}
else {
// If you ask for multiple permissions at once, you
// should check if specific permissions missing
LocalProfile.sharedInstance.updateFromFacebook(updateFromFacebook_Complete: {
self.LBL_Name.text = LocalProfile.sharedInstance.firstName
self.IMG_ProfilePicture.image = LocalProfile.sharedInstance.profilePicture
LocalProfile.sharedInstance.printAllData()
self.animateScreen(loggedIn: LocalProfile.sharedInstance.loggedIn) //Call that doesn't work
})
}
}
public func animateScreen(loggedIn: Bool) {
if(loggedIn) {
//Animating screen objects
print("> Animating screen objects")
UIView.animate(withDuration: 0.7, delay: 1.0, options: UIViewAnimationOptions.curveEaseOut, animations: {
self.BTN_facebookLogin.frame = CGRect(x: self.BTN_facebookLogin.frame.origin.x, y: (self.view.bounds.height - self.BTN_facebookLogin.frame.size.height) - 50, width: self.BTN_facebookLogin.frame.size.width, height: self.BTN_facebookLogin.frame.size.height)
self.IMG_ProfilePicture.alpha = 1
self.LBL_Name.alpha = 1
self.view.layoutIfNeeded()
}, completion: { finished in
print("> Done animating screen objects")
})
}
else {
//Not animating
print("> Not animating screen objects")
}
}
Any help here would be appreciated! Thanks
EDIT: Below code returns that it IS the main thread...
LocalProfile.sharedInstance.updateFromFacebook(updateFromFacebook_Complete: {
self.LBL_Name.text = LocalProfile.sharedInstance.firstName
self.IMG_ProfilePicture.image = LocalProfile.sharedInstance.profilePicture
LocalProfile.sharedInstance.printAllData()
self.animateScreen(loggedIn: LocalProfile.sharedInstance.loggedIn)
let notString = Thread.isMainThread ? "" : "not "
print("This is " + notString + "the main thread")
})
Work around:
Try to update your buttons frame in the main queue. In swift 3.0 you can do as bellow:
DispatchQueue.main.async
{
self.BTN_facebookLogin.frame = CGRect(x: self.BTN_facebookLogin.frame.origin.x, y: (self.view.bounds.height - self.BTN_facebookLogin.frame.size.height) - 50, width: self.BTN_facebookLogin.frame.size.width, height: self.BTN_facebookLogin.frame.size.height)
self.IMG_ProfilePicture.alpha = 1
self.LBL_Name.alpha = 1
self.view.layoutIfNeeded()
}
As others have said, you must make UI calls from the main thread. I'm not familiar with Facebook's APIs, so I don't know if the completion block you're using is called on the main thread or not. You can check with code like this:
let notString = Thread.isMainThread ? "" : "not "
print("This is " + notString + "the main thread")
(That's Swift 3 code)
Put that at the top of your completion block and see what it displays to the console.
My bet is that the completion block is NOT being run on the main thread.
The most common effect of doing UI calls from a background thread is that nothing happens, or it takes a VERY long time to take effect. However, it can also cause other strange effects and even crashes, so it's important not to do it.
You might also be having problems with auto-layout interfering with your frame settings, but based on the symptoms you describe (not working from the completion block but working if you call it directly) it sounds more like a threading problem.
Any time you deal with a completion block you should figure out if the completion block might be called from a background thread. Apple's URLSession class is an example of a class that calls it's completion blocks on background threads. The docs should tell you if a completion block is invoked on a background thread, but the test code I posted above is a good way to be sure.
In contrast, the UIView animateWithDuarion(animations:completion:) family of methods call their completion blocks on the main thread.

How to do animation in 20 seconds later using swift? animateWithDuration:delay: is useless

UIView.animateWithDuration(10, delay: 20, options: .TransitionNone, animations: {
}) { (let finish) in
print("finish")
}
Like the code shown, i want to print "finish" after 20 seconds, however i get it immediately.
By the way, how many ways to do make operations delay?
First Method: NSTimer
func myDelayedFunction() {
print("delayed")
}
func myMainFunction() {
let timer = NSTimer.scheduledTimerWithTimeInterval(10, target: self, selector: "myDelayedFunction", userInfo: nil, repeats: false)
timer.fire()
}
Second Method: performSelector
func myDelayedFunction() {
print("delayed")
}
func myMainFunction() {
performSelector("myDelayedFunction", withObject: self, afterDelay: 10)
}
Third Method: GCD (useful if you want an anonymous function)
(adapted from this answer)
let delayInSeconds: Int64 = 3
let popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * Int64(NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue()) {
print("Delayed")
}
Fourth Method: sleep
func myMainFunction() {
sleep(10) //This will block whichever thread you're running on.
print("delayed")
}
I tend to use the first method, because I can always invalidate the timer if I need to (calling timer.invalidate). Each method has its use case, though.
if you dont actually need to do any animation, can use GCD to accomplish this
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (Int64)(20 * NSEC_PER_SEC)), dispatch_get_main_queue()){
print("finish")
};
you can make a neat little helper function as described in this answer as well
I think, there's a misconception. In your code:
UIView.animate(withDuration: 10, delay: 20, options: [], animations: {
// define animations
self.myView.alpha = 0 // as an example
}) { (finish) in
print("finish")
}
the completion handler will be called, when the animation finished. It starts after delay seconds (20.0) and takes duration seconds (10.0). That is, the animation should be completed after 30 seconds measured from the method call.
(You can place NSLog statements in your code to see when things happen)
I cannot reproduce what you are experiencing, that "finish" will be printed immediately. However, I can confirm that UIView.animateWithDuration works as expected - given the animation block is actually not empty.

apple watch - slow image animation first time

I'm building a small app for apple watch. I have a Group and a Label inside of it. What I'm trying to do is:
animate background image of the group
fade in label after image animation ends
My code looks essentially like this:
group.setBackgroundImageNamed("show_back-");
group.startAnimatingWithImagesInRange(NSMakeRange(0, 39), duration: 1.5, repeatCount: 1);
let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(1.5 * Double(NSEC_PER_SEC)))
dispatch_after(delayTime, dispatch_get_main_queue()) { () -> Void in
self.animateWithDuration(1) { () -> Void in
self.label.setAlpha(1)
};
};
The problem is that the first time this sequence is triggered, the image animation seems to run slower than 1.5 seconds, because the label starts fading in earlier than the images stop changing. If this is triggered again while the app is running, everything works as expected. I guess it has something to do with images preloading or something.
How can I make it work consistently? I couldn't find any sort of callback on image sequence animation end to subscribe to.
EDIT
Another problem I've noticed: I have another case when bg is animated from a dispatch_after block, and when I leave the app by tapping the crown and return by double-tapping it, either the dispatch_after block is not triggered, or the background animation is not rendered correctly the first time it is invoked (I think the second, because I tried adding a breakpoint into the dispatch block and it triggered every time I tested).
I'm running watchOS2, so maybe it is related to the beta state the OS is currently in?
I ran into the same issue as you.
This happens because on the first time you try it, the watch takes time to load the images. Also apple doesn't give us any 'pre load' method, so I came up with a little work around it:
When my controller will be displayed:
func willActivate()
I play the animation sequence once in a background tread, this way when my user clicks on it the images are already loaded.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { [weak self] in
if let uSelf = self {
uSelf.statusAnimationImage.setImageNamed("my image name")
uSelf.statusAnimationImage.startAnimatingWithImagesInRange(NSMakeRange(0, 359), duration: 0.5, repeatCount: 1)
}
}
That was the best way I found to solve this problem and it works for me.
try doing
group.setBackgroundImageNamed("show_back-");
let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(1.5 * Double(NSEC_PER_SEC)))
dispatch_after(delayTime, dispatch_get_main_queue()) { () -> Void in
self.animateWithDuration(1) { () -> Void in
group.startAnimatingWithImagesInRange(NSMakeRange(0, 39), duration: 1.5, repeatCount: 1);
self.label.setAlpha(1)
};
};
I'm not exactly sure what you're doing but also try doing animateWithDuration(0) or (1.5)

Game sometimes crashes when removing CADisplayLink from run loop

This doesn't happen every time you play the game, maybe once for every 5 or 10 plays. When the game ends, I remove my CADisplayLink (which I use to animate the playing area, a bit like the pipes in Flappy Bird) from the run loop. However, on the few occasions, it crashes on that line. Next to the line, it has:
Thread 1: EXC_BAD_ACCESS (code=1, address=0x10)
This is the code:
func endGame(r : String) {
UIView.animateWithDuration(0.4, delay: 0.2, options: .CurveLinear, animations: {
self.scoreLabel.alpha = 0
}, completion: {
(finished: Bool) in
self.scoreLabel.removeFromSuperview()
});
self.view.userInteractionEnabled = false
reason = r
println("Game Over!!!")
//Crashes on this line
blockUpdateDisplayLink.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes)
shiftDisplayLink.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes)
scoreTimer.invalidate()
UIView.animateWithDuration(0.0001, delay: 0.7, options: .CurveLinear, animations: {
}, completion: {
(finished: Bool) in
self.performSegueWithIdentifier("Game Over", sender: self)
});
}
if I comment out the first CADisplayLink part, it will just crash on the second anyway.
This is the stacktrace:
Which has the same "Thread 1" error as above.
What is going on??
You must invalidate display links (rather than just removing them from the run loop) because if they are in the middle of running when they activate they may still try to use their associated run loop.
Which run loop are you adding the CADisplayLink to? You might need to be using NSRunLoop.mainRunLoop() instead.
Also, if your CADisplayLink is only being added on one NSRunLoop, you could try calling blockUpdateDisplayLink.invalidate() instead of removing it.
If you post the code where you create the CADisplayLink objects, it will make it easier to track down the issue.
I will really suggest you to debug you're code with Instruments and NSZombies and find out your memory issue problem.
Instruments
NSZombies

Resources