Home > Back-end >  Scaled view unexpectedly moving up and down when pushed from navigation view
Scaled view unexpectedly moving up and down when pushed from navigation view

Time:09-19

I have a view that should scale in and out, starting immediately when the view is shown and repeating forever. However, I find that it's actually animating up and down as well as scaling much like in this post when it's pushed from a navigation view:

struct PlaceholderView: View {
  @State private var isAnimating = false

  var body: some View {
    Circle()
      .frame(width: 30, height: 30)
      .scaleEffect(self.isAnimating ? 0.8 : 1)
      .animation(Animation.easeInOut(duration: 1).repeatForever())
      .onAppear {
        self.isAnimating = true
      }
      .frame(width: 50, height: 50)
      .contentShape(
        Rectangle()
      )
  }
}

struct SettingsView: View {
  @State private var showPlaceholder = false

  var body: some View {
    NavigationView {
      ZStack {
        Button(
          action: {
            showPlaceholder = true
          }, label: {
            Text("Go to placeholder")
          }
        )

        NavigationLink(
          destination: PlaceholderView(),
          isActive: $showPlaceholder
        ) {
          EmptyView()
        }
        .hidden()
      }
    }
    .navigationViewStyle(.stack)
  }
}

Why is this and how can I stop this from happening?

UPDATE:

Wrapping self.isAnimating = true in DispatchQueue.main.async {} fixes the issue, but I don't understand why...

CodePudding user response:

I can reproduce the problem, but I wasn't able to reproduce your DispatchQueue.main.async {} fix.

Here is how I fixed it:

Animation is made by comparing a before state and and after state and animating between them. With an implicit animation, the before state is the position of the circle before it is added to the navigation view. By switching to an explicit animation and setting your before state after the view appears, the move animation is avoided and you only get the scaling:

Replace isAnimating with scale. In onAppear {}, first establish the before scale of 0.8, then animate the change to scale 1:

struct PlaceholderView: View {
    // To work correctly, this initial value should be different
    // than the before value set in .onAppear {}.
    @State private var scale = 1.0
    
    var body: some View {
        Circle()
            .frame(width: 30, height: 30)
            .scaleEffect(self.scale)
            .onAppear {
                self.scale = 0.8  // before 
                withAnimation(.easeInOut(duration: 1).repeatForever()) {
                    self.scale = 1  // after
                }
            }
            .frame(width: 50, height: 50)
            .contentShape(
                Rectangle()
            )
    }
}
  • Related