I am having an issue after updating to Xcode 15 where my view unexpectedly moves when trying to animate a rotating Circle() in SwiftUI. Previously, this code worked perfectly, but now the entire view either shifts from the top-left corner to the bottom-right corner continuously or, in some cases, just the Circle() moves unexpectedly.
The problem seems to occur when the animation is added. If I remove the animation code, the Circle() remains fixed and displays correctly.
Here is the code I am using to display the rotating circle:
Circle()
.stroke(style: StrokeStyle(lineWidth: 7, lineCap: .round, dash: [10, 30]))
.frame(width: 100, height: 100, alignment: .center)
.foregroundStyle(Color.accentColor)
.rotationEffect(Angle(degrees: isLoading ? 0 : 360.0))
.onAppear {
isLoading = true
}
.animation(Animation.linear(duration: 2.0).repeatForever(autoreverses: false), value: isLoading)
When the animation is applied, the view starts moving as described. However, without the animation, everything works fine.
Does anyone know why this might be happening and how to fix it?
Environment:
Xcode 15 macOS (SwiftUI project)
Already tried this , but same result
.onAppear(perform: {
withAnimation(Animation.linear(duration: 2.0).repeatForever(autoreverses:false)){
isLoading.toggle()
}
})
.rotationEffect(Angle(degrees: isLoading ? 0 : 360.0))
I was able to reproduce the issue running on Mac Sequoia 15.0.1 with Xcode 16.0. It may be another case of a launch animation getting tangled with other animations when the view appears, see also:
Here are three workarounds, none of which I can really explain, but they seem to fix the issue:
.rotationEffect
comes before .frame
:Circle()
.stroke(style: StrokeStyle(lineWidth: 7, lineCap: .round, dash: [10, 30]))
.rotationEffect(Angle(degrees: isLoading ? 0 : 360.0))
.frame(width: 100, height: 100)
.foregroundStyle(Color.accentColor)
.onAppear {
isLoading = true
}
.animation(.linear(duration: 2.0).repeatForever(autoreverses: false), value: isLoading)
.animation
modifier and set the flag using withAnimation
instead.Circle()
.stroke(style: StrokeStyle(lineWidth: 7, lineCap: .round, dash: [10, 30]))
.frame(width: 100, height: 100, alignment: .center)
.foregroundStyle(Color.accentColor)
.rotationEffect(Angle(degrees: isLoading ? 0 : 360.0))
.onAppear {
withAnimation(.linear(duration: 2.0).repeatForever(autoreverses: false)) {
isLoading = true
}
}
.task
, instead of in .onAppear
:Circle()
.stroke(style: StrokeStyle(lineWidth: 7, lineCap: .round, dash: [10, 30]))
.frame(width: 100, height: 100, alignment: .center)
.foregroundStyle(Color.accentColor)
.rotationEffect(Angle(degrees: isLoading ? 0 : 360.0))
.task { @MainActor in
isLoading = true
}
.animation(.linear(duration: 2.0).repeatForever(autoreverses: false), value: isLoading)