0

I'm working on a WatchOS-app that needs to display an animation on top of another view while waiting for a task to finish.

My approach is the following (ConnectionView):

struct ConnectionView: View{
    @EnvironmentObject var isConnected : Bool

    var body: some View {
       return VStack(alignment: .trailing){
          ZStack{
             ScrollView{
               .....
             } 

             if(!isConnected){
                 ConnectionLoadingView()
             }

          }
       }
    }
}

And for the ConnectionLoadingView:

struct ConnectionLoadView: View {
    @State var isSpinning = false
    @EnvironmentObject var isConnected : Bool

    var body: some View {

    var animation : Animation

    ///This is needed in order to make the animation stop when isConnected is true
    if(!isConnected){
        animation = Animation.linear(duration: 4.0)
    }else{
        animation = Animation.linear(duration: 0)
    }

    return Image(systemName: "arrowtriangle.left.fill")
        .resizable()
        .frame(width: 100, height: 100)
        .rotationEffect(.degrees(isSpinning ? 360 : 0))
        .animation(animation)
        .foregroundColor(.green)
        .onAppear(){
            self.isSpinning = true
        }
        .onDisappear(){
            self.isSpinning = false
        }
    }
}

The real problem consists of two parts:

  1. On the very first ConnectionView that is displayed after the app is started, the ConnectionLoadView is displayed properly. On the subsequent runs the ConnectionLoadView has a weird "fade in" effect where it changes it's opacity throughout the animation (doesn't matter if I set the opacity for the view to 1, 0 or anything inbetween).

  2. If I don't have the following code snippet in ConnectionLoadView:

    if(!isConnected){
        animation = Animation.linear(duration: 4.0)
    }else{
        animation = Animation.linear(duration: 0)
    }
    

Without this the ConnectionView will continue to play the animation but move it from the foreground to background of the ZStack, behind the ScrollView, when it should just disappear straight away? Without this code snippet, the animation will only disappear as it should if the animation has stopped before the task has finished.

Is there any reason why the ConnectionLoadView is pushed to the background of the ZStack instead of just being removed from the view altogether when I clearly state that it should only be displayed if and only if !isConnected in ConnectionView?

I also can't quite figure out why there is a difference between the animation behaviour of the initial ConnectionView and the subsequent ones regarding the the opacity behaviour. Is the opacity changing part of the linear-animation?

Thanks!

heinz_dieter
  • 315
  • 1
  • 4
  • 12

1 Answers1

4

You are approaching the animation wrong. You shouldn't use implicit animations for this. Explicit animations are better suited.

Implicit animations are the ones you apply with .animation(). This affect any animatable parameter that changes on a view.

Explicit animations are the ones you trigger with withAnimation { ... }. Only parameters affected by the variables modified inside the closure, are the ones that will animate. The rest will not.

The new code looks like this:

import SwiftUI

class Model: ObservableObject {
    @Published var isConnected = false
}

struct Progress: View{
    @EnvironmentObject var model: Model

    var body: some View {
       return VStack(alignment: .trailing){
          ZStack{
             ScrollView{
                ForEach(0..<3) { idx in
                    Text("Some line of text in row # \(idx)")
                }

                Button("connect") {
                    self.model.isConnected = true
                }
                 Button("disconect") {
                     self.model.isConnected = false
                 }
             }

            if !self.model.isConnected {
                 ConnectionLoadView()
             }

          }
       }
    }
}

struct ConnectionLoadView: View {
    @State var isSpinning = false
    @EnvironmentObject var model: Model

    var body: some View {

    return Image(systemName: "arrowtriangle.left.fill")
        .resizable()
        .frame(width: 100, height: 100)
        .rotationEffect(.degrees(isSpinning ? 360 : 0))
        .foregroundColor(.green)
        .onAppear(){
            withAnimation(Animation.linear(duration: 4.0).repeatForever(autoreverses: false)) {
                self.isSpinning = true
            }
        }
    }
}
kontiki
  • 37,663
  • 13
  • 111
  • 125
  • Thank you so much! I read your article about animations in SwiftUI to better understand the difference between implicit and explicit animations. Now everything works as expected! – heinz_dieter Sep 18 '19 at 09:16
  • 1
    You're welcome! I forgot to point you to my article, but I'm glad you found it anyway. Cheers. – kontiki Sep 18 '19 at 09:24