1
votes

I have a view that uses .mask() to show a hole in a Rectangle(). I want the hole to animate from big to small.

I've wrapped the line circleTransitionWidth = 0.0 in withAnimation, however it goes from one state to the other without animating.

My ContentView:

struct ContentView: View {
    var body: some View {
        CircleTransition()
            .ignoresSafeArea()
    }
}

And here's the rectangle/hole view:

struct CircleTransition: View {
    @State private var circleTransitionWidth: CGFloat = 0.0
    
    func Hole(in rect: CGRect, rectangleRect: CGRect) -> Path {
        var shape = Rectangle().path(in: rectangleRect)
        
        shape.addPath(Circle().path(in: rect))
        
        return shape
    }

    
    var body: some View {
        GeometryReader { geo in
            Rectangle()
                .fill(Color.red)
                .frame(width: geo.size.width, height: geo.size.height)
                .mask(Hole(in: CGRect(x: (geo.size.width * 0.5) - (circleTransitionWidth * 0.5), y: (geo.size.height * 0.5) - (circleTransitionWidth * 0.5), width: circleTransitionWidth, height: circleTransitionWidth), rectangleRect: CGRect(x: 0, y: 0, width: geo.size.width, height: geo.size.height))
                .fill(style: FillStyle(eoFill: true)))
                .onAppear {
                    circleTransitionWidth = geo.size.height

                    withAnimation(Animation.easeInOut(duration: 2.0)) {
                        circleTransitionWidth = 0.0
                    }
                }
        }
    }
}

Why won't my view animate?

2

2 Answers

2
votes

Function cannot be animatable. We need to make Hole real is-a Shape with animatable property provided.

Here is a demo of solution. Prepared with Xcode 12.1 / iOS 14.1

demo

struct CircleTransition: View {
    @State private var circleTransitionWidth: CGFloat = 0
    
    var body: some View {
        GeometryReader { geo in
            Rectangle()
                .fill(Color.red)
                .frame(width: geo.size.width, height: geo.size.height)
                .mask(Hole(circleTransitionWidth: circleTransitionWidth, rectangleRect: CGRect(x: 0, y: 0, width: geo.size.width, height: geo.size.height))
                            .fill(style: FillStyle(eoFill: true)))
                .onAppear {
                    circleTransitionWidth = geo.size.height
                    withAnimation(Animation.easeInOut(duration: 2.0)) {
                        circleTransitionWidth = 0.0
                    }
                }
        }
    }
}

struct Hole: Shape {
    var circleTransitionWidth: CGFloat
    var rectangleRect: CGRect
    
    var animatableData: CGFloat {
        get { circleTransitionWidth }
        set { circleTransitionWidth = newValue }
    }
    
    func path(in rect: CGRect) -> Path {
        let rect = CGRect(x: (rect.size.width * 0.5) - (circleTransitionWidth * 0.5), y: (rect.size.height * 0.5) - (circleTransitionWidth * 0.5), width: circleTransitionWidth, height: circleTransitionWidth)
        var shape = Rectangle().path(in: rectangleRect)
        shape.addPath(Circle().path(in: rect))
        return shape
    }
}
0
votes
struct CircleTransition: View {
@State private var circleTransitionWidth: CGFloat = 0.0
@State private var circleTransitionHeight: CGFloat = 0.0

func Hole() {
    circleTransitionWidth = 100.0
    circleTransitionHeight = 100.0
}


var body: some View {
    ZStack {
        Rectangle()
            .fill(Color.red)
        Circle()
            .fill(Color.white)
            .frame(width: circleTransitionWidth, height: circleTransitionHeight)
    }.onAppear {
        withAnimation(Animation.easeIn(duration: 2.0)) {
            self.Hole()
        }
    }

}

}