I've created BlinkingCircle
that just blinks infinitely in SwiftUI.
When I push this view on a NavigationStack
, it starts to move up and down.
I can't figure out why the circle is moving.
Please help.
Here is the minimal code that reproduces the issue, along with a recording of the issue.
import SwiftUI
struct ContentView: View {
@State private var pushedViewIDs: [String] = []
var body: some View {
NavigationStack(path: $pushedViewIDs) {
Form {
NavigationLink(value: "blinking-circle") {
Label("Push blinking circle", systemImage: "arrow.forward")
}
}
.navigationTitle("App")
.navigationDestination(for: String.self) { pushedViewID in
if pushedViewID == "blinking-circle" {
BlinkingCircle()
}
}
}
}
}
struct BlinkingCircle: View {
@State private var isTranslucent = false
var body: some View {
Circle()
.frame(width: 200, height: 200)
.opacity(isTranslucent ? 0.3 : 1) // If I comment this out it still moves.
.onAppear {
withAnimation(.linear(duration: 1).repeatForever()) {
isTranslucent.toggle()
}
}
}
}
#Preview {
ContentView()
}
When the circle has just been pushed (before onAppear
), its position is a little bit above where it should be (I suspect this is because the initial position doesn't take into account the navigation bar).
Only after that does it get re-positioned in the right place, moving it down a bit. Therefore, withAnimation
also animates this change in position.
As workingdog support Ukraine mentioned in the comments, this is no longer the behaviour in iOS 18, so this is probably a bug.
One way to fix this is to limit the scope of the animation to just opacity
.
struct BlinkingCircle: View {
@State private var isTranslucent = false
var body: some View {
Circle()
.frame(width: 200, height: 200)
.animation(nil, value: isTranslucent) // this line disables the animation before this point
.opacity(isTranslucent ? 0.3 : 1)
.animation(.linear(duration: 1).repeatForever(), value: isTranslucent)
.onAppear {
isTranslucent.toggle()
}
}
}
On iOS 17, you can also do:
struct BlinkingCircle: View {
@State private var isTranslucent = false
var body: some View {
Circle()
.frame(width: 200, height: 200)
.animation(.linear(duration: 1).repeatForever()) { content in
content.opacity(isTranslucent ? 0.3 : 1)
}
.onAppear {
isTranslucent.toggle()
}
}
}