Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What might be causing this animation bug with SwiftUI and NavigationView?

I've been experimenting with some SwiftUI layouts and one of the things that I wanted to try out was creating a simple circular progress ring. After playing around with the code for a while I managed to get everything working the way I was hoping for it to, at least for a prototype. The issue arrises when I embed this view inside a SwiftUI NavigationView. Now, every time I run the app in the canvas, simulator, or on a device, the initial loading of the progress ring has the entire view slowly sliding up into position.

This is a simple prototype, just messing around with the new SwiftUI tools. After some experimentation, I've found that if I remove the NavigationView the ring acts like it's meant to from the beginning. I'm not seeing an obvious reason for why this issue is occurring though.

import SwiftUI

struct ProgressRing_ContentView: View {

@State var progressToggle = false
@State var progressRingEndingValue: CGFloat = 0.75

var ringColor: Color = Color.green
var ringWidth: CGFloat = 20
var ringSize: CGFloat = 200

var body: some View {
    TabView{
        NavigationView{
            VStack{

                Spacer()

                ZStack{
                    Circle()
                        .trim(from: 0, to: progressToggle ? progressRingEndingValue : 0)
                        .stroke(ringColor, style: StrokeStyle(lineWidth: ringWidth, lineCap: .round, lineJoin: .round))
                        .background(Circle().stroke(ringColor, lineWidth: ringWidth).opacity(0.2))
                        .frame(width: ringSize, height: ringSize)
                        .rotationEffect(.degrees(-90.0))
                        .animation(.easeInOut(duration: 1))
                        .onAppear() {
                            self.progressToggle.toggle()
                        }

                    Text("\(Int(progressRingEndingValue * 100)) %")
                        .font(.largeTitle)
                        .fontWeight(.bold)
                }

                Spacer()

                Button(action: {
                    self.progressRingEndingValue = CGFloat.random(in: 0...1)
                }) { Text("Randomize")
                        .font(.largeTitle)
                        .foregroundColor(ringColor)
                }

                Spacer()

            }
            .navigationBarTitle("ProgressRing", displayMode: .inline)
            .navigationBarItems(leading:
                Button(action: {
                    print("Refresh Button Tapped")
                    }) {
                    Image(systemName: "arrow.clockwise")
                        .foregroundColor(Color.green)
                    }, trailing:
                Button(action: {
                    print("Share Button Tapped")
                    }) {
                    Image(systemName: "square.and.arrow.up")
                        .foregroundColor(Color.green)
                }
            )
        }
    }
}
}

#if DEBUG
struct ProgressRing_ContentView_Previews: PreviewProvider {
static var previews: some View {
    Group {

        ProgressRing_ContentView()
            .environment(\.colorScheme, .light)
            .previewDisplayName("Light Mode")

}
}
#endif

Above is the exact code that I'm currently working with. The actual animation of the ring sliding seems to be working how I expected it to, I'm just not sure why the entire ring itself is moving when embedded in a NavigationView.

like image 320
pmostoff Avatar asked Aug 31 '19 10:08

pmostoff


People also ask

What is NavigationView?

com.google.android.material.navigation.NavigationView. Represents a standard navigation menu for application. The menu contents can be populated by a menu resource file. NavigationView is typically placed inside a DrawerLayout .

How do I make an animation in SwiftUI?

SwiftUI has built-in support for animations with its animation() modifier. To use this modifier, place it after any other modifiers for your views, tell it what kind of animation you want, and also make sure you attach it to a particular value so the animation triggers only when that specific value changes.

Does SwiftUI use Core Animation?

SwiftUI uses Core Animation for rendering by default, and its performance is great for most animations. But if you find yourself creating a very complex animation that seems to suffer from lower framerates, you may want to utilize the power of Metal, which is Apple's framework used for working directly with the GPU.


1 Answers

You need to use explicit animations, instead of implicit. With implicit animations, any animatable parameter that changes, the framework will animate. Whenever possible, you should use explicit animations. Below is the updated code. Notice I remove the .animation() call and added two withAnimation() closures.

If you would like to expand your knowledge on implicit vs. explicit animations, check this link: https://swiftui-lab.com/swiftui-animations-part1/

struct ContentView: View {

    @State var progressToggle = false
    @State var progressRingEndingValue: CGFloat = 0.75

    var ringColor: Color = Color.green
    var ringWidth: CGFloat = 20
    var ringSize: CGFloat = 200

    var body: some View {
        TabView{
            NavigationView{
                VStack{

                    Spacer()

                    ZStack{
                        Circle()
                            .trim(from: 0, to: progressToggle ? progressRingEndingValue : 0)
                            .stroke(ringColor, style: StrokeStyle(lineWidth: ringWidth, lineCap: .round, lineJoin: .round))
                            .background(Circle().stroke(ringColor, lineWidth: ringWidth).opacity(0.2))
                            .frame(width: ringSize, height: ringSize)
                            .rotationEffect(.degrees(-90.0))
                            .onAppear() {
                                withAnimation(.easeInOut(duration: 1)) {
                                    self.progressToggle.toggle()
                                }
                        }

                        Text("\(Int(progressRingEndingValue * 100)) %")
                            .font(.largeTitle)
                            .fontWeight(.bold)
                    }

                    Spacer()

                    Button(action: {
                        withAnimation(.easeInOut(duration: 1)) {
                            self.progressRingEndingValue = CGFloat.random(in: 0...1)
                        }
                    }) { Text("Randomize")
                        .font(.largeTitle)
                        .foregroundColor(ringColor)
                    }

                    Spacer()

                }
                .navigationBarTitle("ProgressRing", displayMode: .inline)
                .navigationBarItems(leading:
                    Button(action: {
                        print("Refresh Button Tapped")
                    }) {
                        Image(systemName: "arrow.clockwise")
                            .foregroundColor(Color.green)
                    }, trailing:
                    Button(action: {
                        print("Share Button Tapped")
                    }) {
                        Image(systemName: "square.and.arrow.up")
                            .foregroundColor(Color.green)
                    }
                )
            }
        }
    }
}
like image 83
kontiki Avatar answered Oct 17 '22 07:10

kontiki