Angular Gradient in SwiftUI

DevTechie Inc
Jun 17, 2022


Photo by Luke Chesser on Unsplash

AngularGradient view in SwiftUI was introduced with iOS 13 and is used to draw an angular gradient. This view conforms to ShapeStyle protocol.

For the simplest initializer AngularGradient takes colors as a parameter along with UnitPoint for the location of center as shown below:

struct AngularGradientExample: View {
    let colors = [Color.blue, .purple, .cyan, .teal, .green, .orange, .pink, .red]
    var body: some View {
        AngularGradient(colors: colors, center: .center)
            .edgesIgnoringSafeArea(.all)
    }
}
We can move center by changing the value for center.

struct AngularGradientExample: View {
    let colors = [Color.blue, .purple, .cyan, .teal, .green, .orange, .pink, .red]
    var body: some View {
        AngularGradient(colors: colors, center: .topLeading)
            .edgesIgnoringSafeArea(.all)
    }
}
Instead of typing all enum values for UnitPoint, we will animate between them.

struct AngularGradientExample: View {
    let colors = [Color.blue, .purple, .cyan, .teal, .green, .orange, .pink, .red]    let unitPoints = [UnitPoint.top, .topLeading, .center, .bottom, .bottomLeading, .bottomTrailing, .topTrailing, .trailing, .leading]    @State private var centerLocation = UnitPoint.center    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    
    var body: some View {
        AngularGradient(colors: colors, center: centerLocation)
            .edgesIgnoringSafeArea(.all)
            .animation(Animation.easeInOut.speed(0.5), value: centerLocation)
            .onReceive(timer) { input in
                centerLocation = unitPoints.randomElement() ?? .center
            }
    }
}
By combining ZStack and Material, we can even create an interesting background effect as shown below:

struct AngularGradientExample: View {
    let colors = [Color.blue, .purple, .cyan, .teal]    let unitPoints = [UnitPoint.top, .topLeading, .center, .bottom, .bottomLeading, .bottomTrailing, .topTrailing, .trailing, .leading]    @State private var centerLocation = UnitPoint.center    let timer = Timer.publish(every: 2, on: .main, in: .common).autoconnect()
    
    var body: some View {
        ZStack {
            AngularGradient(colors: colors, center: centerLocation)
                .edgesIgnoringSafeArea(.all)
                .animation(Animation.easeInOut.speed(0.1), value: centerLocation)
                .onReceive(timer) { input in
                    centerLocation = unitPoints.randomElement() ?? .center
                }
            
            VStack {
                Text("DevTechie")
                    .font(.largeTitle)
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(.thinMaterial)
        }
    }
}
UnitPoint can take point values as well so instead of selecting from an enum, we can define UnitPoint in terms of values for x and y.

struct AngularGradientExample: View {
    let colors = [Color.blue, .purple, .cyan, .teal]
    
    @State private var centerLocation = UnitPoint.center
    
    let timer = Timer.publish(every: 2, on: .main, in: .common).autoconnect()
    
    var body: some View {
        ZStack {
            AngularGradient(colors: colors, center: centerLocation)
                .edgesIgnoringSafeArea(.all)
                .animation(Animation.easeInOut.speed(0.1), value: centerLocation)
                .onReceive(timer) { input in
                    centerLocation = UnitPoint(x: CGFloat.random(in: 0...1), y: CGFloat.random(in: 0...1))
                }
        }
    }
}
AngularGradient comes with start and end angle. Angle can be defined in degree and radian. We will use degree value for our case.

struct AngularGradientExample: View {
    let colors = [Color.blue, .purple, .cyan, .teal]
    
    @State private var centerLocation = UnitPoint.center
    
    @State private var startAngle = Angle(degrees: 90.0)
    @State private var endAngle = Angle(degrees: 360.0)
    
    let timer = Timer.publish(every: 2, on: .main, in: .common).autoconnect()
    
    var body: some View {
        ZStack {
            AngularGradient(colors: colors, center: centerLocation, startAngle: startAngle, endAngle: endAngle)
                .edgesIgnoringSafeArea(.all)
                .animation(Animation.easeInOut.speed(0.1), value: centerLocation)
                .onReceive(timer) { input in
                    centerLocation = UnitPoint(x: CGFloat.random(in: 0...1), y: CGFloat.random(in: 0...1))
                    startAngle = Angle(degrees: Double.random(in: 1...180))
                    endAngle = Angle(degrees: Double.random(in: 0...360))
                }
        }
    }
}
So far we have seen examples of setting values for center, start and end angle for AngularGradient view but there is one more aspect that you can control for this view.

AngularGradient allows you to control the color stop, essentially you can define where one color should stop and another one should begin. For this purpose, AngularGradient has an overloaded initializer which takes Gradient.Stop array value as shown below:

struct AngularGradientExample: View {
    let colorStops = [Gradient.Stop(color: Color.blue, location: 0.0),
                      Gradient.Stop(color: Color.purple, location: 0.1),
                      Gradient.Stop(color: Color.cyan, location: 0.2),
                      Gradient.Stop(color: Color.teal, location: 1.0)]
    
    @State private var centerLocation = UnitPoint.center
    
    @State private var startAngle = Angle(degrees: 90.0)
    @State private var endAngle = Angle(degrees: 360.0)
    
    let timer = Timer.publish(every: 2, on: .main, in: .common).autoconnect()
    
    var body: some View {
        ZStack {
            AngularGradient(stops: colorStops, center: centerLocation, startAngle: startAngle, endAngle: endAngle)
                .edgesIgnoringSafeArea(.all)
                .animation(Animation.easeInOut.speed(0.1), value: centerLocation)
                .onReceive(timer) { input in
                    centerLocation = UnitPoint(x: CGFloat.random(in: 0...1), y: CGFloat.random(in: 0...1))
                    startAngle = Angle(degrees: Double.random(in: 1...180))
                    endAngle = Angle(degrees: Double.random(in: 0...360))
                }
        }
    }
}
With that we have reached the end of this article. Thank you once again for reading. Don’t forget to subscribe our weekly newsletter at https://www.devtechie.com