SwiftUI transition offset

Viewed 4238

I have a circle which has an offset. The circle appears with a scale animation when I press a button. The problem is that the animation does not start at the offset position of the circle, it starts at it's original position. How do i get the animation to start at the offset position of the circle.

Circle()
    .frame(width: 64, height: 64)
    .foregroundColor(.red)
    .transition(.scale)
    .offset(x: -69, y: 0)
3 Answers

Just add animation to transition, like below (tested with Xcode 11.4)

demo

struct Test: View {
    @State private var show = false
    var body: some View {
        VStack {
            Button("Tap") {
                withAnimation {
                    self.show.toggle()
                }
            }
            if show {
                Circle()
                    .frame(width: 64, height: 64)
                    .foregroundColor(.red)
                    .transition(AnyTransition.scale.animation(.default)) // here !!
                    .offset(x: -69, y: 0)
            }
        }
    }
}

Use alignment guides:

struct ViewsExercise: View {
    @State var show = true

    var body: some View {
        VStack(alignment: .leading, spacing: 20) {
            Spacer()
            Button("Tap") {
                self.show.toggle()
            }
            .font(.title)

            if show {
                Circle()
                    .frame(width: 64, height: 64)
                    .foregroundColor(.red)
                    .alignmentGuide(.leading, computeValue: { d in
                        64
                    })
                    .transition(AnyTransition.scale.animation(.easeInOut(duration: 1)))
            }
            Spacer()
        }
    }
}

enter image description here

You can learn more about alignment guides here. As @Asperi mentioned, the .offset modifier does not change position of view. The view is still at (0,0), so the transition happens at the origin point. If you want to "physically" offset the view then you should use alignment guides.

Hope this helps.

A lot is to do with the order in which the modifiers are called. You could try something like this:

enum ScaleFactor: Double {
        case full = 1.0
        case zero = 0.0
    }
    
    var body: some View {
        
        VStack(alignment: .leading, spacing: 20) {
            Spacer()
            Button("Tap") {
                scaleFactor = scaleFactor == .zero ? .full : .zero
            }
            .font(.title)
            
            Circle()
                .scaleEffect(scaleFactor.rawValue)
                .frame(width: 64, height: 64)
                .foregroundColor(.red)
                .offset(x: -150, y: 0)
                .animation(.linear(duration: 1), value: scaleFactor)
        }
    }

This will toggle between 0.0 and 1.0 scale factor and because the scaleEffect is before the offset, you will find that it scales in situ as desired.

Related