0

I'm trying to get two UIViews to swap where they are in front by brining either one in front of another depending on a bool value before a animation happens, but doing this can mess up the animation. The animation simply swaps their positions, but sometimes it doesn't show this, and instead shows them both moving in opposite directions!

    if(self.takePhotoSelected){

        self.view.bringSubview(toFront: self.takePhotoBtn)

    }else{

        self.view.bringSubview(toFront: self.takeVideoBtn)

    }

    UIView.animate(withDuration: 0.25, delay: 0.1, options: [], animations: {

        let videoFrame = CGRect(x:self.takeVideoBtn.frame.origin.x, y: self.takeVideoBtn.frame.origin.y, width: self.takeVideoBtn.frame.width, height: self.takeVideoBtn.frame.height)
        let photoFrame = CGRect(x:self.takePhotoBtn.frame.origin.x, y: self.takePhotoBtn.frame.origin.y, width: self.takePhotoBtn.frame.width, height: self.takePhotoBtn.frame.height)

        self.takeVideoBtn.frame = photoFrame
        self.takePhotoBtn.frame = videoFrame

    }, completion: { (finished: Bool) in

    })

If I remove the bringSubview to front, there's no strange animation effect, and works fine, but it's not what I want. I want them to swap which one is in front before the animation even starts. I even added in a delay.. doesn't work.

Chewie The Chorkie
  • 3,401
  • 4
  • 37
  • 77
  • 1
    `CGRect` is a struct, so `let videoFrame = self.takeVideoBtn.frame` is all you need to do. – Samah Jan 24 '18 at 22:50
  • 1
    The `animations` block is always executed immediately; you can tell this, because the parameter is not flagged with `@escaping`. The `delay` simply tells UIKit when to start drawing the animation. You could try putting `UIView.setAnimationsEnabled(false)` before the `bringSubview(toFront:)`, and then re-enable it immediately afterward. – Samah Jan 24 '18 at 23:18
  • Good info Samah, but the disabling then enabling doesn't seem to work. – Chewie The Chorkie Jan 25 '18 at 16:06
  • @Samah Actually that's not true. The animation is _ordered_ immediately but it does not start _running_ until the CATransaction ends (i.e. your code finishes running). – matt Jan 25 '18 at 17:45

2 Answers2

1

Try adding your delay with my delay function:

if(self.takePhotoSelected) {
    self.view.bringSubview(toFront: self.takePhotoBtn)
} else {
    self.view.bringSubview(toFront: self.takeVideoBtn)
}
delay(0.1) {
    UIView.animate(withDuration: 0.25, delay: 0.1, options: [], animations: {
        // ...

The idea here is to give the render tree a chance to deal with the change in subview order before we start asking for the animation.

matt
  • 447,615
  • 74
  • 748
  • 977
  • 1
    Why not just use `DispatchQueue.main.asyncAfter(deadline: .now() + 0.1)` – Samah Jan 24 '18 at 22:41
  • 1
    @Samah No reason not to, I just find the `delay` utility a handy shorthand. – matt Jan 24 '18 at 22:50
  • 1
    I guess so, it just means that other people on the project need to be aware that `delay` always runs on the main dispatch queue. – Samah Jan 24 '18 at 22:53
  • There's a new problem this causes. Now both the buttons get switches before they even start animating to switch places. – Chewie The Chorkie Jan 25 '18 at 17:32
  • @VagueExplanation Can you post a minimal example project? I can't reproduce that based on the code you've shown. – matt Jan 25 '18 at 17:46
  • Maybe it has to do with the boolean values I'm changing? Here's a snippet of the take video vs take photo. takeVideoBtn and takePhotoBtn are UIButtons overlapping each other in interface builder. https://codepaste.net/vhkbyd – Chewie The Chorkie Jan 25 '18 at 18:12
  • 1
    Maybe, but there are no boolean values in the code in your question. Please post an actual runnable downloadable project that shows the issue. Perhaps this would be better as a new question? I believe the question _as you asked it_ has been answered (though of course you are free to decide otherwise). – matt Jan 25 '18 at 18:13
  • Yes I think it would be better suited as a new question. Thank you! – Chewie The Chorkie Jan 25 '18 at 18:14
1

self.view.layoutIfNeeded()

does the trick without delay

  • This is the correct answer. I removed the delay like suggested, set the subviews to the correct "toFront" position inside UIView animate, self.view.layoutIfNeeded() then set the frames. – Chewie The Chorkie Feb 25 '18 at 21:01