Custom graphics and visualizations are powerful ways to enhance your iOS apps. SwiftUI provides several approaches for drawing lines and custom shapes, from simple straight lines to complex curved paths. In this guide, we'll explore various techniques for drawing lines in SwiftUI, with practical examples you can incorporate into your own projects.

Drawing Lines with Path

The most fundamental way to draw lines in SwiftUI is using the Path struct, which allows you to create custom shapes by defining a series of points and connecting them with lines:

BasicLineExample.swift
struct BasicLineExample: View {
    var body: some View {
        Path { path in
            path.move(to: CGPoint(x: 50, y: 50))
            path.addLine(to: CGPoint(x: 350, y: 250))
        }
        .stroke(Color.blue, lineWidth: 3)
        .frame(width: 400, height: 300)
        .background(Color.gray.opacity(0.1))
    }
}

This example creates a simple diagonal line from point (50, 50) to point (350, 250). The stroke modifier applies a blue color and sets the line width to 3 points.

Customizing Line Appearance

SwiftUI offers several ways to customize the appearance of lines:

CustomizedLineExample.swift
struct CustomizedLineExample: View {
    var body: some View {
        VStack(spacing: 30) {
            // Solid line with rounded caps
            Path { path in
                path.move(to: CGPoint(x: 50, y: 0))
                path.addLine(to: CGPoint(x: 350, y: 0))
            }
            .stroke(
                Color.blue,
                style: StrokeStyle(
                    lineWidth: 10,
                    lineCap: .round
                )
            )
            
            // Dashed line
            Path { path in
                path.move(to: CGPoint(x: 50, y: 0))
                path.addLine(to: CGPoint(x: 350, y: 0))
            }
            .stroke(
                Color.green,
                style: StrokeStyle(
                    lineWidth: 3,
                    lineCap: .butt,
                    lineJoin: .miter,
                    dash: [10, 5]
                )
            )
            
            // Gradient line
            Path { path in
                path.move(to: CGPoint(x: 50, y: 0))
                path.addLine(to: CGPoint(x: 350, y: 0))
            }
            .stroke(
                LinearGradient(
                    gradient: Gradient(colors: [.red, .blue]),
                    startPoint: .leading,
                    endPoint: .trailing
                ),
                lineWidth: 5
            )
        }
        .frame(width: 400, height: 200)
        .padding()
    }
}

This example demonstrates three different line styles:

  • A solid blue line with rounded caps
  • A dashed green line with custom dash pattern
  • A gradient line that transitions from red to blue

Creating Custom Line Shapes

For more reusable line drawings, you can create custom shapes by conforming to the Shape protocol:

CustomLineShape.swift
struct DiagonalLine: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()
        path.move(to: CGPoint(x: rect.minX, y: rect.minY))
        path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
        return path
    }
}

struct CustomLineShapeExample: View {
    var body: some View {
        VStack(spacing: 30) {
            // Using the custom shape
            DiagonalLine()
                .stroke(Color.purple, lineWidth: 3)
                .frame(width: 300, height: 200)
            
            // Reusing the shape with different styling
            DiagonalLine()
                .stroke(Color.orange, style: StrokeStyle(
                    lineWidth: 5,
                    lineCap: .round,
                    dash: [10, 5]
                ))
                .frame(width: 300, height: 200)
        }
        .padding()
    }
}

The advantage of this approach is that shapes automatically adapt to the available space and can be reused throughout your app with different styles.

Drawing Multiple Connected Lines

To draw more complex line patterns, you can connect multiple lines into a single path:

MultiLineExample.swift
struct MultiLineExample: View {
    var body: some View {
        Path { path in
            // Starting point
            path.move(to: CGPoint(x: 50, y: 50))
            
            // Draw lines to form a zigzag pattern
            path.addLine(to: CGPoint(x: 100, y: 150))
            path.addLine(to: CGPoint(x: 150, y: 50))
            path.addLine(to: CGPoint(x: 200, y: 150))
            path.addLine(to: CGPoint(x: 250, y: 50))
            path.addLine(to: CGPoint(x: 300, y: 150))
        }
        .stroke(Color.blue, lineWidth: 3)
        .frame(width: 350, height: 200)
        .background(Color.gray.opacity(0.1))
        .padding()
    }
}

This creates a zigzag pattern by connecting multiple line segments in sequence.

Drawing Curved Lines

SwiftUI also supports drawing curved lines using Bézier curves:

CurvedLineExample.swift
struct CurvedLineExample: View {
    var body: some View {
        VStack(spacing: 30) {
            // Quadratic curve
            Path { path in
                path.move(to: CGPoint(x: 50, y: 100))
                path.addQuadCurve(
                    to: CGPoint(x: 300, y: 100),
                    control: CGPoint(x: 175, y: 0)
                )
            }
            .stroke(Color.blue, lineWidth: 3)
            .frame(height: 150)
            
            // Cubic curve
            Path { path in
                path.move(to: CGPoint(x: 50, y: 100))
                path.addCurve(
                    to: CGPoint(x: 300, y: 100),
                    control1: CGPoint(x: 100, y: 0),
                    control2: CGPoint(x: 250, y: 200)
                )
            }
            .stroke(Color.green, lineWidth: 3)
            .frame(height: 150)
        }
        .padding()
        .background(Color.gray.opacity(0.1))
    }
}

This example demonstrates:

  • A quadratic Bézier curve with one control point
  • A cubic Bézier curve with two control points

Drawing Lines from Data Points

A common use case for drawing lines is visualizing data. Here's how to draw a line chart from a data array:

DataLineChartExample.swift
struct DataLineChartExample: View {
    let dataPoints = [80, 40, 110, 75, 90, 140, 60]
    
    var body: some View {
        LineChart(dataPoints: dataPoints)
            .stroke(Color.blue, lineWidth: 3)
            .frame(height: 200)
            .padding()
            .background(Color.gray.opacity(0.1))
    }
}

struct LineChart: Shape {
    var dataPoints: [Double]
    
    func path(in rect: CGRect) -> Path {
        var path = Path()
        
        // Find the maximum value for scaling
        guard let maxValue = dataPoints.max() else {
            return path
        }
        
        // Calculate x step based on the number of data points
        let xStep = rect.width / CGFloat(dataPoints.count - 1)
        
        // Start at the first data point
        guard dataPoints.count > 0 else {
            return path
        }
        
        // Scale the first y-value to fit the rect height
        let firstPoint = CGPoint(
            x: rect.minX,
            y: rect.height - CGFloat(dataPoints[0]) / CGFloat(maxValue) * rect.height
        )
        
        path.move(to: firstPoint)
        
        // Add lines to each data point
        for index in 1..let x = rect.minX + CGFloat(index) * xStep
            let y = rect.height - CGFloat(dataPoints[index]) / CGFloat(maxValue) * rect.height
            path.addLine(to: CGPoint(x: x, y: y))
        }
        
        return path
    }
}

This example creates a reusable LineChart shape that scales data points to fit within the available space. The data points are connected with straight lines to create a simple line chart.

Drawing Dynamic Lines with Animation

SwiftUI's animation system allows you to create dynamic, animated lines:

AnimatedLineExample.swift
struct AnimatedLineExample: View {
    @State private var percentage: CGFloat = 0
    
    var body: some View {
        VStack {
            AnimatedLine(percentage: percentage)
                .stroke(Color.blue, lineWidth: 4)
                .frame(height: 200)
                .padding()
                .background(Color.gray.opacity(0.1))
            
            Button("Animate") {
                withAnimation(Animation.easeInOut(duration: 2.0)) {
                    percentage = percentage == 1.0 ? 0.0 : 1.0
                }
            }
            .padding()
        }
    }
}

struct AnimatedLine: Shape {
    var percentage: CGFloat // 0 to 1.0
    
    var animatableData: CGFloat {
        get { percentage }
        set { percentage = newValue }
    }
    
    func path(in rect: CGRect) -> Path {
        var path = Path()
        path.move(to: CGPoint(x: 0, y: rect.midY))
        
        // Calculate end point based on percentage
        let endX = rect.width * percentage
        
        // Create a wavy line
        for x in stride(from: 0, through: endX, by: 1) {
            let relativePosX = x / rect.width
            let sine = sin(relativePosX * .pi * 4)
            let y = rect.midY + sine * rect.height * 0.3
            path.addLine(to: CGPoint(x: x, y: y))
        }
        
        return path
    }
}

This example creates an animated wavy line that grows from left to right when the "Animate" button is tapped. The animatableData property allows SwiftUI to animate the percentage property, creating a smooth animation.

Drawing Interactive Lines

You can also create interactive line drawings that respond to user input:

InteractiveLineExample.swift
struct InteractiveLineExample: View {
    @State private var lines: [Line] = []
    @State private var currentLine: Line? = nil
    
    var body: some View {
        VStack {
            ZStack {
                Rectangle()
                    .fill(Color.white)
                    .border(Color.gray, width: 1)
                
                // Draw all completed lines
                ForEach(lines) { line in
                    Path { path in
                        path.addLines(line.points)
                    }
                    .stroke(line.color, lineWidth: 3)
                }
                
                // Draw the current line being drawn
                if let line = currentLine {
                    Path { path in
                        path.addLines(line.points)
                    }
                    .stroke(line.color, lineWidth: 3)
                }
            }
            .gesture(
                DragGesture(minimumDistance: 0, coordinateSpace: .local)
                    .onChanged { value in
                        let point = value.location
                        if currentLine == nil {
                            currentLine = Line(points: [point], color: .randomColor())
                        } else {
                            currentLine!.points.append(point)
                        }
                    }
                    .onEnded { value in
                        if let line = currentLine {
                            lines.append(line)
                            currentLine = nil
                        }
                    }
            )
            
            Button("Clear") {
                lines = []
                currentLine = nil
            }
            .padding()
        }
        .padding()
    }
}

struct Line: Identifiable {
    var id = UUID()
    var points: [CGPoint]
    var color: Color
}

extension Color {
    static func randomColor() -> Color {
        let colors: [Color] = [.red, .blue, .green, .orange, .purple, .pink]
        return colors.randomElement()!
    }
}

This example creates a simple drawing app where users can draw lines by dragging on the screen. Each stroke is stored as a Line object with an array of points and a random color.

Best Practices for Drawing Lines in SwiftUI

When drawing lines in SwiftUI, keep these best practices in mind:

  1. Use the appropriate abstraction level: Use Path for simple one-off drawings, Shape for reusable shapes, and custom views for complex interactive drawings.
  2. Scale appropriately: Make your lines scale to fit the available space using GeometryReader or percentage-based calculations.
  3. Optimize performance: For complex drawings or animations, consider using fewer points or simplifying the path to improve performance.
  4. Provide accessibility: Add accessibility labels and traits to ensure your custom drawings are accessible to all users.
  5. Use animatable properties: When animating lines, implement animatableData to create smooth transitions.

Conclusion

Drawing lines in SwiftUI offers a powerful way to create custom visualizations, charts, and interactive graphics. By mastering the techniques covered in this guide, you can enhance your iOS apps with custom drawings that are both visually appealing and functional.

Whether you're creating simple straight lines, complex curved paths, or interactive drawing experiences, SwiftUI provides the tools you need to bring your ideas to life. Experiment with these techniques in your own projects to discover the full potential of line drawing in SwiftUI.