Create a circular slider in SwiftUI with ChatGPT

It may have been too ambitious to ask ChatGPT to create an iOS app. So in this article, I ask ChatGPT to create a circular slider in SwiftUI. It can take a while and the more information provided to ChatGPT the better, but it is possible to get useable code from ChatGPT.

Related articles on ChatGPT and SwiftUI:

See related article on how to Create a circular slider in SwiftUI.



Ask ChatGPT to create a Circular Slider - Circular Slider 1

Let's give ChatGPT as much information as we can on what we want for a circular slider. The following prompt is given to ChatGPT to get it to generate the SwiftUI code.


First prompt to create a circular slider

PROMPT:  
Write the swiftui code to create a circular slider. The circle should have a grey
background and progress is shown with the color turning blue when complete. the
progress is measured in a clockwise direction with a starting value of zero at 12
o clock and progress clockwise to an end value of 100. A drag gesture should be
used to interact with the circular slider. The thumb indicator should be a yellow
circle on the circular slider. The current Slider value should be displayed in
large text in the centre of the circular slider 

Response from chatGPT

First, you'll need to create a custom CircularSlider view that can handle the drawing and interaction. Here's a SwiftUI implementation of the circular slider you described:

 1import SwiftUI
 2
 3struct ContentView: View {
 4    @State private var sliderValue: Double = 0
 5
 6    var body: some View {
 7        VStack {
 8            CircularSlider(value: $sliderValue)
 9            Text("Current Value: \(sliderValue, specifier: "%.2f")")
10                .padding()
11        }
12    }
13}
14
15struct CircularSlider: View {
16    @Binding var value: Double
17    private let lineWidth: CGFloat = 20
18    private let thumbSize: CGFloat = 30
19
20    var body: some View {
21        ZStack {
22            Circle()
23                .stroke(Color.gray, lineWidth: lineWidth)
24            
25            Circle()
26                .trim(from: 0, to: CGFloat(value / 100))
27                .stroke(Color.blue, lineWidth: lineWidth)
28                .rotationEffect(.degrees(-90))
29
30            Circle()
31                .fill(Color.yellow)
32                .frame(width: thumbSize, height: thumbSize)
33                .padding(lineWidth / 2)
34                .rotationEffect(.degrees(-90 + value * 3.6))
35                .gesture(DragGesture(minimumDistance: 0)
36                            .onChanged(updateValue(from:)))
37
38            Text("\(value, specifier: "%.0f")")
39                .font(.largeTitle)
40                .fontWeight(.bold)
41        }
42    }
43
44    private func updateValue(from drag: DragGesture.Value) {
45        let vector = CGVector(dx: drag.location.x - lineWidth - thumbSize / 2, dy: drag.location.y - lineWidth - thumbSize / 2)
46        let angle = atan2(vector.dy, vector.dx) + .pi / 2
47        let fixedAngle = angle < 0 ? angle + 2 * .pi : angle
48        value = Double(fixedAngle / (2 * .pi) * 100)
49    }
50}
51
52struct ContentView_Previews: PreviewProvider {
53    static var previews: some View {
54        ContentView()
55    }
56}

This code defines a CircularSlider struct that takes a binding to a value variable. The slider is drawn using a combination of Circle and Text views, and a DragGesture is used to update the value based on user interaction. The thumb is a yellow circle on the circular slider, and the current slider value is displayed in large text in the center of the slider. The progress is measured in a clockwise direction, starting at 12 o'clock, with the color changing from gray to blue as the slider is completed.


This is not bad, but it doesn't work. There is a gray circle for the slider and the progress value is displayed in the center. There is an additional text field showing the current value below the circular indicator, which is not required. The progress does change and display in blue and the progress value does update in real time. The problem is the drag gesture works somewhere very close to the center of the circular slider and the yellow circle for the thumb is in the center and remains in the center.


First Circular Slider with thumb in the wrong place and the gesture not working - code generated by ChatGPT 4
First Circular Slider with thumb in the wrong place and the gesture not working - code generated by ChatGPT 4


First Circular Slider with thumb in the wrong place and the gesture not working - code generated by ChatGPT 4



Regenerate response - Circular Slider 2

Rather than try and get ChatGPT to fix the issues, let's just select Regenerate response to see what else we might get.

Prompt to ChatGPT

PROMPT:  
Write the swiftui code to create a circular slider. The circle should have a grey
background and progress is shown with the color turning blue when complete. the
progress is measured in a clockwise direction with a starting value of zero at 12
o clock and progress clockwise to an end value of 100. A drag gesture should be
used to interact with the circular slider. The thumb indicator should be a yellow
circle on the circular slider. The current Slider value should be displayed in
large text in the centre of the circular slider

Response from chatGPT

To create a circular slider in SwiftUI, you can create a custom view that represents the circular slider and use a drag gesture to update the slider's value. Here's the code for the CircularSlider view:


CircularSlider

 1import SwiftUI
 2
 3struct CircularSlider: View {
 4    @State private var sliderValue: Double = 0
 5    @State private var dragOffset = CGSize.zero
 6    private let circleWidth: CGFloat = 5
 7    
 8    private var circleProgress: Double {
 9        return sliderValue / 100
10    }
11    
12    private var rotationAngle: Double {
13        return 360 * circleProgress
14    }
15    
16    var body: some View {
17        ZStack {
18            Circle()
19                .stroke(Color.gray, lineWidth: circleWidth)
20            
21            Circle()
22                .trim(from: 0, to: CGFloat(circleProgress))
23                .stroke(Color.blue, lineWidth: circleWidth)
24                .rotation(Angle(degrees: -90))
25            
26            Circle()
27                .fill(Color.yellow)
28                .frame(width: circleWidth * 3, height: circleWidth * 3)
29                .offset(x: -radius, y: 0)
30                .rotationEffect(Angle(degrees: rotationAngle - 90))
31                .gesture(
32                    DragGesture(minimumDistance: 0)
33                        .onChanged(updateSliderValue(from:))
34                )
35            
36            Text("\(Int(sliderValue))")
37                .font(.largeTitle)
38                .fontWeight(.bold)
39        }
40    }
41    
42    private var radius: CGFloat {
43        return circleWidth * 20
44    }
45    
46    private func updateSliderValue(from drag: DragGesture.Value) {
47        let vector = CGVector(dx: drag.location.x - radius, dy: drag.location.y - radius)
48        let angle = atan2(vector.dy, vector.dx)
49        let fixedAngle = angle < 0 ? angle + 2 * CGFloat.pi : angle
50        sliderValue = Double(fixedAngle / (2 * CGFloat.pi)) * 100
51    }
52}

To use the CircularSlider view, include it in your main view:

ContentView

 1import SwiftUI
 2
 3struct ContentView: View {
 4    var body: some View {
 5        CircularSlider()
 6            .frame(width: 200, height: 200)
 7            .padding()
 8    }
 9}
10
11struct ContentView_Previews: PreviewProvider {
12    static var previews: some View {
13        ContentView()
14    }
15}

This code will create a circular slider with the specified colors and interaction. The slider will start at the 12 o'clock position and progress clockwise, with a yellow thumb indicator. The current slider value will be displayed in the center of the circular slider.


This is worse than the first attempt with a compile error in the CircularSlider struct. This is caused by the the use of rotation instead of rotationEffect on a circle.


Compile error in code for Circular Slider 2 - code generated by ChatGPT 4



Let's feedback the compile error to ChatGPT

PROMPT:  
There is a compile error in line ".rotation(Angle(degrees: -90))" - error =
"Value of type 'some View' has no member 'rotation'"

Response from chatGPT

Apologies for the confusion. I made a mistake in the code. The correct function name should be rotationEffect. Here's the corrected code snippet:


CircularSlider and ContentView

 1import SwiftUI
 2
 3struct CircularSlider: View {
 4    @State private var sliderValue: CGFloat = 0
 5    @State private var angle: Double = 0
 6    
 7    var body: some View {
 8        ZStack {
 9            Circle()
10                .stroke(Color.gray, lineWidth: 20)
11                .frame(width: 200, height: 200)
12            
13            Circle()
14                .trim(from: 0, to: sliderValue / 100)
15                .stroke(Color.blue, lineWidth: 20)
16                .frame(width: 200, height: 200)
17                .rotationEffect(.degrees(-90))
18            
19            Circle()
20                .fill(Color.yellow)
21                .frame(width: 30, height: 30)
22                .offset(y: -100)
23                .rotationEffect(Angle.degrees(Double(sliderValue / 100 * 360 - 90)))
24                .gesture(
25                    DragGesture(minimumDistance: 0)
26                        .onChanged { value in
27                            let vector = CGVector(dx: value.location.x - 100, dy: value.location.y - 100)
28                            let angle = atan2(vector.dy, vector.dx)
29                            let degree = Double(angle) * 180 / .pi
30                            let newValue = (degree + 90) * 100 / 360
31                            sliderValue = CGFloat(max(min(newValue, 100), 0))
32                        }
33                )
34            
35            Text("\(Int(sliderValue))")
36                .font(.system(size: 50, weight: .bold, design: .rounded))
37                .foregroundColor(.blue)
38        }
39    }
40}
41
42struct ContentView: View {
43    var body: some View {
44        CircularSlider()
45            .padding()
46    }
47}
48
49struct ContentView_Previews: PreviewProvider {
50    static var previews: some View {
51        ContentView()
52    }
53}

Now, the code should compile and work as expected.



Circular Slider 2 with wierd behavior on drag gesture - code generated by ChatGPT 4


Circular Slider 2 with wierd behavior on drag gesture - code generated by ChatGPT 4



Regenerate response - Circular Slider 3

We can't just use Regenerate response as this will just rerun the last prompt, which was to address the compile error. In order to regenerate a response on the original request, it is necessary to edit the request and then select Save & Submit. I just added a space at the end as I want to stick with the original request and see the different responses we get.

Prompt to ChatGPT

PROMPT:  
Write the swiftui code to create a circular slider. The circle should have a grey
background and progress is shown with the color turning blue when complete. the
progress is measured in a clockwise direction with a starting value of zero at 12
o clock and progress clockwise to an end value of 100. A drag gesture should be
used to interact with the circular slider. The thumb indicator should be a yellow
circle on the circular slider. The current Slider value should be displayed in
large text in the centre of the circular slider

Response from chatGPT

To create a circular slider in SwiftUI, you can create a custom view that combines the drag gesture and drawing the desired shapes. Here's a basic implementation for the slider you described:

CircularSlider and ContentView

 1import SwiftUI
 2
 3struct CircularSlider: View {
 4    @State private var sliderValue: CGFloat = 0
 5    private let maxValue: CGFloat = 100
 6    
 7    var body: some View {
 8        ZStack {
 9            Circle()
10                .stroke(Color.gray, lineWidth: 20)
11            
12            Circle()
13                .trim(from: 0, to: sliderValue / maxValue)
14                .stroke(Color.blue, lineWidth: 20)
15                .rotationEffect(.degrees(-90))
16            
17            Text("\(Int(sliderValue))")
18                .font(.largeTitle)
19                .fontWeight(.bold)
20            
21            Circle()
22                .fill(Color.yellow)
23                .frame(width: 24, height: 24)
24                .offset(y: -130)
25                .rotationEffect(.degrees(Double(sliderValue / maxValue * 360 - 90)))
26                .gesture(
27                    DragGesture(minimumDistance: 0)
28                        .onChanged { value in
29                            let vector = CGVector(dx: value.location.x - 130, dy: value.location.y - 130)
30                            let angle = atan2(vector.dy, vector.dx)
31                            let normalizedAngle = angle < 0 ? angle + 2 * .pi : angle
32                            sliderValue = max(0, min(maxValue, normalizedAngle * maxValue / (2 * .pi)))
33                        }
34                )
35        }
36        .frame(width: 260, height: 260)
37    }
38}
39
40struct ContentView: View {
41    var body: some View {
42        CircularSlider()
43    }
44}
45
46struct ContentView_Previews: PreviewProvider {
47    static var previews: some View {
48        ContentView()
49    }
50}

This code creates a circular slider with a gray background circle, a blue progress circle, a yellow thumb indicator, and the slider value displayed in the center. The slider interaction is achieved using a drag gesture, and the slider value updates as you move the thumb indicator around the circle.



Circular Slider 3 with wierd behavior on drag gesture - code generated by ChatGPT 4


Circular Slider 3 with wierd behavior on drag gesture - code generated by ChatGPT 4



Regenerate response - Circular Slider 4

Select Regenerate response to see another version.

Prompt to ChatGPT

PROMPT:  
Write the swiftui code to create a circular slider. The circle should have a grey
background and progress is shown with the color turning blue when complete. the
progress is measured in a clockwise direction with a starting value of zero at 12
o clock and progress clockwise to an end value of 100. A drag gesture should be
used to interact with the circular slider. The thumb indicator should be a yellow
circle on the circular slider. The current Slider value should be displayed in
large text in the centre of the circular slider

Response from chatGPT

To create a custom SwiftUI circular slider, you can define a custom view CircularSlider that takes care of drawing the background circle, progress circle, and the thumb indicator. To handle the gesture, you'll use the DragGesture SwiftUI gesture.

Here's the code for the custom circular slider:


CircularSlider and ContentView

 1import SwiftUI
 2
 3struct CircularSlider: View {
 4    @State private var sliderValue: Double = 0
 5    @State private var angle: Angle = .degrees(0)
 6    let circleWidth: CGFloat = 10
 7    let thumbRadius: CGFloat = 15
 8
 9    var body: some View {
10        GeometryReader { geometry in
11            let circleRadius = min(geometry.size.width, geometry.size.height) / 2 - thumbRadius
12            let circleCenter = CGPoint(x: geometry.size.width / 2, y: geometry.size.height / 2)
13
14            ZStack {
15                // Background circle
16                Circle()
17                    .stroke(Color.gray, lineWidth: circleWidth)
18
19                // Progress circle
20                Circle()
21                    .trim(from: 0, to: CGFloat(sliderValue / 100))
22                    .stroke(Color.blue, lineWidth: circleWidth)
23                    .rotation(Angle.degrees(-90))
24
25                // Thumb indicator
26                Circle()
27                    .fill(Color.yellow)
28                    .frame(width: thumbRadius * 2, height: thumbRadius * 2)
29                    .position(x: circleCenter.x + circleRadius * cos(CGFloat(angle.radians - Double.pi / 2)),
30                              y: circleCenter.y + circleRadius * sin(CGFloat(angle.radians - Double.pi / 2)))
31
32                // Current value text
33                Text("\(Int(sliderValue))")
34                    .font(.largeTitle)
35                    .fontWeight(.bold)
36                    .foregroundColor(Color.blue)
37            }
38            .gesture(
39                DragGesture(minimumDistance: 0)
40                    .onChanged { value in
41                        let angleInRadians = atan2(value.location.y - circleCenter.y, value.location.x - circleCenter.x)
42                        angle = Angle(radians: Double(angleInRadians) + Double.pi / 2)
43
44                        let percent = (angle.degrees + 90) / 360
45                        sliderValue = max(min(percent * 100, 100), 0)
46                    }
47            )
48        }
49        .aspectRatio(1, contentMode: .fit)
50    }
51}
52
53struct ContentView: View {
54    var body: some View {
55        CircularSlider()
56            .padding()
57    }
58}
59
60struct ContentView_Previews: PreviewProvider {
61    static var previews: some View {
62        ContentView()
63    }
64}

This is really close. The yellow circle for the thumb indicator tracks the gesture and moves back and forth with the gesture. The issue is the progress of the slider is off by an angle of 90 degrees. The thumb also jumps to any position on the circular slider on the initialization of a drag gesture. It is good to see the progress color on the circular slider updating in real time and the and to see the value in the center updating in synch. It is a bit disconcerting to see the progress is always ahead of the expected progress. In addition, the thumb circle is displayed inside the circular slider rather than on top.


Circular Slider 4 with thumb and progress off by 90 degrees - code generated by ChatGPT 4


Circular Slider 4 with thumb and progress off by 90 degrees - code generated by ChatGPT 4



Regenerate response - Circular Slider 5

Once again, select Regenerate response to see another version.

Prompt to ChatGPT

PROMPT:  
Write the swiftui code to create a circular slider. The circle should have a grey
background and progress is shown with the color turning blue when complete. the
progress is measured in a clockwise direction with a starting value of zero at 12
o clock and progress clockwise to an end value of 100. A drag gesture should be
used to interact with the circular slider. The thumb indicator should be a yellow
circle on the circular slider. The current Slider value should be displayed in
large text in the centre of the circular slider

Response from chatGPT

To create a custom SwiftUI circular slider, you can use a combination of shapes, gestures, and SwiftUI views. Here's a starting point for your circular slider:

CircularSlider and ContentView

 1import SwiftUI
 2
 3struct CircularSlider: View {
 4    @State private var sliderValue: Double = 0
 5    private let sliderRange = 0.0...100.0
 6
 7    var body: some View {
 8        ZStack {
 9            Circle()
10                .stroke(Color.gray, lineWidth: 20)
11            
12            Circle()
13                .trim(from: 0, to: CGFloat(sliderValue / 100))
14                .stroke(Color.blue, lineWidth: 20)
15                .rotationEffect(.degrees(-90))
16            
17            Circle()
18                .fill(Color.yellow)
19                .frame(width: 30, height: 30)
20                .offset(y: -150)
21                .rotationEffect(.degrees(-90 + sliderValue * 3.6))
22
23            Text("\(Int(sliderValue))")
24                .font(.largeTitle)
25                .fontWeight(.bold)
26        }
27        .frame(width: 300, height: 300)
28        .gesture(
29            DragGesture(minimumDistance: 0)
30                .onChanged { value in
31                    let vector = CGVector(dx: value.location.x - 150, dy: value.location.y - 150)
32                    let angle = atan2(vector.dy, vector.dx) + .pi / 2
33                    let value = angle >= 0 ? angle : angle + 2 * .pi
34                    sliderValue = max(sliderRange.lowerBound, min(sliderRange.upperBound, Double(value) * 100 / (2 * .pi)))
35                }
36        )
37    }
38}
39
40struct ContentView: View {
41    var body: some View {
42        CircularSlider()
43    }
44}
45
46struct ContentView_Previews: PreviewProvider {
47    static var previews: some View {
48        ContentView()
49    }
50}

This code defines a CircularSlider view that has a gray background circle and a blue progress circle, with a yellow draggable thumb indicator. The current value of the slider is displayed in large text in the center. The slider value ranges from 0 to 100, with 0 at the 12 o'clock position and progress going clockwise.

This is also really close, except in this version the progress is matching the touch position in the drag gesture and it is the thumb that is lagging behind by 90 degrees. Circular Slider 5 is arguably better than Circular Slider 4 in a number of ways. For the first time ehe thumb is placed centered over the top of the circular slider. It also feels better to use when the progress matches the touch position. However, the thumb position is off by 90 degrees.


Circular Slider 5 with drag matching progress - code generated by ChatGPT 4


Circular Slider 5 with drag matching progress - code generated by ChatGPT 4



Regenerate response - Circular Slider 6

Once again, select Regenerate response to see another version.

Prompt to ChatGPT

PROMPT:  
Write the swiftui code to create a circular slider. The circle should have a grey
background and progress is shown with the color turning blue when complete. the
progress is measured in a clockwise direction with a starting value of zero at 12
o clock and progress clockwise to an end value of 100. A drag gesture should be
used to interact with the circular slider. The thumb indicator should be a yellow
circle on the circular slider. The current Slider value should be displayed in
large text in the centre of the circular slider

Response from chatGPT

To create a circular slider in SwiftUI, you can use the following code snippet. This code demonstrates how to create a custom view that acts as a circular slider with the specified properties. Make sure to import SwiftUI to use this code:

CircularSlider and ContentView

 1import SwiftUI
 2
 3struct CircularSlider: View {
 4    @State private var currentValue: CGFloat = 0
 5    
 6    var body: some View {
 7        GeometryReader { geometry in
 8            let radius = min(geometry.size.width, geometry.size.height) / 2
 9            
10            ZStack {
11                Circle()
12                    .stroke(Color.gray, lineWidth: 10)
13                
14                Circle()
15                    .trim(from: 0, to: currentValue / 100)
16                    .stroke(Color.blue, lineWidth: 10)
17                    .rotationEffect(.degrees(-90))
18                
19                Circle()
20                    .fill(Color.yellow)
21                    .frame(width: 20, height: 20)
22                    .offset(x: 0, y: -radius + 10)
23                    .rotationEffect(.degrees(Double(currentValue / 100) * 360 - 90))
24                    .gesture(
25                        DragGesture(minimumDistance: 0)
26                            .onChanged { value in
27                                let vector = CGVector(dx: value.location.x - geometry.size.width / 2, dy: value.location.y - geometry.size.height / 2)
28                                let angle = atan2(vector.dy, vector.dx) + .pi / 2
29                                let fixedAngle = angle < 0 ? angle + 2 * .pi : angle
30                                currentValue = min(max(0, fixedAngle / (2 * .pi) * 100), 100)
31                            }
32                    )
33                
34                Text("\(Int(currentValue))")
35                    .font(.system(size: 50))
36                    .fontWeight(.bold)
37                    .foregroundColor(.black)
38            }
39        }
40        .aspectRatio(1, contentMode: .fit)
41    }
42}
43
44struct ContentView: View {
45    var body: some View {
46        CircularSlider()
47            .padding()
48    }
49}
50
51struct ContentView_Previews: PreviewProvider {
52    static var previews: some View {
53        ContentView()
54    }
55}

This code creates a custom CircularSlider view that displays a circular slider with a grey background, blue progress, and a yellow thumb indicator. The slider value is displayed in the center of the circular slider. The slider uses a drag gesture to interact with it, and you can adjust the progress value from 0 to 100 by dragging the thumb indicator.


This is worse than previous versions. This is reassuring in some ways, as I was wondering why ChatGPT was getting better simply by selecting Regenerate response. I would expect that without any feedback, there is no way for ChatGPT to improve.


Circular Slider 6 with drag not working - code generated by ChatGPT 4


Circular Slider 6 with drag not working - code generated by ChatGPT 4




Regenerate response - Circular Slider 7

Once again, select Regenerate response to see another version.

Prompt to ChatGPT

PROMPT:  
Write the swiftui code to create a circular slider. The circle should have a grey
background and progress is shown with the color turning blue when complete. the
progress is measured in a clockwise direction with a starting value of zero at 12
o clock and progress clockwise to an end value of 100. A drag gesture should be
used to interact with the circular slider. The thumb indicator should be a yellow
circle on the circular slider. The current Slider value should be displayed in
large text in the centre of the circular slider

Response from chatGPT

To create a circular slider in SwiftUI, you'll need to create a custom view that combines a few different elements. Here's an example implementation of the circular slider you described:

CircularSlider with extension on CGPoint

 1import SwiftUI
 2
 3struct CircularSlider: View {
 4    @State private var sliderValue: Double = 0
 5    @GestureState private var dragOffset: CGFloat = 0
 6    
 7    var body: some View {
 8        GeometryReader { geometry in
 9            let radius = min(geometry.size.width, geometry.size.height) / 2
10            let dragGesture = DragGesture()
11                .updating($dragOffset) { value, state, _ in
12                    state = angleBetweenPoints(radius: radius, center: geometry.frame(in: .local).midPoint, point: value.location)
13                }
14                .onEnded { value in
15                    sliderValue = Double(angleBetweenPoints(radius: radius, center: geometry.frame(in: .local).midPoint, point: value.location)) * 100 / 360
16                }
17            
18            ZStack {
19                // Grey background circle
20                Circle()
21                    .stroke(Color.gray, lineWidth: 20)
22                
23                // Blue progress circle
24                Circle()
25                    .trim(from: 0, to: CGFloat(sliderValue / 100))
26                    .stroke(Color.blue, lineWidth: 20)
27                    .rotationEffect(.degrees(-90))
28                
29                // Yellow thumb indicator
30                Circle()
31                    .fill(Color.yellow)
32                    .frame(width: 20, height: 20)
33                    .offset(x: 0, y: -radius + 10)
34                    .rotationEffect(.degrees(-90 + sliderValue * 360 / 100))
35                
36                // Slider value text
37                Text("\(Int(sliderValue))")
38                    .font(.system(size: 50, design: .rounded))
39            }
40            .gesture(dragGesture)
41        }
42    }
43    
44    private func angleBetweenPoints(radius: CGFloat, center: CGPoint, point: CGPoint) -> CGFloat {
45        let angle = atan2(point.y - center.y, point.x - center.x)
46        let degree = angle >= 0 ? Double(angle) : (2 * Double.pi + Double(angle))
47        return CGFloat(degree * 180 / Double.pi)
48    }
49}
50
51extension CGPoint {
52    var midPoint: CGPoint {
53        return CGPoint(x: x / 2, y: y / 2)
54    }
55}
56
57struct CircularSlider_Previews: PreviewProvider {
58    static var previews: some View {
59        CircularSlider()
60            .frame(width: 300, height: 300)
61    }
62}

This code defines a CircularSlider view that displays a circular slider with a grey background, blue progress circle, yellow thumb indicator, and a centered text showing the current slider value. The slider accepts a drag gesture to interact with it and update its value.


There is a compile error in this code with midPoint being called on CGRect and the property is added as an extension on CGPoint


PROMPT:  
Compile errors - "Value of type 'CGRect' has no member 'midPoint'"

Response from chatGPT

Apologies for the confusion. It seems I made a mistake in the extension for CGPoint. Please use the following code instead:

CircularSlider and ContentView

 1import SwiftUI
 2
 3struct CircularSlider: View {
 4    @State private var sliderValue: Double = 0
 5    @GestureState private var dragOffset: CGFloat = 0
 6    
 7    var body: some View {
 8        GeometryReader { geometry in
 9            let radius = min(geometry.size.width, geometry.size.height) / 2
10            let dragGesture = DragGesture()
11                .updating($dragOffset) { value, state, _ in
12                    state = angleBetweenPoints(radius: radius, center: geometry.frame(in: .local).center, point: value.location)
13                }
14                .onEnded { value in
15                    sliderValue = Double(angleBetweenPoints(radius: radius, center: geometry.frame(in: .local).center, point: value.location)) * 100 / 360
16                }
17            
18            ZStack {
19                // Grey background circle
20                Circle()
21                    .stroke(Color.gray, lineWidth: 20)
22                
23                // Blue progress circle
24                Circle()
25                    .trim(from: 0, to: CGFloat(sliderValue / 100))
26                    .stroke(Color.blue, lineWidth: 20)
27                    .rotationEffect(.degrees(-90))
28                
29                // Yellow thumb indicator
30                Circle()
31                    .fill(Color.yellow)
32                    .frame(width: 20, height: 20)
33                    .offset(x: 0, y: -radius + 10)
34                    .rotationEffect(.degrees(-90 + sliderValue * 360 / 100))
35                
36                // Slider value text
37                Text("\(Int(sliderValue))")
38                    .font(.system(size: 50, design: .rounded))
39            }
40            .gesture(dragGesture)
41        }
42    }
43    
44    private func angleBetweenPoints(radius: CGFloat, center: CGPoint, point: CGPoint) -> CGFloat {
45        let angle = atan2(point.y - center.y, point.x - center.x)
46        let degree = angle >= 0 ? Double(angle) : (2 * Double.pi + Double(angle))
47        return CGFloat(degree * 180 / Double.pi)
48    }
49}
50
51extension CGRect {
52    var center: CGPoint {
53        return CGPoint(x: midX, y: midY)
54    }
55}
56
57struct CircularSlider_Previews: PreviewProvider {
58    static var previews: some View {
59        CircularSlider()
60            .frame(width: 300, height: 300)
61    }
62}

In this corrected code, I changed the extension from CGPoint to CGRect and replaced the "midPoint" property with "center" property, which calculates the center point of the CGRect correctly.


This code compiles and runs. It looks and behaves worse than it is. The progress is off by 90 degrees from the touch point and the thumb is off by 180 degrees from the touch point. The progress and the thumb are not update until the drag gesture is complete, which gives the impression that it is not working at all. Circular Slider 7 is still worse than Circular Slider 5.


Circular Slider 7 with progress and thumb off and drag not animated - code generated by ChatGPT 4


Circular Slider 7 with progress and thumb off and drag not animated - code generated by ChatGPT 4



Regenerate response - Circular Slider 8

Once again, because we had to prompt to correct the compile errror, we need to edit the original prompt and select Save & Submit to regenerate a response to see another version.

Prompt to ChatGPT

PROMPT:  
Write the swiftui code to create a circular slider. The circle should have a grey
background and progress is shown with the color turning blue when complete. the
progress is measured in a clockwise direction with a starting value of zero at 12
o clock and progress clockwise to an end value of 100. A drag gesture should be
used to interact with the circular slider. The thumb indicator should be a yellow
circle on the circular slider. The current Slider value should be displayed in
large text in the centre of the circular slider

Response from chatGPT

To create a custom circular slider in SwiftUI, you can create a reusable CircularSlider view. Here's the complete code for the slider:

CircularSlider and ContentView

 1import SwiftUI
 2
 3struct CircularSlider: View {
 4    @Binding var value: Double
 5    let startAngle: Double = -90
 6    let lineWidth: CGFloat = 10
 7    
 8    var body: some View {
 9        ZStack {
10            Circle()
11                .stroke(Color.gray, style: StrokeStyle(lineWidth: lineWidth))
12            
13            Circle()
14                .trim(from: 0, to: CGFloat(value / 100))
15                .stroke(Color.blue, style: StrokeStyle(lineWidth: lineWidth, lineCap: .round))
16                .rotationEffect(Angle(degrees: startAngle))
17            
18            Circle()
19                .fill(Color.yellow)
20                .frame(width: lineWidth * 2, height: lineWidth * 2)
21                .offset(y: -lineWidth * 5)
22                .rotationEffect(Angle(degrees: startAngle + value * 360 / 100))
23            
24            Text(String(format: "%.0f", value))
25                .font(.system(size: 60))
26                .fontWeight(.bold)
27                .foregroundColor(.black)
28        }
29        .frame(width: lineWidth * 10, height: lineWidth * 10)
30        .gesture(DragGesture(minimumDistance: 0)
31                    .onChanged { gesture in
32                        let angle = calculateAngle(from: gesture.location)
33                        value = Double(angle + 90) / 360 * 100
34                    })
35    }
36    
37    private func calculateAngle(from point: CGPoint) -> Double {
38        let center = CGPoint(x: lineWidth * 5, y: lineWidth * 5)
39        let angle = atan2(point.y - center.y, point.x - center.x) * 180 / .pi
40        return angle < 0 ? angle + 360 : angle
41    }
42}
43
44struct ContentView: View {
45    @State private var sliderValue: Double = 0
46    
47    var body: some View {
48        VStack {
49            CircularSlider(value: $sliderValue)
50        }
51    }
52}
53
54struct ContentView_Previews: PreviewProvider {
55    static var previews: some View {
56        ContentView()
57    }
58}

This code defines a CircularSlider view that takes a binding value as input. The circle has a gray background, and the progress is shown with the color turning blue when complete. The progress is measured in a clockwise direction, with a starting value of 0 at 12 o'clock and progress clockwise to an end value of 100. The thumb indicator is a yellow circle on the circular slider. The current slider value is displayed in large text in the center of the circular slider.


Once again, probably not better than Circular Slider 5, but better than Circular Slider 7. The progress follows the touch point and is animated. The thumb is off by 90 degrees and the progress values seem to go from 25 to 125 rather than 0 to 100. In addition, the size of the circular slider is too small and the progress value is truncated at the larger values.


Circular Slider 8 - drag matches progress, but thumb is off and values incorrect - code generated by ChatGPT 4


Circular Slider 8 - drag matches progress, but thumb is off and values incorrect - code generated by ChatGPT 4



Regenerate response - Circular Slider 9

Once again, select Regenerate response to see another version.

Prompt to ChatGPT

PROMPT:  
Write the swiftui code to create a circular slider. The circle should have a grey
background and progress is shown with the color turning blue when complete. the
progress is measured in a clockwise direction with a starting value of zero at 12
o clock and progress clockwise to an end value of 100. A drag gesture should be
used to interact with the circular slider. The thumb indicator should be a yellow
circle on the circular slider. The current Slider value should be displayed in
large text in the centre of the circular slider

Response from chatGPT

To create a circular slider in SwiftUI, you can create a custom view that represents the circular slider with the specified requirements. Here's the code:

CircularSlider and ContentView

 1import SwiftUI
 2
 3struct CircularSlider: View {
 4    @State private var sliderValue: Double = 0
 5    private let maxValue: Double = 100
 6    
 7    var body: some View {
 8        ZStack {
 9            Circle()
10                .stroke(Color.gray, lineWidth: 10)
11                .frame(width: 200, height: 200)
12            
13            Circle()
14                .trim(from: 0, to: CGFloat(sliderValue / maxValue))
15                .stroke(Color.blue, lineWidth: 10)
16                .rotation(Angle(degrees: -90))
17                .frame(width: 200, height: 200)
18            
19            Circle()
20                .fill(Color.yellow)
21                .frame(width: 20, height: 20)
22                .offset(y: -100)
23                .rotationEffect(Angle(degrees: (360 * sliderValue / maxValue) - 90))
24                .gesture(
25                    DragGesture(minimumDistance: 0)
26                        .onChanged({ value in
27                            let angle = atan2(value.location.y - 100, value.location.x - 100) + .pi
28                            sliderValue = max(0, min(maxValue, Double(angle) / (2 * .pi) * maxValue))
29                        })
30                )
31            
32            Text("\(Int(sliderValue))")
33                .font(.largeTitle)
34                .fontWeight(.bold)
35                .foregroundColor(.black)
36        }
37    }
38}
39
40struct ContentView: View {
41    var body: some View {
42        CircularSlider()
43            .padding()
44    }
45}
46
47struct ContentView_Previews: PreviewProvider {
48    static var previews: some View {
49        ContentView()
50    }
51}

This code creates a custom CircularSlider view with the following elements:

  1. A gray background circle.
  2. A blue progress circle.
  3. A yellow thumb indicator that moves along the circular slider as the user interacts with it.
  4. A drag gesture that updates the slider value.
  5. A text view in the center of the circular slider displaying the current slider value.

You can use this custom CircularSlider view in your app by including it in your SwiftUI view hierarchy.


Once again, this has the compile error for use of rotation instead of rotationEffect. Correcting this error gets Circular Slider 9 displaying, but the drag behavior is weird. Circular Slider 9 is not better than Circular Slider 5.


Circular Slider 9 - odd drag behavior with progress animated - code generated by ChatGPT 4


Circular Slider 9 - odd drag behavior with progress animated - code generated by ChatGPT 4



Regenerate response - Circular Slider 10

Once again, select Regenerate response to see another version.

Prompt to ChatGPT

PROMPT:  
Write the swiftui code to create a circular slider. The circle should have a grey
background and progress is shown with the color turning blue when complete. the
progress is measured in a clockwise direction with a starting value of zero at 12
o clock and progress clockwise to an end value of 100. A drag gesture should be
used to interact with the circular slider. The thumb indicator should be a yellow
circle on the circular slider. The current Slider value should be displayed in
large text in the centre of the circular slider

Response from chatGPT

To create a circular slider in SwiftUI, you will need to create a custom component that combines a few built-in SwiftUI views and modifiers. The code below demonstrates how to create a circular slider with the specified requirements:

CircularProgressView and ContentView

 1import SwiftUI
 2
 3struct CircularProgressView: View {
 4    @Binding var progress: Double
 5    let circleWidth: CGFloat = 30
 6    
 7    var body: some View {
 8        ZStack {
 9            Circle()
10                .stroke(lineWidth: circleWidth)
11                .fill(Color.gray)
12            
13            Circle()
14                .trim(from: 0.0, to: CGFloat(min(self.progress, 1.0)))
15                .stroke(lineWidth: circleWidth)
16                .fill(Color.blue)
17                .rotationEffect(Angle(degrees: -90))
18            
19            Text("\(Int(progress * 100))")
20                .font(.system(size: 50))
21                .fontWeight(.bold)
22            
23            Circle()
24                .fill(Color.yellow)
25                .frame(width: circleWidth, height: circleWidth)
26                .offset(y: -100)
27                .rotationEffect(Angle(degrees: 360 * progress - 90))
28        }
29        .gesture(DragGesture(minimumDistance: 0)
30                    .onChanged(updateProgress(from:to:)))
31    }
32    
33    private func updateProgress(from startPoint: DragGesture.Value, to endPoint: DragGesture.Value) {
34        let width = endPoint.location.x - startPoint.location.x
35        let height = endPoint.location.y - startPoint.location.y
36        let distanceFromCenter = sqrt(width * width + height * height)
37        
38        if distanceFromCenter > 25 && distanceFromCenter < 150 {
39            let angle = atan2(height, width)
40            let progress = 1.0 - Double(angle / (2 * CGFloat.pi))
41            self.progress = (progress + 0.25).truncatingRemainder(dividingBy: 1.0)
42        }
43    }
44}
45
46struct ContentView: View {
47    @State private var progress: Double = 0
48    
49    var body: some View {
50        CircularProgressView(progress: $progress)
51            .frame(width: 200, height: 200)
52            .padding()
53    }
54}
55
56struct ContentView_Previews: PreviewProvider {
57    static var previews: some View {
58        ContentView()
59    }
60}

This code defines a custom CircularProgressView that takes a binding to a progress value between 0 and 1. The slider is rendered using two circles, one for the background and one for the progress, along with a text view to display the current progress value. The thumb indicator is created using another circle view that rotates based on the progress value. A drag gesture is added to allow interaction with the slider.


This code has a compile error, so we fee that back to ChatGPT.

Prompt to ChatGPT

PROMPT:  
compile error on line ".onChanged(updateProgress(from:to:)))" - error = "Cannot
convert value of type '(DragGesture.Value, DragGesture.Value) -> ()' to expected
argument type '(DragGesture.Value) -> Void'"

Response from chatGPT

Apologies for the confusion. The error is due to a mistake in the syntax of the DragGesture onChanged closure. Here's the corrected version of the CircularProgressView:

code updates to CircularProgressView

 1    // ...
 2        .gesture(DragGesture(minimumDistance: 0)
 3            .onChanged { value in updateProgress(from: value.startLocation, to: value.location) })    
 4    // ...
 5    
 6    private func updateProgress(from startPoint: CGPoint, to endPoint: CGPoint) {
 7        let width = endPoint.x - startPoint.x
 8        let height = endPoint.y - startPoint.y
 9        let distanceFromCenter = sqrt(width * width + height * height)
10        
11        if distanceFromCenter > 25 && distanceFromCenter < 150 {
12            let angle = atan2(height, width)
13            let progress = 1.0 - Double(angle / (2 * CGFloat.pi))
14            self.progress = (progress + 0.25).truncatingRemainder(dividingBy: 1.0)
15        }
16    }
17
18    // ...    

The change is in the .gesture modifier. Instead of using updateProgress(from:to:), we use a closure { value in updateProgress(from: value.startLocation, to: value.location) } to correctly handle the onChanged event. This should resolve the compile error.


This does resolve the compile error and Circular Slider 10 is displayed, however, the drag gesture results in odd behavior off the circular slider and does not appear to be any better than previous versions.


Circular Slider 10 - odd drag behavior with progress animated - code generated by ChatGPT 4


Circular Slider 10 - odd drag behavior with progress animated - code generated by ChatGPT 4



Work with Circular Slider 5

All of the code from ChatGPT is from one chat, so we look to go back to the answer that gave us the code for Circular Slider 5 to see if we can improve this rather than generating new samples. In the chat we can see that the prompt has been adjusted 4 times and each promot has given multiple responses. Navigate to the third prompt (3/4) and the fourth response to this prompt (4/6) to get to the code for Circular Slider 5.


Navigate to a previous prompt and response from ChatGPT 4


Prompt to ChatGPT

PROMPT:  
This is really close. The progress matches the drag gesture, but the thumb is off
by an angle of 90 degrees in an anticlockwise direction. So when the progress is
at 3 o clock the thumb is at 12 o clock. can the code be adjusted so the thumb
tracks with the progress and the drag gesture?

Response from chatGPT

I apologize for the mistake in my previous response. Here is the updated code with the thumb position corrected:

Update to CircularSlider

1// ...
2            Circle()
3                .fill(Color.yellow)
4                .frame(width: 30, height: 30)
5                .offset(y: -150)
6                .rotationEffect(.degrees(sliderValue * 3.6 - 90))
7// ...                

The only change is in the rotation effect of the thumb indicator:

1.rotationEffect(.degrees(sliderValue * 3.6 - 90))

Now, the thumb should correctly track with the progress and the drag gesture.


This does not change anything as the change is from (-90 + sliderValue * 3.6) to (sliderValue * 3.6 - 90). The correct change would be to remove the -90 and the thumb would match the sliderValue.


Let's try a Regenerate response to see another response.

Prompt to ChatGPT

PROMPT:  
This is really close. The progress matches the drag gesture, but the thumb is off
by an angle of 90 degrees in an anticlockwise direction. So when the progress is
at 3 o clock the thumb is at 12 o clock. can the code be adjusted so the thumb
tracks with the progress and the drag gesture?

Response from chatGPT

I apologize for the confusion. To fix the issue with the thumb tracking, you can adjust the rotation effect of the thumb indicator circle. Here's the updated code with the correct thumb position:

Update to CircularSlider

1// ...
2            Circle()
3                .fill(Color.yellow)
4                .frame(width: 30, height: 30)
5                .offset(y: -150)
6                .rotationEffect(.degrees(sliderValue * 3.6 - 90))
7// ...                

In this updated code, I modified the rotation effect of the thumb indicator circle from .rotationEffect(.degrees(-90 + sliderValue * 3.6)) to .rotationEffect(.degrees(sliderValue * 3.6 - 90)). Now the thumb should track correctly with the progress and the drag gesture.


This is fascinating, wrong, but fascinating. ChatGPT has given the exact same response and even highlighted the change. But the math logic in the change shows that there is no change.


Let's try and be more suggestive in the correction.

Prompt to ChatGPT

PROMPT:  
This change does not fix the issue. perhaps the "- 90" could be removed from the
rotationEffect on the thumb as we want the thumb to track with the progress and
the drag gesture?

Response from chatGPT

You're right, my apologies for the confusion. The rotation effect for the thumb indicator should match the rotation of the progress circle. Here's the corrected code with the appropriate rotation effect:

Update to CircularSlider

1// ...
2            Circle()
3                .fill(Color.yellow)
4                .frame(width: 30, height: 30)
5                .offset(y: -150)
6                .rotationEffect(.degrees(sliderValue * 3.6)) // Corrected rotation effect
7// ...                

In this updated code, I modified the rotation effect of the thumb indicator circle from .rotationEffect(.degrees(sliderValue * 3.6 - 90)) to .rotationEffect(.degrees(sliderValue * 3.6)). Now the thumb should correctly track the progress and the drag gesture.


What an inspired answer from ChatGPT! This does fix the issue and the CircularSlider works as expected.


Circular Slider 5b - with drag gesture behavior as expected - code generated by ChatGPT 4


Circular Slider 5b - with drag gesture behavior as expected - code generated by ChatGPT 4



Make the size adjustable

The CircularSlider works, but it has its size fixed within CircularSlider5a to 300 by 300. This is very limiting for a UI control and I just can't leave it.

Let's ask ChatGPT for help.

Prompt to ChatGPT

PROMPT:  
This circular slider works great, but the size is fixed at 300 by 300. could the
CircularSlider be adjusted to work for any size so that it could be used in
different views?

Response from chatGPT

Sure, you can modify the CircularSlider view to accept a custom size parameter and adjust its internal components accordingly. Here's the updated code that allows you to set a custom size for the circular slider:


updates to CircularSlider and ContentView

 1import SwiftUI
 2
 3struct CircularSlider: View {
 4    @State private var sliderValue: Double = 0
 5    private let sliderRange = 0.0...100.0
 6    let size: CGFloat
 7
 8    var body: some View {
 9        ZStack {
10            Circle()
11                .stroke(Color.gray, lineWidth: size * 0.067)
12            
13            Circle()
14                .trim(from: 0, to: CGFloat(sliderValue / 100))
15                .stroke(Color.blue, lineWidth: size * 0.067)
16                .rotationEffect(.degrees(-90))
17            
18            Circle()
19                .fill(Color.yellow)
20                .frame(width: size * 0.1, height: size * 0.1)
21                .offset(y: -size * 0.5)
22                .rotationEffect(.degrees(sliderValue * 3.6))
23
24            Text("\(Int(sliderValue))")
25                .font(.system(size: size * 0.2))
26                .fontWeight(.bold)
27        }
28        .frame(width: size, height: size)
29        .gesture(
30            DragGesture(minimumDistance: 0)
31                .onChanged { value in
32                    let vector = CGVector(dx: value.location.x - size / 2, dy: value.location.y - size / 2)
33                    let angle = atan2(vector.dy, vector.dx) + .pi / 2
34                    let value = angle >= 0 ? angle : angle + 2 * .pi
35                    sliderValue = max(sliderRange.lowerBound, min(sliderRange.upperBound, Double(value) * 100 / (2 * .pi)))
36                }
37        )
38    }
39}
40
41struct ContentView: View {
42    var body: some View {
43        CircularSlider(size: 200) // Set the custom size here
44    }
45}
46
47struct ContentView_Previews: PreviewProvider {
48    static var previews: some View {
49        ContentView()
50    }
51}

In this version, the CircularSlider view has a new size parameter. I've adjusted the lineWidth, frame, offset, and font size of the internal components based on the custom size. You can now use the CircularSlider(size: CGFloat) initializer to create sliders of different sizes. For example, to create a slider with a size of 200, you can call CircularSlider(size: 200).


This is one approach, where the size is passed into the CircularSlider and this allows the use of the CircularSlider in different sizes to match a particular UI layout. A nice touch here is the adjustment of the font size of the progress value displayed in the center of the CircularSlider. This is set as a factor of the size passed into the CircularSlider.


Multiple Circular Sliders of different sizes - code generated by ChatGPT 4


Multiple Circular Sliders of different sizes - code generated by ChatGPT 4




Conclusion

I was looking for ChatGPT to generate SwiftUI code for a circular slider. I asked ChatGPT to regenerate its response 10 times and none of the samples of code just worked as expected. Some samples came very close, so I focussed on one sample (Circular Slider 5) and prompted ChatGPT to address the issue of the thumb not matching the progress. This failed twice until I prompted ChatGPT with the fix, which it agreed with and generated the code.

In a number of samples, the first iteration of code does not compile, but ChatGPT was able to resolve these issues when the details of the compile error are fed back to it. There are lessons to learn from the different samples, such as some of the samples of code generated put the drag gesture on the thumb circle, while the ones with better behavior have the drag gesture on the overall ZStack for the circular slider.

There are still some outstanding issues regarding overall size of the Circular Slider and how it will extend outside of the frame in which it is placed, but this could be addressed in a number of ways.

The final code has the same calculations as in my original article on Create a circular slider in SwiftUI, but arguably the code is neater. Although not commented and may be more difficult to read, debug and maintain.

Overall, ChatGPT cannot just generate code that can be used without question, but it can generate code that can be examined, reviewed and tweaked to work in SwiftUI development.

All this code was generated with ChatGPT version 4 - so it will be interesting to see where this goes in the next version of ChatGPT.

The source code for CircularSliderWithChatGpt4 is available on GitHub.