AngularGradient in SwiftUI

AngularGradient in SwiftUI is used to create a color gradient of two or more colors where the colors radiate around a center point. The AngularGradient object is initialized with an array of colors that are shown in order around a center point. SwiftUI automatically interpolates between the colors as it goes around the circle.

AngularGradient can be adjusted by changing its center and the startAngle and endAngle parameters to create different effects.

Related articles on color gradients in SwiftUI:



Simple Angular Gradient

We'll reuse the arrays of colors defined in LinearGradient in SwiftUI. The view shows AngularGradient with two colors and with seven colors, specifying the center point of the gradient as the center of the view. The initial color starts at 3 o clock and moves in clockwise direction to complete 360 degrees.


AppColors

 1struct AppColors {
 2    static let oneColor = [Color(hue: 0.01, saturation: 0.2, brightness: 0.9),
 3                           Color(hue: 0.01, saturation: 1.0, brightness: 0.9)]
 4
 5    static let twoColors = [Color.blue, .yellow]
 6
 7    static let rainbow = [
 8        Color.red,
 9        .orange,
10        .yellow,
11        .green,
12        .blue,
13        Color(hue: 0.773, saturation: 1.0, brightness: 0.51),
14        Color(hue: 0.771, saturation: 1.0, brightness: 1.00)
15    ]
16}

StartAngularGradientView

 1struct StartAngularGradientView: View {
 2    var body: some View {
 3        VStack {
 4            Text("AngularGradient")
 5                .font(.largeTitle)
 6                .fontWeight(.bold)
 7            Divider()
 8            
 9            VStack(spacing: 0) {
10                Text("Two colors")
11                    .font(.title)
12                    .fontWeight(.bold)
13                
14                RoundedRectangle(cornerRadius: 20)
15                    .fill(
16                        AngularGradient(colors: AppColors.twoColors,
17                                        center: .center)
18                    )
19                    .frame(width: 300, height: 300)
20            }
21            Divider()
22
23            VStack(spacing: 0) {
24                Text("Seven colors")
25                    .font(.title)
26                    .fontWeight(.bold)
27                
28                RoundedRectangle(cornerRadius: 20)
29                    .fill(
30                        AngularGradient(colors: AppColors.rainbow,
31                                        center: .center)
32                    )
33                    .frame(width: 300, height: 300)
34            }
35            
36            Spacer()
37        }
38    }
39}

Simple Angular Gradient specifying the colors and the center in SwiftUI



Standard Center Options

An array of colors and a center point are required to initialise an AngularGradient. This code displays how the seven colors are shown using the different standard center point options.


AngularGradientView

 1struct AngularGradientView: View {
 2    var center: UnitPointType
 3
 4    var body: some View {
 5        HStack {
 6            VStack {
 7                Text("\(center.rawValue)").font(.title2)
 8            }
 9            RoundedRectangle(cornerRadius: 20)
10                .fill(
11                    AngularGradient(colors: AppColors.rainbow,
12                                    center: center.unitPoint)
13                )
14                .frame(width: 100, height: 100)
15        }
16    }
17}

CenterOptionsView

 1struct CenterOptionsView: View {
 2    var body: some View {
 3        ScrollView {
 4            VStack {
 5                Text("All Center Options")
 6                    .font(.largeTitle)
 7                    .fontWeight(.bold)
 8                Divider()
 9                
10                VStack(alignment: .trailing) {
11                    ForEach(UnitPointType.allCases, id: \.self) { endinging in
12                        AngularGradientView(center: endinging)
13                    }
14                }
15            }
16        }
17    }
18}

Standard center options for Angular Gradient in SwiftUI



Four Corners

It can be seen in the previous examples that some of the colors are not displayed when a center point is set at one of the edges of the view. This code shows that setting the center as the bottomTrailing the top right corner of the full color gradient is shown - if the center had been chosen. A full 360 degree circle of color gradient is generated around the center point using the specified colors when there is no start or end angle specified - even if some of these colors are not visible in the view because of the position of the center point.


FourCornersView

 1struct FourCornersView: View {
 2    var body: some View {
 3        VStack {
 4            Text("Four Corners")
 5                .font(.largeTitle)
 6                .fontWeight(.bold)
 7            
 8            Rectangle()
 9                .fill(
10                    AngularGradient(colors: AppColors.rainbow,
11                                    center: .center)
12                )
13                .frame(width: 300, height: 300)
14
15            VStack(spacing: 40) {
16                HStack(spacing: 40) {
17                    Rectangle()
18                        .fill(
19                            AngularGradient(colors: AppColors.rainbow,
20                                            center: .bottomTrailing)
21                        )
22                        .frame(width: 150, height: 150)
23                    Rectangle()
24                        .fill(
25                            AngularGradient(colors: AppColors.rainbow,
26                                            center: .bottomLeading)
27                        )
28                        .frame(width: 150, height: 150)
29                }
30                HStack(spacing: 40) {
31                    Rectangle()
32                        .fill(
33                            AngularGradient(colors: AppColors.rainbow,
34                                            center: .topTrailing)
35                        )
36                        .frame(width: 150, height: 150)
37                    Rectangle()
38                        .fill(
39                            AngularGradient(colors: AppColors.rainbow,
40                                            center: .topLeading)
41                        )
42                        .frame(width: 150, height: 150)
43                }
44            }
45            .padding()
46            Spacer()
47        }    
48    }
49}

Selecting a corner as the center on an AngularGradient will exclude some colors from the view



Animate movement of center point

The effect of changing the center point of the AngularGradient is illustrated by animating through the standard UnitPoints. This illustrates how the gradient changes with the setting of the center point.


enum UnitPointType

 1enum UnitPointType: String, CaseIterable {
 2    case topLeading = "topLeading"
 3    case top = "top"
 4    case topTrailing = "topTrailing"
 5    case leading = "leading"
 6    case center = "center"
 7    case trailing = "trailing"
 8    case bottomLeading = "bottomLeading"
 9    case bottom = "bottom"
10    case bottomTrailing = "bottomTrailing"
11
12    var unitPoint: UnitPoint {
13        switch self {
14        case .topLeading:
15            return UnitPoint.topLeading
16        case .top:
17            return UnitPoint.top
18        case .topTrailing:
19            return UnitPoint.topTrailing
20        case .leading:
21            return UnitPoint.leading
22        case .center:
23            return UnitPoint.center
24        case .trailing:
25            return UnitPoint.trailing
26        case .bottomLeading:
27            return UnitPoint.bottomLeading
28        case .bottom:
29            return UnitPoint.bottom
30        case .bottomTrailing:
31            return UnitPoint.bottomTrailing
32        }
33    }
34}

AnimateCenterView

 1struct AnimateCenterView: View {
 2    @State private var centerPointIndex = 4
 3    
 4    let timer = Timer.publish(every: 2,
 5                              on: .main,
 6                              in: .common).autoconnect()
 7    
 8    var body: some View {
 9        VStack {
10            Text("Move center")
11                .font(.largeTitle)
12                .fontWeight(.bold)
13            
14            Rectangle()
15                .fill(
16                    AngularGradient(colors: AppColors.rainbow,
17                                    center: UnitPointType.allCases[centerPointIndex].unitPoint)
18                )
19                .animation(Animation.easeInOut.speed(0.2),
20                           value: centerPointIndex)
21                .frame(width: 400, height: 400)
22                .onReceive(timer) { input in
23                    centerPointIndex = (centerPointIndex + 1) % UnitPointType.allCases.count
24                }
25            
26            
27            VStack(alignment: .leading)  {
28                HStack() {
29                    Text("center: ")
30                    Text(" \(UnitPointType.allCases[centerPointIndex].rawValue)")
31                        .animation(Animation.easeInOut.speed(0.6),
32                               value: centerPointIndex)
33                    Spacer()
34                }
35                .padding()
36                .font(.title)
37                .fontWeight(.bold)
38            }
39            
40            Spacer()
41        }
42        .padding()
43    }
44}

Animate a change in center of AngularGradient shows how the colors change


Animate a change in center of AngularGradient shows how the colors change



Setting Start and End angles in AngularGradient

In addition to specifying the center of an AngularGradient, it is also possible to specify the start and end angles where the gradient will begin and end. When the angle difference between the start and end is less than 360 degrees, the missing area is filled in with the colors at the beginning and end. Note that the center does not have to be one of the standard UnitPoints, but can be specified as any (x,y) coordinate values between 0 and 1.


StartEndAngleView

 1struct StartEndAngleView: View {
 2    var body: some View {
 3        VStack(spacing: 30) {
 4            Text("Start and End Angle")
 5                .font(.title)
 6                .fontWeight(.bold)
 7            
 8            VStack(alignment: .trailing) {
 9                HStack {
10                    Spacer()
11                    VStack(alignment: .trailing)  {
12                        Text("Start Angle =   0")
13                        Text("End Angle = 90")
14                    }
15                    RoundedRectangle(cornerRadius: 20)
16                        .fill(
17                            AngularGradient(colors: AppColors.rainbow,
18                                            center: .center,
19                                            startAngle: Angle(degrees: 0),
20                                            endAngle: Angle(degrees: 90))
21                        )
22                        .frame(width: 200, height: 200)
23                }
24            }
25            
26            VStack(alignment: .trailing) {
27                HStack {
28                    Spacer()
29                    VStack(alignment: .trailing)  {
30                        Text("Start Angle = -90")
31                        Text("End Angle =   90")
32                    }
33                    RoundedRectangle(cornerRadius: 20)
34                        .fill(
35                            AngularGradient(colors: AppColors.rainbow,
36                                            center: .center,
37                                            startAngle: Angle(degrees: -90),
38                                            endAngle: Angle(degrees: 90))
39                        )
40                        .frame(width: 200, height: 200)
41                }
42            }
43            
44            VStack(alignment: .trailing) {
45                HStack {
46                    Spacer()
47                    VStack(alignment: .trailing)  {
48                        Text("Center = (0.2, 0.8)")
49                        Text("Start Angle =          -90")
50                        Text("End Angle =            90")
51                    }
52                    RoundedRectangle(cornerRadius: 20)
53                        .fill(
54                            AngularGradient(colors: AppColors.rainbow,
55                                            center: UnitPoint(x: 0.2, y: 0.8),
56                                            startAngle: Angle(degrees: -90),
57                                            endAngle: Angle(degrees: 90))
58                        )
59                        .frame(width: 200, height: 200)
60                }
61            }
62            Spacer()            
63        }
64        .padding()
65    }
66}

Setting Start and End angles in AngularGradient in SwiftUI



Animate Start and End angles in AngularGradient

The effect of changing the startAngle and endAngle of the AngularGradient is illustrated by animating through random values for the angles. This illustrates how the gradient changes with the setting of the angles around a fixed center point.


AnimateAnglesView

 1struct AnimateAnglesView: View {
 2    @State private var startAngle = Angle(degrees: 0)
 3    @State private var endAngle = Angle(degrees: 90)
 4    
 5    let timer = Timer.publish(every: 2,
 6                              on: .main,
 7                              in: .common).autoconnect()
 8    
 9    func randomAngles() -> (Angle, Angle) {
10        let angle1 = Angle(degrees: Double.random(in: 0...360))
11        let angle2 = Angle(degrees: Double.random(in: 0...360))
12        
13        return (min(angle1, angle2), max(angle1, angle2))
14    }
15    
16    var body: some View {
17        VStack {
18            Text("Move Angles")
19                .font(.largeTitle)
20                .fontWeight(.bold)
21            
22            Rectangle()
23                .fill(
24                    AngularGradient(colors: AppColors.rainbow,
25                                    center: .center,
26                                    startAngle: startAngle,
27                                    endAngle: endAngle)
28                )
29                .animation(Animation.easeInOut.speed(0.2),
30                           value: startAngle)
31                .frame(width: 400, height: 400)
32                .onReceive(timer) { _ in
33                    (startAngle, endAngle) = randomAngles()
34                }
35            
36            
37            VStack(alignment: .trailing)  {
38                HStack() {
39                    Spacer()
40                    Text("Start Angle: ")
41                        .font(.title2)
42                    Text("\(startAngle.degrees, specifier: "%.2F")")
43                        .font(.title)
44                        .frame(width: 130)
45                        .animation(Animation.easeInOut.speed(0.6),
46                                   value: startAngle)
47                }
48                HStack() {
49                    Spacer()
50                    Text("End Angle: ")
51                        .font(.title2)
52                    Text("\(endAngle.degrees, specifier: "%.2F")")
53                        .font(.title)
54                        .frame(width: 130)
55                        .animation(Animation.easeInOut.speed(0.6),
56                                   value: endAngle)
57                }
58            }
59            .padding()
60            .fontWeight(.bold)
61            
62            
63            Spacer()
64        }
65        .padding()
66    }
67}

Animate changes in Start and End angles of AngularGradient shows how the colors change



Animate changing of center and angles

This animates changing the center point as well as the start and end angles every 2 seconds - just for fun.


AnimateCenterAnglesView

 1struct AnimateCenterAnglesView: View {
 2    @State private var centerPoint = UnitPoint(x: 0.5, y: 0.5)
 3    @State private var startAngle = Angle(degrees: 0)
 4    @State private var endAngle = Angle(degrees: 90)
 5    
 6    let timer = Timer.publish(every: 2,
 7                              on: .main,
 8                              in: .common).autoconnect()
 9    
10    func randomAngles() -> (Angle, Angle) {
11        let angle1 = Angle(degrees: Double.random(in: 0...360))
12        let angle2 = Angle(degrees: Double.random(in: 0...360))
13        return (min(angle1, angle2), max(angle1, angle2))
14    }
15    
16    func randomPoint() -> UnitPoint {
17        return UnitPoint(x: Double.random(in: 0...1),
18                         y: Double.random(in: 0...1))
19    }
20    
21    var body: some View {
22        VStack {
23            Text("Move Center & Angles")
24                .font(.largeTitle)
25                .fontWeight(.bold)
26            
27            Rectangle()
28                .fill(
29                    AngularGradient(colors: AppColors.rainbow,
30                                    center: centerPoint,
31                                    startAngle: startAngle,
32                                    endAngle: endAngle)
33                )
34                .animation(Animation.easeInOut.speed(0.2),
35                           value: startAngle)
36                .frame(width: 400, height: 400)
37                .onReceive(timer) { _ in
38                    (startAngle, endAngle) = randomAngles()
39                    centerPoint = randomPoint()
40                }
41            
42            
43            VStack(alignment: .trailing)  {
44                HStack() {
45                    Spacer()
46                    Text("Center: ")
47                        .font(.title2)
48                    Text("(\(centerPoint.x, specifier: "%.2F"), \(centerPoint.y, specifier: "%.2F"))")
49                        .font(.title)
50                        .frame(width: 200)
51                        .animation(Animation.easeInOut.speed(0.6),
52                                   value: centerPoint)
53                }
54                HStack() {
55                    Spacer()
56                    Text("Start Angle: ")
57                        .font(.title2)
58                    Text("\(startAngle.degrees, specifier: "%.0F")")
59                        .font(.title)
60                        .frame(width: 200)
61                        .animation(Animation.easeInOut.speed(0.6),
62                                   value: startAngle)
63                }
64                HStack() {
65                    Spacer()
66                    Text("End Angle: ")
67                        .font(.title2)
68                    Text("\(endAngle.degrees, specifier: "%.0F")")
69                        .font(.title)
70                        .frame(width: 200)
71                        .animation(Animation.easeInOut.speed(0.6),
72                                   value: endAngle)
73                }
74            }
75            .padding()
76            .fontWeight(.bold)
77            
78            
79            Spacer()
80        }
81        .padding()
82    }
83}

Animate changes in Center as well as Start and End angles of AngularGradient



AngularGradient on a circular shape

The result of the angular gradient on the square shapes is good to illustrate the result of specifying different center points and angles. Realistically makes more sense on a circular structure, where the area outside the specified angles is trimmed away. Here is a circle where the AngularGradient is used to color the rim of the circle with the specified array of seve colors.


CircleView

 1struct CircleView: View {
 2    @State private var startAngle = Angle(degrees: 0)
 3    @State private var endAngle = Angle(degrees: 90)
 4    
 5    let timer = Timer.publish(every: 2,
 6                              on: .main,
 7                              in: .common).autoconnect()
 8    
 9    func randomAngles() -> (Angle, Angle) {
10        let angle1 = Angle(degrees: Double.random(in: 0...360))
11        let angle2 = Angle(degrees: Double.random(in: 0...360))
12        return (min(angle1, angle2), max(angle1, angle2))
13    }
14    
15    var body: some View {
16        VStack {
17            Text("Circle")
18                .font(.largeTitle)
19                .fontWeight(.bold)
20            Text("Anglular Gradient")
21                .font(.title)
22                .fontWeight(.bold)
23            
24            Spacer()
25                .frame(height: 50)
26            
27            ZStack {
28                Circle()
29                    .strokeBorder(Color(hue: 0, saturation: 0, brightness: 0.85),
30                                  style: StrokeStyle(lineWidth: 60))
31                
32                Circle()
33                    .trim(from: startAngle.degrees/360.0, to: endAngle.degrees/360.0)
34                    .stroke(AngularGradient(colors: AppColors.rainbow,
35                                            center: .center,
36                                            startAngle: startAngle,
37                                            endAngle: endAngle),
38                            style: StrokeStyle(lineWidth: 60,
39                                               lineCap: .round))
40                    .padding(30)
41                    .animation(Animation.easeInOut.speed(0.2),
42                               value: startAngle)
43                    .onReceive(timer) { _ in
44                        (startAngle, endAngle) = randomAngles()
45                    }
46                
47                VStack()  {
48                    HStack() {
49                        Text("Start: ")
50                            .font(.title2)
51                            .frame(width: 60)
52                        Text("\(startAngle.degrees, specifier: "%.0F")")
53                            .font(.title)
54                            .frame(width: 60)
55                            .animation(Animation.easeInOut.speed(0.6),
56                                       value: startAngle)
57                    }
58                    HStack() {
59                        Text("End: ")
60                            .font(.title2)
61                            .frame(width: 60)
62                        Text("\(endAngle.degrees, specifier: "%.0F")")
63                            .font(.title)
64                            .frame(width: 60)
65                            .animation(Animation.easeInOut.speed(0.6),
66                                       value: endAngle)
67                    }
68                }
69                .padding()
70                .fontWeight(.bold)
71            }
72            .frame(width: 380, height: 380)
73            
74            
75            Spacer()
76        }
77        .padding()
78    }
79}

Changes in Start and End angles of AngularGradient on a circle


Animate changes in Start and End angles of AngularGradient on a circle




Conclusion

AngularGradient is used to transition from one color to another, optionally through a range of colors in a radial pattern around a specified center point. This article explored the effect of setting different center points as well as specifying a range of start and end angles for the gradients. AngularGradient can be used to create visually compelling effects in SwiftUI views, especially when used in circular shapes or arcs.

The source code for AngularGradientApp is available on GitHub.