Maysam
Maysam

Reputation: 7367

How to make sure that completion will be called in animateWithDuration()

I have a function which switches between UICollectionView:

func toggleCollectionView(target: NSObject, targetName:String){
    self.view.userInteractionEnabled = false
    if let tempTarget = target as? UICollectionView {
        //if selected item is same as active one, won't do anything
        if(targetName != activeToolbarName){

            tempTarget.hidden = false
            tempTarget.frame.origin.y = screenSize.height
            UIView.animateWithDuration(0.4, delay: 0.0, options: .CurveEaseOut, animations: {
                if let tempActiveToolbar  = self.activeToolbar as? UICollectionView {
                    tempActiveToolbar.frame.origin.y = self.screenSize.height
                }

                tempTarget.frame.origin.y = self.screenSize.height - tempTarget.frame.height - self.selectorsContainer.frame.height
                }, completion: { finished in
                    if let tempActiveToolbar  = self.activeToolbar as? UICollectionView {
                        tempActiveToolbar.hidden = true
                        self.activeToolbar = target
                        self.activeToolbarName = targetName
                        self.view.userInteractionEnabled = true

                    }

            })
        }

    }

}

It's triggered by several button on the screen like this:

@IBAction func showFontsTool(sender: UIBarButtonItem) {
   toggleCollectionView(fontsCV, targetName:"fontsCV")
}

If user tap on buttons very fast, completion block won't be called sometimes and self.view.userInteractionEnabled won't be enabled. How can I make sure completion block will be called always after starting animation?

Update

Fixed function which works fine:

func toggleCollectionView(target: NSObject, targetName:String){

    if let tempTarget = target as? UICollectionView {
        //if selected item is same as active one, won't do anything
        if(targetName != activeToolbarName){
            tempTarget.hidden = false
            tempTarget.frame.origin.y = screenSize.height

            if (runningAnimation == false){
                runningAnimation = true
                self.activeToolbarName = targetName
                UIView.animateWithDuration(0.4, delay: 0.0, options: .CurveEaseOut, animations: {
                    if let tempActiveToolbar  = self.activeToolbar as? UICollectionView {
                        tempActiveToolbar.frame.origin.y = self.screenSize.height
                    }

                    tempTarget.frame.origin.y = self.screenSize.height - tempTarget.frame.height - self.selectorsContainer.frame.height
                    }, completion: { finished in
                        if let tempActiveToolbar  = self.activeToolbar as? UICollectionView {
                            tempActiveToolbar.hidden = true
                            self.activeToolbar = target
                            self.runningAnimation = false                            }
                })
            }

        }

    }

}

Upvotes: 1

Views: 117

Answers (2)

Vizllx
Vizllx

Reputation: 9246

First, you must declare a Global Bool variable.

var isAnimating:BOOL = False

Now in button action,

@IBAction func showFontsTool(sender: UIBarButtonItem) {
   if(!isAnimating) //if animation is not happening then call the method
   {
     toggleCollectionView(fontsCV, targetName:"fontsCV")
   }
   else  //if animation is happening then return
   {
     return;
   }

}

Now in animation method i.e func toggleCollectionView ,set the value for isAnimating variable, like this :-

func toggleCollectionView(target: NSObject, targetName:String){
    isAnimating=True   //notify animation has started, so that this method don't get fire on button click
    self.view.userInteractionEnabled = false
    if let tempTarget = target as? UICollectionView {
        //if selected item is same as active one, won't do anything
        if(targetName != activeToolbarName){

            tempTarget.hidden = false
            tempTarget.frame.origin.y = screenSize.height
            UIView.animateWithDuration(0.4, delay: 0.0, options: .CurveEaseOut, animations: {
                if let tempActiveToolbar  = self.activeToolbar as? UICollectionView {
                    tempActiveToolbar.frame.origin.y = self.screenSize.height
                }

                tempTarget.frame.origin.y = self.screenSize.height - tempTarget.frame.height - self.selectorsContainer.frame.height
                }, completion: { finished in
                    if let tempActiveToolbar  = self.activeToolbar as? UICollectionView {
                        tempActiveToolbar.hidden = true
                        self.activeToolbar = target
                        self.activeToolbarName = targetName
                        self.view.userInteractionEnabled = true
                        isAnimating=False //now set bool value  on this completion handler to false so that this method can get fired again. 

                    }

            })
        }

    }

}

Upvotes: 1

Kelvin Lau
Kelvin Lau

Reputation: 6781

This is workaround:

Create a boolean variable animationTriggering that sets itself to true when your button is pressed. This variable can act as a flag for you to check; If it's true, don't execute the UIView animation a second time. You'll only execute the UIView animation when it's in the false state.

After the UIView animation completes, set it back to false so it can be triggered again next time.

I'm guessing the problem is due to multi-threaded issues. I've encountered this before and I got around it doing this, but I really have no confidence in claiming I know what the problem was.

If I could hazard a guess, it's that UIView animations have an internal asynchronous thread that is some how interfering with each other if you send it requests too quickly.

Upvotes: 1

Related Questions