SwiftUI cho phép vẽ tùy chỉnh với hai loại khác nhau một cách tinh tế: đường dẫn và hình dạng. Đường dẫn là một chuỗi các hướng dẫn vẽ chẳng hạn như “bắt đầu ở đây, vẽ một đường thẳng tới đây, sau đó thêm một vòng tròn ở đó”, tất cả đều sử dụng tọa độ tuyệt đối. Ngược lại, một hình dạng không biết nó sẽ được sử dụng ở đâu hoặc nó sẽ được sử dụng lớn như thế nào mà thay vào đó sẽ được yêu cầu vẽ chính nó bên trong một hình chữ nhật nhất định.

Thật hữu ích, các hình dạng được xây dựng bằng cách sử dụng các đường dẫn, vì vậy một khi bạn hiểu được các đường dẫn thì bạn sẽ dễ dàng hiểu được các hình dạng đó. Ngoài ra, giống như đường dẫn, màu sắc và độ chuyển màu, hình dạng là chế độ xem, có nghĩa là chúng ta có thể sử dụng chúng cùng với chế độ xem văn bản, hình ảnh, v.v.

SwiftUI triển khai Shape như một giao thức với một phương thức bắt buộc duy nhất: cho hình chữ nhật sau, bạn muốn vẽ đường dẫn nào? Điều này sẽ vẫn tạo và trả về một đường dẫn giống như sử dụng trực tiếp một đường dẫn thô, nhưng vì chúng ta đã được cung cấp kích thước nên hình dạng sẽ được sử dụng nên chúng ta biết chính xác mức độ lớn để vẽ đường dẫn của mình – chúng ta không còn cần phải dựa vào tọa độ cố định nữa.

Ví dụ: trước đây chúng ta đã tạo một hình tam giác bằng cách sử dụng Path, nhưng chúng ta có thể gói hình tam giác đó vào một hình để đảm bảo nó tự động chiếm toàn bộ không gian có sẵn như thế này:

struct Triangle: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()

        path.move(to: CGPoint(x: rect.midX, y: rect.minY))
        path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
        path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
        path.addLine(to: CGPoint(x: rect.midX, y: rect.minY))

        return path
    }
}

Công việc đó được thực hiện dễ dàng hơn nhiều nhờ CGRect, nó cung cấp các thuộc tính hữu ích như minX(giá trị X nhỏ nhất trong hình chữ nhật), maxX(giá trị X lớn nhất trong hình chữ nhật) và midX(điểm giữa minX và maxX).

Sau đó chúng ta có thể tạo một hình tam giác màu đỏ với kích thước chính xác như thế này:

Triangle()
    .fill(.red)
    .frame(width: 300, height: 300)
Hình dạng cũng hỗ trợ StrokeStyletham số tương tự để tạo các nét nâng cao hơn:

Triangle()
    .stroke(.red, style: StrokeStyle(lineWidth: 10, lineCap: .round, lineJoin: .round))
    .frame(width: 300, height: 300)

Chìa khóa để hiểu sự khác biệt giữa Path và Shape là khả năng sử dụng lại: các đường dẫn được thiết kế để thực hiện một việc cụ thể, trong khi các hình dạng có sự linh hoạt về không gian vẽ và cũng có thể chấp nhận các tham số để cho phép chúng ta tùy chỉnh chúng thêm.

Để chứng minh điều này, chúng ta có thể tạo một Arc hình có ba tham số: góc bắt đầu, góc kết thúc và có vẽ cung theo chiều kim đồng hồ hay không. Điều này có vẻ đơn giản, đặc biệt là vì Path có một addArc() phương pháp, nhưng như bạn sẽ thấy nó có một số điểm kỳ quặc thú vị.

Hãy bắt đầu với phiên bản đơn giản nhất của hình vòng cung:

struct Arc: Shape {
    var startAngle: Angle
    var endAngle: Angle
    var clockwise: Bool

    func path(in rect: CGRect) -> Path {
        var path = Path()
        path.addArc(center: CGPoint(x: rect.midX, y: rect.midY), radius: rect.width / 2, startAngle: startAngle, endAngle: endAngle, clockwise: clockwise)

        return path
    }
}

Bây giờ chúng ta có thể tạo một vòng cung như thế này:

Arc(startAngle: .degrees(0), endAngle: .degrees(110), clockwise: true)
    .stroke(.blue, lineWidth: 10)
    .frame(width: 300, height: 300)

Nếu bạn nhìn vào bản xem trước của vòng cung của chúng tôi, rất có thể nó trông không giống như bạn mong đợi. Chúng tôi đã yêu cầu một cung từ 0 độ đến 110 độ với góc quay theo chiều kim đồng hồ, nhưng dường như chúng tôi đã được cung cấp một cung từ 90 độ đến 200 độ với góc quay ngược chiều kim đồng hồ.

Những gì đang xảy ra ở đây có hai mặt:

Trong mắt SwiftUI, 0 độ không phải là hướng thẳng lên trên mà thay vào đó là hướng thẳng về bên phải.
Các hình dạng đo tọa độ của chúng từ góc dưới bên trái thay vì góc trên cùng bên trái, điều đó có nghĩa là SwiftUI đi theo chiều ngược lại từ góc này sang góc khác. Theo quan điểm của tôi, điều này cực kỳ xa lạ.
Chúng tôi có thể khắc phục cả hai vấn đề đó bằng một path(in:) phương pháp mới trừ 90 độ so với góc bắt đầu và kết thúc, đồng thời đảo hướng để SwiftUI hoạt động theo cách tự nhiên đã dự định:

func path(in rect: CGRect) -> Path {
    let rotationAdjustment = Angle.degrees(90)
    let modifiedStart = startAngle - rotationAdjustment
    let modifiedEnd = endAngle - rotationAdjustment

    var path = Path()
    path.addArc(center: CGPoint(x: rect.midX, y: rect.midY), radius: rect.width / 2, startAngle: modifiedStart, endAngle: modifiedEnd, clockwise: !clockwise)

    return path
}

Chạy mã đó và xem bạn nghĩ gì – đối với tôi, nó tạo ra cách làm việc tự nhiên hơn nhiều và tách biệt rõ ràng hành vi vẽ của SwiftUI.

Nguồn: https://www.hackingwithswift.com/books/ios-swiftui/paths-vs-shapes-in-swiftui