I have the following view I place inside a button's label:
struct ButtonLoading: View {
@Binding var isAnimating: Bool
var body: some View {
HStack(spacing: 4) {
CircleView(isAnimating: $isAnimating, idx: 0)
CircleView(isAnimating: $isAnimating, idx: 1)
CircleView(isAnimating: $isAnimating, idx: 2)
}
}
}
struct CircleView: View {
@Binding var isAnimating: Bool
var idx: Int
var body: some View {
Circle()
.fill(Color.white)
.frame(width: 4, height: 4)
.offset(y: isAnimating ? -4 : 0)
.animation(Animation.spring(response: 0.6, dampingFraction: 1.0, blendDuration: 1.0)
.repeatForever(autoreverses: true)
.delay(Double(idx) * 0.25))
}
}
then the following views:
struct ContentView: View {
@State var current: String = "one"
var body: some View {
ZStack {
Color.red.opacity(0.05).ignoresSafeArea()
VStack {
if current == "one" {
ViewOne()
.transition(.move(edge: .leading))
}
if current == "two" {
ViewTwo()
.transition(.move(edge: .trailing))
}
}
}
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
withAnimation {
self.current = "two"
}
}
}
}
}
struct ViewOne: View {
var body: some View {
ZStack {
Color.blue.ignoresSafeArea()
Text("View One")
.foregroundColor(.white)
}
}
}
struct ViewTwo: View {
@State var isAnimating = false
var body: some View {
ZStack {
Color.white.ignoresSafeArea()
Button(action: {
isAnimating = true
}) {
ZStack(alignment: .center) {
ButtonLoading(isAnimating: $isAnimating)
.opacity(isAnimating ? 1 : 0)
Text("Continue")
.foregroundColor(.white)
.opacity(isAnimating ? 0 : 1)
}
.frame(height: 44)
.padding(.horizontal, 20)
.background(Color.blue)
}
}
}
}
When I click on the button, the expected behavior is to see the button's content change to the ellipsis loading animation (with the dots moving up and down, repeatedly). This works just fine if I remove the .transition(.move(edge: .trailing))
attached to ViewTwo
. However, in its current state, it appears that the transition animation clobbers the button animation. I see it moving in and out from the trailing edge. Any ideas? SwiftUI is driving me nuts!
Update:
Found another answer from a year ago that the prolific, Asperi, contributed to. Wrapping my button in one of these does solve the problem:
struct HelperView<Content: View> : UIViewControllerRepresentable {
var content: () -> Content
func makeUIViewController(context: Context) -> UIHostingController<Content> {
UIHostingController(rootView: content())
}
func updateUIViewController(_ host: UIHostingController<Content>, context: Context) {
host.rootView = content() // Update content
}
}
Is this the only way to solve this?
Update 2
Switching to opacity instead of offset and adding an .animation(nil)
resolves the issue with the animation inheriting the parent transition:
struct CircleView: View {
@Binding var isAnimating: Bool
var idx: Int
var body: some View {
Circle()
.fill(Color.white)
.frame(width: 4, height: 4)
.animation(nil)
.opacity(isAnimating: 0 : 1)
.animation(Animation.spring(response: 0.6, dampingFraction: 1.0, blendDuration: 1.0)
.repeatForever(autoreverses: true)
.delay(Double(idx) * 0.25))
}
}
However, adding a custom GeometryEffect
to change the offset does not work. Furthermore, adding a .transition(.identity)
with and without .animation(nil)
also doesn't work. Trying everything here. =)