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:
- LinearGradient in SwiftUI
- AngularGradient in SwiftUI
- RadialGradient in SwiftUI
- Animate background with color gradient 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}
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}
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}
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}
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}
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 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}
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}
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.