3

I created a custom LoadingView as a Indicator for loading objects from internet. When add it to NavigationView, it shows like this enter image description here

I only want it showing in the middle of screen rather than move from top left corner

Here is my Code

struct LoadingView: View {
    @State private var isLoading = false
    var body: some View {
        Circle()
            .trim(from: 0, to: 0.8)
            .stroke(Color.primaryDota, lineWidth: 5)
            .frame(width: 30, height: 30)
            .rotationEffect(Angle(degrees: isLoading ? 360 : 0))
            .onAppear {
                withAnimation(.linear(duration: 1).repeatForever(autoreverses: false)) {
                    self.isLoading.toggle()
                }
            }
            
    }
}

and my content view

struct ContentView: View {
    var body: some View {
        NavigationView {
            LoadingView()
                .frame(width: 30, height: 30)
        }
    }
}
George
  • 25,988
  • 10
  • 79
  • 133
kartbnb
  • 53
  • 2
  • That example code isn't producing the issue. However, if there is more to it, I suspect you may have a `.animation(...)` somewhere in a parent view. This can often cause problems, and I believe it's now deprecated anyway in iOS 15 in favour of `.animation(_:value:)`. This allows the animation to **only** happen when the specified value changes. If relevant, tested this code & it works fine in Xcode 13b5, iOS 15 simulator – George Aug 24 '21 at 10:21
  • Similar questions: https://stackoverflow.com/questions/68886773/what-happens-when-using-withanimation-without-specifying-dispatchqueue#comment121744203_68886773, https://stackoverflow.com/q/64566492/14351818 – aheze Aug 24 '21 at 14:17

2 Answers2

3

This looks like a bug of NavigationView: without it animation works totally fine. And it wan't fixed in iOS15.

Working solution is waiting one layout cycle using DispatchQueue.main.async before string animation:

struct LoadingView: View {
    @State private var isLoading = false
    var body: some View {
        Circle()
            .trim(from: 0, to: 0.8)
            .stroke(Color.red, lineWidth: 5)
            .frame(width: 30, height: 30)
            .rotationEffect(Angle(degrees: isLoading ? 360 : 0))
            .onAppear {
                DispatchQueue.main.async {
                    withAnimation(.linear(duration: 1).repeatForever(autoreverses: false)) {
                        self.isLoading.toggle()
                    }
                }
            }
    }
}
Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
2

This is a bug from NavigationView, I tried to kill all possible animation but NavigationView ignored all my try, NavigationView add an internal animation to children! here all we can do right now!

struct ContentView: View {
    var body: some View {
        NavigationView {
            LoadingView()
        }
        
    }
}


struct LoadingView: View {
    
    @State private var isLoading: Bool = Bool()
    
    var body: some View {
        
        Circle()
            .trim(from: 0, to: 0.8)
            .stroke(Color.blue, lineWidth: 5.0)
            .frame(width: 30, height: 30)
            .rotationEffect(Angle(degrees: isLoading ? 360 : 0))
            .animation(Animation.linear(duration: 1).repeatForever(autoreverses: false), value: isLoading)
            .onAppear { DispatchQueue.main.async { isLoading.toggle() } }
 
    }
    
}
ios coder
  • 1
  • 4
  • 31
  • 91
  • @LiangWang how is it better than mine? It looks like it should be usable in case you'd like to stop the animation, but I've tried stopping it with a button, and got [this](https://i.stack.imgur.com/B62nm.gif). p.s. also `Bool()` looks very strange – Phil Dukhov Sep 01 '21 at 04:11
  • @PhilipDukhov: but why you stop it with Button in first place? we need it for showing when the view loads, then we put it out with a condition. right? – ios coder Sep 01 '21 at 04:36
  • @swiftPunk I just don't understand how is it better, and that was my guess=) – Phil Dukhov Sep 01 '21 at 04:38
  • @PhilipDukhov: That case you said would never happen, look here: https://i.stack.imgur.com/YKclE.gif – ios coder Sep 01 '21 at 04:42
  • @swiftPunk I understand that, but how is it different from running the animation in `onAppear` then? – Phil Dukhov Sep 01 '21 at 04:51
  • @PhilipDukhov: I would say using animation modifier is better than to not using, for better code reading or future refactoring maybe. – ios coder Sep 01 '21 at 04:55
  • @swiftPunk I see. To me when I see the animation modifier, I think how it would work in different cases, like State updates. And `withAnimation` directly showing that this animation will be only performed once. Up to you ofc – Phil Dukhov Sep 01 '21 at 04:58
  • @PhilipDukhov: I did not said your code is wrong or has issue, I mean when we can be politically correct, why not? we can use your way even, but it may change our behaviour or others to not using the real and basic one for more control and clear code. – ios coder Sep 01 '21 at 05:02
  • @swiftPunk My question was originally addressed to LiangWang. I also don't find your approach cleaner for the reasons described in my previous comment – Phil Dukhov Sep 01 '21 at 05:11
  • @PhilipDukhov: I also tried my best to express my point of view in my last comments! but it seems I was not successful! Let me ask you how would you embed `value` for animation if we would use your method? – ios coder Sep 01 '21 at 05:16
  • @swiftPunk You are still using the same `toggle` that I do, without being able to embed it into `async`. I understand your points and agree that your code looks more functional, but I don't agree that this is always the right way to go. – Phil Dukhov Sep 01 '21 at 05:26
  • @PhilipDukhov: Well I am sure this is the only and safe way to apply the animation on the value we want! In your way the animation would apply to entire view, which may be not the case we want or wired animation even, with my way we are sure where we apply the animation without infecting the entire view! In my way I can gave nil value to some view or kill animation in some modifier then start it again, those are not possible with your way. – ios coder Sep 01 '21 at 05:54