Showing posts with label uiviewanimation. Show all posts
Showing posts with label uiviewanimation. Show all posts

Thursday, March 30, 2017

Break a loop inside a animation block

Leave a Comment

I'm trying to break a for-loop after a completed UIView animation. Here is the following snippet:

public func greedyColoring() {     let colors = [UIColor.blue, UIColor.green, UIColor.yellow, UIColor.red, UIColor.cyan, UIColor.orange, UIColor.magenta, UIColor.purple]      for vertexIndex in 0 ..< self.graph.vertexCount {         let neighbours = self.graph.neighborsForIndex(vertexIndex)         let originVertex = vertices[vertexIndex]          print("Checking now Following neighbours for vertex \(vertexIndex): \(neighbours)")          var doesNotMatch = false          while doesNotMatch == false {             inner: for color in colors{                 UIView.animate(withDuration: 1, delay: 2, options: .curveEaseIn, animations: {                     originVertex.layer.backgroundColor = color.cgColor                 }, completion: { (complet) in                     if complet {                         let matches = neighbours.filter {                             let vertIdx = Int($0)!                              print("Neighbour to check: \(vertIdx)")                              let vertex = self.vertices[vertIdx-1]                              if vertex.backgroundColor == color{                                 return true                             }else{                                 return false                             }                         }                          //print("there were \(matches.count) matches")                          if matches.count == 0 {                             // do some things                             originVertex.backgroundColor = color                             doesNotMatch = true                             break inner                         } else {                             doesNotMatch = false                         }                     }                 })             }         }     } } 

Basically this method iterate over a Graph and checks every vertex and its neighbours and give the vertex a color that none of its neighbours has. Thats why it has to break the iteration at the first color that hasn't been used. I tried to use Unlabeled loops but it still doesn't compile (break is only allowed inside a loop). The problem is that I would like to visualise which colors have been tested. Thats why I'm using the UIView.animate()

Is there anyway to solve my problem?

4 Answers

Answers 1

You need to understand, that the completion block that you pass to the animate function is called after the animation has finished, which is a long time (in computer time) after your for loop has iterated through the colors array. You set the duration to be 1 second, which means that the completion is called 1 second later. Since the for loop isn't waiting for your animation to finish, they will all start animating at the same time (off by some milliseconds perhaps). The for loop has completed way before the animation has completed, which is why it doesn't make sense to break the for loop, since it is no longer running!

If you want to see this add a print("Fire") call right before the UIView.animate function is called and print("Finished") in the completion block. In the console you should see all the fire before all the finished.

You should instead queue the animations, so that they start and finish one after the other.

Answers 2

As Frederik mentioned, the animations the order in execution of the next in your for loop is not synchronous with the order of your completions. This means that the for loop will keep cycling no matter of your blocks implementations. An easy fix would be to create the animations by using CABasicAnimation and CAAnimationGroup. So that the product of your for loop would be a chain of animations stacked in an animation group.

This tutorial will give you an idea on how to use CABasicAnimation and CAAnimationGroup: https://www.raywenderlich.com/102590/how-to-create-a-complex-loading-animation-in-swift

You can use this approach, because the condition to break your for loop is given by parameters that are not dependent by the animation itself. The animations will be executed anyway once they will be attached to the view you are trying to animate.

Hope this helps.

Answers 3

Just modifying your code a little bit. Its a old school recursion but should work. Assuming all the instance variables are available here is a new version.      public func greedyColoring(vertex:Int,colorIndex:Int){        if  vertexIndex > self.graph.vertexCount { return }         if colorIndex > color.count {return}         let neighbours = self.graph.neighborsForIndex(vertexIndex)         let originVertex = vertices[vertexIndex]         let color = self.colors[colorIndex]       UIView.animate(withDuration: 1, delay: 2, options: .curveEaseIn,   animations: {             originVertex.layer.backgroundColor = color.cgColor         }, completion: { (complet) in             if complet {                 let matches = neighbours.filter {                     let vertIdx = Int($0)!                      print("Neighbour to check: \(vertIdx)")                      let vertex = self.vertices[vertIdx-1]                      //No idea what you are trying to do here. So leaving as it is.                     if vertex.backgroundColor == color{                         return true                     }else{                         return false                     }                 }                  //print("there were \(matches.count) matches")                  if matches.count == 0 {                     // do some things                     originVertex.backgroundColor = color                     greedyColoring(vertex: vertex++,colorIndex:0)                 } else {                     greedyColoring(vertex: vertex,colorIndex: colorIndex++)                 }             }         })     }   Now we can call this function simply      greedyColor(vertex:0,colorIndex:0) 

Answers 4

As mentioned before, the completion blocks are all called asynchronously, and thus do not exist within the for loop. Your best bet is to call a common method from within the completion blocks which will ignore everything after the first call.

var firstMatch: UIColor?  func foundMatch (_ colour: UIColor) {     if firstMatch == nil     {         firstMatch = colour         print (firstMatch?.description ?? "Something went Wrong")     } }   let colours: [UIColor] = [.red, .green, .blue]  for colour in colours {     UIView.animate(withDuration: 1.0, animations: {}, completion:         { (success) in             if success { foundMatch (colour) }         }) } 
Read More

Thursday, April 14, 2016

UIView.animateWithDuration Not Animating Swift (again)

Leave a Comment

Note: I’ve already checked the following stack overflow issues:

27907570, 32229252, 26118141, 31604300

All I am trying to do is fade animate in a view (by alpha) when called by an IBAction attached to a button. Then reverse when a button on the view is hit.

My wrinkle may be that I'm using a secondary view that is on the ViewDock in the storyboard View. The view is added to the subview at the time of viewDidLoad where the frame/bounds are set to the same as the superview (for a full layover)

The reason this is done as an overlay view since it is a tutorial indicator.

The result (like many others who've listed this problem) is that the view (and contained controls) simply appears instantly and disappears as instantly. No fade.

I have tried animationWithDuration with delay, with and without completion, with transition, and even started with the old UIView.beginAnimations.

Nothing is working. Suggestions warmly welcomed.

The code is about as straight forward as I can make it:
Edit: Expanded the code to everything relevant
Edit2: TL;DR Everything works with the exception of UIViewAnimateWithDuration which seems to ignore the block and duration and just run the code inline as an immediate UI change. Solving this gets the bounty

@IBOutlet var infoDetailView: UIView! // Connected to the view in the SceneDock  override func viewDidLoad() {     super.viewDidLoad()      // Cut other vDL code that isn't relevant      setupInfoView() }  func setupInfoView() {     infoDetailView.alpha = 0.0     view.addSubview(infoDetailView)     updateInfoViewRect(infoDetailView.superview!.bounds.size) }  func updateInfoViewRect(size:CGSize) {     let viewRect = CGRect(origin: CGPointZero, size: size)      infoDetailView.frame = viewRect     infoDetailView.bounds = viewRect      infoDetailView.layoutIfNeeded()     infoDetailView.setNeedsDisplay() }  override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {     super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)     updateInfoViewRect(size) }  func hideInfoView() {     AFLog.enter(thisClass)     UIView.animateWithDuration(         2.0,         animations:         {             self.infoDetailView.alpha = 0.0         },         completion:         { (finished) in             return true         }     )     AFLog.exit(thisClass) }  func showInfoView() {     AFLog.enter(thisClass)     UIView.animateWithDuration(         2.0,         animations:         {             self.infoDetailView.alpha = 0.75         },         completion:         { (finished) in             return true         }     )     AFLog.exit(thisClass) }  // MARK: - IBActions  @IBAction func openInfoView(sender: UIButton) {     showInfoView() }  @IBAction func closeInfoView(sender: UIButton) {     hideInfoView() } 

Please note, I started with the following:

func showInfoView() {     UIView.animateWithDuration(2.0, animations: { () -> Void in         self.infoDetailView.alpha = 0.75     }) }  func hideInfoView() {     UIView.animateWithDuration(2.0, animations: { () -> Void in         self.infoDetailView.alpha = 0.00     }) } 

7 Answers

Answers 1

There are several strange things I can see,

first, remove:

infoDetailView.layoutIfNeeded() infoDetailView.setNeedsDisplay() 

Usually you don't need to call those methods manually unless you know exactly what you are doing.

Also, when you are changing the size:

infoDetailView.frame = viewRect infoDetailView.bounds = viewRect 

You never need to set both bounds and frame. Just set frame.

Also, you should probably make sure that the view actually doesn't ignore the frame by setting:

infoDetailView.translatesAutoresizingMaskIntoConstraints = true 

Instead of resetting the frame, just set autoresize mask:

infoDetailView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight] 

Resulting in:

override func viewDidLoad() {     super.viewDidLoad()      // Cut other vDL code that isn't relevant      setupInfoView() }  func setupInfoView() {     infoDetailView.alpha = 0.0     infoDetailView.translatesAutoresizingMaskIntoConstraints = true     infoDetailView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]     infoDetailView.frame = view.bounds     view.addSubview(infoDetailView) }  func hideInfoView() {   ... } 

I think this should actually help because immediate animations are often connected to size problems.

If the problem persists, you should check whether the infoDetailView in your animation is the same object as the infoDetailView you are adding to the controller.

Answers 2

Use this code:

UIView.animateWithDuration(0.3, animations: { () -> Void in     self.infoDetailView.alpha = 0.0  }) 

Answers 3

Make sure infoDetailView's opaque is false.

https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/#//apple_ref/occ/instp/UIView/opaque

This property provides a hint to the drawing system as to how it should treat the view. If set to true, the drawing system treats the view as fully opaque, which allows the drawing system to optimize some drawing operations and improve performance. If set to false, the drawing system composites the view normally with other content. The default value of this property is true.

Answers 4

Try Below code. Just play with alpha and duration time to perfect it.

Hide func

func hideInfoView() { AFLog.enter(thisClass) UIView.animateWithDuration(     2.0,     animations:     {         self.infoDetailView.alpha = 0.8     },     completion:     { (finished) in         UIView.animateWithDuration(     2.0,     animations:     {         self.infoDetailView.alpha = 0.4     },     completion:     { (finished) in         self.infoDetailView.alpha = 0.0     } )     } ) AFLog.exit(thisClass) } 

Show func

func showInfoView() { AFLog.enter(thisClass) UIView.animateWithDuration(     2.0,     animations:     {         self.infoDetailView.alpha = 0.3     },     completion:     { (finished) in         UIView.animateWithDuration(     2.0,     animations:     {         self.infoDetailView.alpha = 0.7     },     completion:     { (finished) in         self.infoDetailView.alpha = 1.0     } )     } ) AFLog.exit(thisClass) } 

Answers 5

If the animation does not seem to execute then consider examining the state of each of your views, before you enter the animation block. For example, if the alpha is already set to 0.4 then the animation that adjusts your view alpha, will complete almost instantly, with no apparent effect.

Consider using a keyframe animation instead. This is what a shake animation in objective c looks like.

+(CAKeyframeAnimation*)shakeAnimation {     CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];     animation.values = @[[NSValue valueWithCATransform3D:CATransform3DMakeTranslation(-10.0, 0.0, 0.0)],                          [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(10.0, 0.0, 0.0)]];     animation.autoreverses = YES;     animation.repeatCount = 2;     animation.duration = 0.07;     return animation; } 

Here is a post that shows you how to adjust alpha with keyframes http://stackoverflow.com/a/18658081/1951992

Answers 6

I've replicated your code and it work well, it's all ok. Probably you must control constraints, IBOutlet and IBActions connections. Try to isolate this code into a new project if it's necessary.

Update: my code and my storyboard and project folder photo:

my storyboard and project folder photo

Every object (view and buttons) are with default settings.

enter image description here

I've commented all AFLog lines (probably it's only any more "verbose mode" to help you) , the rest of your code is ok and it do what do you aspected from it, if you press open button the view fade in, and when you tap close button the view fade out.

PS Not relevant but i'm using xCode 7.3 , a new swift 2.2 project.

Answers 7

If you infoDetailView is under auto layout constraints you need to call layoutIfNeeded on the parent view inside animateWithDuration:

func showInfoView() {     self.view.layoutIfNeeded() // call it also here to finish pending layout operations     UIView.animateWithDuration(2.0, animations: { () -> Void in         self.infoDetailView.alpha = 0.75         self.view.layoutIfNeeded()     }) } 

Theoretically this should not be needed if you just change the .alpha value, but maybe this could be the problem in this case.

Read More