Create a color pencil in SwiftUI

SwiftUI can be used to simulate real-world objects, such as selecting a color by selecting a specific colored pencil rather than from a colored rectangle. This can provide a more intuitive user experience, especially in a drawing or note-taking app. This article defines a SwiftUI view for a color pencil using the Path and Shape protocols for the components of the pencil.



Initial Pencil

Start with a pencil composed of three shapes for the pencil tip, the pencil timber and the pencil body. Each of these shapes are defined as Path objects to fit inside their containing frame. First define the x,y coordinates of the desired points inside a 1.0 by 1.0 grid. Then simply multiply the containing rect maxX and maxY to get the specific points. The three shapes are combined together in a ZStack to display the pencil.


PencilTipShape

 1struct PencilTipShape: Shape {
 2    func path(in rect: CGRect) -> Path {
 3        let path = Path { path in
 4            path.move(to: CGPoint(x: 0.470 * rect.maxX, y: 0.035 * rect.maxY))
 5            path.addLine(to: CGPoint(x: 0.530 * rect.maxX, y: 0.020 * rect.maxY))
 6            path.addLine(to: CGPoint(x: 0.670 * rect.maxX, y: 0.106 * rect.maxY))
 7            path.addCurve(to: CGPoint(x: 0.330 * rect.maxX, y: 0.106 * rect.maxY),
 8                          control1: CGPoint(x: 0.580 * rect.maxX, y: 0.120 * rect.maxY),
 9                          control2: CGPoint(x: 0.420 * rect.maxX, y: 0.120 * rect.maxY))
10            path.closeSubpath()
11        }
12        return path
13    }
14}

PencilTimberShape

 1struct PencilTimberShape: Shape {
 2    func path(in rect: CGRect) -> Path {
 3        let path = Path { path in
 4            path.move(to: CGPoint(x: 0.330 * rect.maxX, y: 0.106 * rect.maxY))
 5            path.addCurve(to: CGPoint(x: 0.670 * rect.maxX, y: 0.106 * rect.maxY),
 6                          control1: CGPoint(x: 0.420 * rect.maxX, y: 0.120 * rect.maxY),
 7                          control2: CGPoint(x: 0.580 * rect.maxX, y: 0.120 * rect.maxY))
 8            path.addLine(to: CGPoint(x: 0.980 * rect.maxX, y: 0.270 * rect.maxY))
 9            path.addCurve(to: CGPoint(x: 0.020 * rect.maxX, y: 0.270 * rect.maxY),
10                          control1: CGPoint(x: 0.700 * rect.maxX, y: 0.320 * rect.maxY),
11                          control2: CGPoint(x: 0.300 * rect.maxX, y: 0.320 * rect.maxY))
12            path.closeSubpath()
13        }
14        return path
15    }
16}

PencilBodyShape

 1struct PencilBodyShape: Shape {
 2    func path(in rect: CGRect) -> Path {
 3        let path = Path { path in
 4            path.move(to: CGPoint(x: 0.020 * rect.maxX, y: 0.270 * rect.maxY))
 5            path.addCurve(to: CGPoint(x: 0.980 * rect.maxX, y: 0.270 * rect.maxY),
 6                          control1: CGPoint(x: 0.300 * rect.maxX, y: 0.320 * rect.maxY),
 7                          control2: CGPoint(x: 0.700 * rect.maxX, y: 0.320 * rect.maxY))
 8            path.addLine(to: CGPoint(x: 0.980 * rect.maxX, y: 0.900 * rect.maxY))
 9            path.addCurve(to: CGPoint(x: 0.020 * rect.maxX, y: 0.900 * rect.maxY),
10                          control1: CGPoint(x: 0.700 * rect.maxX, y: 0.950 * rect.maxY),
11                          control2: CGPoint(x: 0.300 * rect.maxX, y: 0.950 * rect.maxY))
12            path.closeSubpath()
13        }
14        return path
15    }
16}

GrayPencilView

 1struct GrayPencilView: View {
 2    var body: some View {
 3        VStack {
 4            Text("Gray Pencil")
 5                .font(.largeTitle)
 6            
 7            ZStack {
 8                PencilTipShape()
 9                    .fill(.gray)
10                PencilTimberShape()
11                    .fill(.orange.opacity(0.3))
12                PencilBodyShape()
13                    .fill(.red.opacity(0.9))
14            }
15            .frame(width: 100, height: 400)
16        }
17    }
18}

Pencil defined using three shapes in SwiftUI



Color Pencil

Create a color pencil SwiftUI view that takes a Color as a parameter and is composed of the three pencil component shapes. The color of the tip and the body are set to the specified color. The color of the timber shape is also changed to remove the opacity as this would not have a consistent color depending on the background.


ColorPencilView1

 1struct ColorPencilView1: View {
 2    var color: Color
 3    
 4    var body: some View {
 5        ZStack {
 6            PencilTipShape()
 7                .fill(color)
 8            PencilTimberShape()
 9                .fill(Color(hue: 0.07, saturation: 0.4, brightness: 0.9))
10            PencilBodyShape()
11                .fill(color)
12        }
13    }
14}

BluePencilView

 1struct BluePencilView: View {
 2    var body: some View {
 3        VStack {
 4            Text("Blue Pencil")
 5                .font(.largeTitle)
 6            
 7            ColorPencilView1(color: .blue)
 8                .frame(width: 100, height: 400)
 9        }
10    }
11}

Color pencil view that takes a color and creates the pencil within the frame



Use Linear Gradient

The color pencil is looking better, but it appears a bit flat. The appearance of 3D can be added by using a color gradient with the pencil getting brighter at one point. A linear gradient can be created with variations of the specified color using getHue function of UIColor as described in Convert color from RGB to HSB in Swift. The gradient is created using the same Hue and changing the Saturation and Brightness to give the impression of light being reflected off the round body of the pencil.


ColorPencilView

 1struct ColorPencilView: View {
 2    var color: Color
 3    
 4    func getColorGradient(_ col: Color) -> LinearGradient {
 5        var hue: CGFloat  = 0.0
 6        var saturation: CGFloat = 0.0
 7        var brightness: CGFloat = 0.0
 8        var alpha: CGFloat = 0.0
 9        
10        let uiColor = UIColor(col)
11        uiColor.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha)
12        
13        return LinearGradient(
14            colors: [
15                Color(hue: hue, saturation: saturation * 1.0, brightness: brightness * 0.4),
16                Color(hue: hue, saturation: saturation * 1.0, brightness: brightness * 0.8),
17                Color(hue: hue, saturation: saturation * 1.0, brightness: brightness * 1.0),
18                Color(hue: hue, saturation: saturation * 0.6, brightness: brightness * 1.0),
19                Color(hue: hue, saturation: saturation * 0.8, brightness: brightness * 0.9)
20            ],
21            startPoint: .leading,
22            endPoint: .trailing)
23    }
24    
25    var body: some View {
26        ZStack {
27            PencilTipShape()
28                .fill(getColorGradient(color))
29            PencilTimberShape()
30                .fill(Color(hue: 0.07, saturation: 0.4, brightness: 0.9))
31            PencilBodyShape()
32                .fill(getColorGradient(color))
33        }
34    }
35}

BlueGradientPencilView

 1struct BlueGradientPencilView: View {
 2    var body: some View {
 3        VStack {
 4            Text("Blue Pencil")
 5                .font(.largeTitle)
 6            Text("Color gradient")
 7                .font(.title)
 8
 9            HStack {
10                ColorPencilView(color: .blue)
11                    .frame(width: 100, height: 400)
12            }
13        }
14    }
15}

Add color gradient to pencil based on specified color using Hue



Rainbow Pencils

Finally we create a set of color pencils for the seven colors of the rainbow.


ColorPencilSetView

 1struct ColorPencilSetView: View {
 2    var body: some View {
 3        VStack {
 4            Text("Rainbow Pencil Set")
 5                .font(.largeTitle)
 6
 7            HStack {
 8                ColorPencilView(color: .red)
 9                ColorPencilView(color: .orange)
10                ColorPencilView(color: .yellow)
11                ColorPencilView(color: .green)
12                ColorPencilView(color: .blue)
13                ColorPencilView(color: Color(hue: 0.773, saturation: 1.0, brightness: 0.51))
14                ColorPencilView(color: Color(hue: 0.771, saturation: 1.0, brightness: 1.00))
15            }
16            .frame(width: 400, height: 300)
17        }
18    }
19}

Create a set of color pencils in SwiftUI




Conclusion

The color pencil is defined as being composed of three shapes for the pencil tip, the pencil timber and the pencil body. A 3D effect is added by getting the hue of the specified color and creating a linear color gradient across the body of the pencil. There are numerous improvements that could be done to the pencil such as making the edges of the timber section more jagged, adding an angled light reflection to the colored tip of the pencil and so on.

The source code for ColorPencilsApp is available on GitHub.