Scale Animation With Anchor in SwiftUI

DevTechie Inc
Jun 24, 2022


Photo by Sean Quillen on Unsplash

ScaleEffect modifier has anchor parameter which helps us anchor the view and scale other sides. Today we will look at ScaleEffect with anchor property and apply animation on it.

For this example, we will create a list of topics DevTechie has covered over the years for iOS app development. Here is what our final output will look like:

We will start with a VStack listing all the tech stacks. To generate a random color, we will build a computed variable, which will return a random color.

private var randomColor: Color {
        Color(red: Double.random(in: 0...1), green: Double.random(in: 0...1), blue: Double.random(in: 0...1), opacity: Double.random(in: 0...1))
    }
Our view will look like this:

struct ScaleWithAnchor: View {
    var body: some View {
        VStack(spacing: 10) {
            Text("UIKit")
                .padding()
                .background(randomColor, in: RoundedRectangle(cornerRadius: 10))
                .shadow(radius: 5)
            
            Text("Swift")
                .padding()
                .background(randomColor, in: RoundedRectangle(cornerRadius: 10))
                .shadow(radius: 5)
            
            Text("SwiftUI")
                .padding()
                .background(randomColor, in: RoundedRectangle(cornerRadius: 10))
                .shadow(radius: 5)
            
            Text("App Dev")
                .padding()
                .background(randomColor, in: RoundedRectangle(cornerRadius: 10))
                .shadow(radius: 5)
            
            Text("DevTechie")
                .padding()
                .background(randomColor, in: RoundedRectangle(cornerRadius: 10))
                .shadow(radius: 5)
            
            Text("iOS")
                .padding()
                .background(randomColor, in: RoundedRectangle(cornerRadius: 10))
                .shadow(radius: 5)
            
            Text("Machine Learning")
                .padding()
                .background(randomColor, in: RoundedRectangle(cornerRadius: 10))
                .shadow(radius: 5)
            
            Text("NLP")
                .padding()
                .background(randomColor, in: RoundedRectangle(cornerRadius: 10))
                .shadow(radius: 5)
            
            Text("Computer Vision")
                .padding()
                .background(randomColor, in: RoundedRectangle(cornerRadius: 10))
                .shadow(radius: 5)
        }
        .font(.largeTitle)
        .foregroundColor(.white)
    }
    
    private var randomColor: Color {
        Color(red: Double.random(in: 0...1), green: Double.random(in: 0...1), blue: Double.random(in: 0...1), opacity: Double.random(in: 0...1))
    }
}
Next, we will add a state property called animate. We will also add scaleEffect to each Text view with a different anchor value. Let’s also add withAnimation block and toggle animate property in that block..

struct ScaleWithAnchor: View {
    @State private var animate = false
    var body: some View {
        VStack(spacing: 10) {
            Text("UIKit")
                .padding()
                .background(randomColor, in: RoundedRectangle(cornerRadius: 10))
                .scaleEffect(animate ? 1.5 : 1, anchor: .top)
                .shadow(radius: 5)
            
            Text("Swift")
                .padding()
                .background(randomColor, in: RoundedRectangle(cornerRadius: 10))
                .scaleEffect(animate ? 1.5 : 1, anchor: .topLeading)
                .shadow(radius: 5)
            
            Text("SwiftUI")
                .padding()
                .background(randomColor, in: RoundedRectangle(cornerRadius: 10))
                .scaleEffect(animate ? 1.5 : 1, anchor: .topTrailing)
                .shadow(radius: 5)
            
            Text("App Dev")
                .padding()
                .background(randomColor, in: RoundedRectangle(cornerRadius: 10))
                .scaleEffect(animate ? 1.5 : 1, anchor: .bottom)
                .shadow(radius: 5)
            
            Text("DevTechie")
                .padding()
                .background(randomColor, in: RoundedRectangle(cornerRadius: 10))
                .scaleEffect(animate ? 1.5 : 1, anchor: .bottomLeading)
                .shadow(radius: 5)
            
            Text("iOS")
                .padding()
                .background(randomColor, in: RoundedRectangle(cornerRadius: 10))
                .scaleEffect(animate ? 1.5 : 1, anchor: .bottomTrailing)
                .shadow(radius: 5)
            
            Text("Machine Learning")
                .padding()
                .background(randomColor, in: RoundedRectangle(cornerRadius: 10))
                .scaleEffect(animate ? 1.5 : 1, anchor: .center)
                .shadow(radius: 5)
            
            Text("NLP")
                .padding()
                .background(randomColor, in: RoundedRectangle(cornerRadius: 10))
                .scaleEffect(animate ? 1.5 : 1, anchor: .leading)
                .shadow(radius: 5)
            
            Text("Computer Vision")
                .padding()
                .background(randomColor, in: RoundedRectangle(cornerRadius: 10))
                .scaleEffect(animate ? 1.5 : 1, anchor: .trailing)
                .shadow(radius: 5)
        }
        .font(.largeTitle)
        .foregroundColor(.white)
        .onTapGesture {
            withAnimation {
                animate.toggle()
            }
        }
    }
    
    private var randomColor: Color {
        Color(red: Double.random(in: 0...1), green: Double.random(in: 0...1), blue: Double.random(in: 0...1), opacity: Double.random(in: 0...1))
    }
}
Our output will look like this:

Anchor is a UnitPoint type meaning other than predefined enum values, we also have flexibility to set anchor based on CGFloat value. Let’s change our example to have random UnitPoints.

We will first create a computed property to randomize UnitPoint between 0 and 1 values.

private var randomUnitPoint: UnitPoint {
    UnitPoint(x: CGFloat.random(in: 0...1), y: CGFloat.random(in: 0...1))
}
Our view will look like this now:

struct ScaleWithAnchor: View {
    @State private var animate = false
    var body: some View {
        VStack(spacing: 10) {
            Text("UIKit")
                .padding()
                .background(randomColor, in: RoundedRectangle(cornerRadius: 10))
                .scaleEffect(animate ? 1.5 : 1, anchor: randomUnitPoint)
                .shadow(radius: 5)
            
            Text("Swift")
                .padding()
                .background(randomColor, in: RoundedRectangle(cornerRadius: 10))
                .scaleEffect(animate ? 1.5 : 1, anchor: randomUnitPoint)
                .shadow(radius: 5)
            
            Text("SwiftUI")
                .padding()
                .background(randomColor, in: RoundedRectangle(cornerRadius: 10))
                .scaleEffect(animate ? 1.5 : 1, anchor: randomUnitPoint)
                .shadow(radius: 5)
            
            Text("App Dev")
                .padding()
                .background(randomColor, in: RoundedRectangle(cornerRadius: 10))
                .scaleEffect(animate ? 1.5 : 1, anchor: randomUnitPoint)
                .shadow(radius: 5)
            
            Text("DevTechie")
                .padding()
                .background(randomColor, in: RoundedRectangle(cornerRadius: 10))
                .scaleEffect(animate ? 1.5 : 1, anchor: randomUnitPoint)
                .shadow(radius: 5)
            
            Text("iOS")
                .padding()
                .background(randomColor, in: RoundedRectangle(cornerRadius: 10))
                .scaleEffect(animate ? 1.5 : 1, anchor: randomUnitPoint)
                .shadow(radius: 5)
            
            Text("Machine Learning")
                .padding()
                .background(randomColor, in: RoundedRectangle(cornerRadius: 10))
                .scaleEffect(animate ? 1.5 : 1, anchor: randomUnitPoint)
                .shadow(radius: 5)
            
            Text("NLP")
                .padding()
                .background(randomColor, in: RoundedRectangle(cornerRadius: 10))
                .scaleEffect(animate ? 1.5 : 1, anchor: randomUnitPoint)
                .shadow(radius: 5)
            
            Text("Computer Vision")
                .padding()
                .background(randomColor, in: RoundedRectangle(cornerRadius: 10))
                .scaleEffect(animate ? 1.5 : 1, anchor: randomUnitPoint)
                .shadow(radius: 5)
        }
        .font(.largeTitle)
        .foregroundColor(.white)
        .onTapGesture {
            withAnimation {
                animate.toggle()
            }
        }
    }
    
    private var randomColor: Color {
        Color(red: Double.random(in: 0...1), green: Double.random(in: 0...1), blue: Double.random(in: 0...1), opacity: Double.random(in: 0...1))
    }
    
    private var randomUnitPoint: UnitPoint {
        UnitPoint(x: CGFloat.random(in: 0...1), y: CGFloat.random(in: 0...1))
    }
}


With that we have reached the end of this article. Thank you once again for reading. Subscribe to our weekly newsletter at https://www.devtechie.com