CGContext là đối tượng chính của Core Graphics để thực thi các lệnh vẽ 2D trong iOS và macOS. Nó đại diện cho một ngữ cảnh đồ họa lưu trữ trạng thái hiện tại: màu tô, độ dày đường, biến đổi, vùng cắt và phông chữ. Tất cả các thao tác vẽ — đường, hình chữ nhật, văn bản, hình ảnh — được thực hiện thông qua CGContext. Theo Apple Developer Documentation, 2026, CGContext là nền tảng của tất cả đồ họa raster và vector trong hệ sinh thái Apple.
Những điểm chính
CGContext là một kiểu không trong suốt (opaque type) từ framework Core Graphics, đại diện cho môi trường thực thi các lệnh render 2D. Nó quản lý ngăn xếp trạng thái, không gian màu hiện tại, biến đổi tọa độ và vùng cắt. CGContext là đối tượng duy nhất mà tất cả các hàm vẽ Quartz 2D tương tác — bất kỳ thao tác đồ họa nào cũng đi qua nó.
CGContext tồn tại ở nhiều dạng khác nhau tùy theo mục đích: bitmap context để làm việc với hình ảnh raster trong bộ nhớ, PDF context để tạo tài liệu PDF, window context để vẽ trên màn hình (được UIView cung cấp trong draw(_:)). Các nhà phát triển iOS thường làm việc nhất với ngữ cảnh lấy từ UIGraphicsGetCurrentContext() bên trong phương thức draw(_:) hoặc từ UIGraphicsBeginImageContextWithOptions để render ngoại tuyến.
Ngữ cảnh áp dụng tất cả cài đặt thông qua ngăn xếp trạng thái — nhà phát triển có thể lưu trạng thái hiện tại (saveGState), sửa đổi tham số và khôi phục trạng thái trước đó (restoreGState). Điều này cực kỳ quan trọng khi vẽ các cảnh phức tạp, nơi các phần tử khác nhau yêu cầu màu sắc, độ dày đường và biến đổi khác nhau. Theo Apple WWDC 2023, việc sử dụng đúng ngăn xếp trạng thái cải thiện khả năng đọc mã và ngăn ngừa các hiện vật hình ảnh.
CGContextSaveGState đặt một bản sao của trạng thái ngữ cảnh hiện tại vào ngăn xếp. Trạng thái này bao gồm tất cả cài đặt: màu sắc, độ dày đường, biến đổi, bóng, vùng cắt, phông chữ. Sau khi sửa đổi tham số, gọi CGContextRestoreGState đưa ngữ cảnh trở lại trạng thái đã lưu, hoàn tác tất cả thay đổi trung gian. Ngăn xếp có thể chứa tới 32 trạng thái trên iOS.
Khi vẽ các nhóm phần tử với kiểu khác nhau (ví dụ: các phần biểu đồ nhiều màu), ngăn xếp trạng thái loại bỏ nhu cầu ghi nhớ và khôi phục thủ công từng tham số. Không có saveGState/restoreGState, nhà phát triển phải đặt lại màu, bóng và biến đổi sau mỗi phần tử. Sử dụng ngăn xếp giảm mã và ngăn ngừa lỗi khôi phục.
Điều quan trọng là duy trì sự cân bằng giữa lưu và khôi phục — mỗi saveGState phải có một restoreGState tương ứng. Phá vỡ cân bằng dẫn đến rò rỉ trạng thái hoặc khôi phục sớm, gây ra hiển thị không thể đoán trước. Trình phân tích tĩnh của Xcode không kiểm tra cân bằng ngăn xếp CGContext, do đó tính chính xác thuộc trách nhiệm của nhà phát triển. Nên nhóm các cặp trong khối do/finally.
override func draw(_ rect: CGRect) {
guard let ctx = UIGraphicsGetCurrentContext() else { return }
ctx.saveGState()
ctx.setStrokeColor(UIColor.red.cgColor)
ctx.setLineWidth(3)
ctx.addRect(CGRect(x: 50, y: 50, width: 200, height: 100))
ctx.strokePath()
ctx.restoreGState()
}
CGContextSetFillColorWithColor đặt màu tô cho các thao tác tiếp theo. Màu được truyền dưới dạng CGColorRef, chứa thông tin về không gian màu và các thành phần màu. CGContextSetStrokeColorWithColor hoạt động tương tự cho màu đường viền. Apple khuyến nghị lưu cache các giá trị CGColorRef thường dùng để cải thiện hiệu suất.
CGContextDrawLinearGradient vẽ gradient tuyến tính giữa hai điểm với màu sắc chỉ định. CGContextDrawRadialGradient tạo gradient xuyên tâm chuyển từ vòng tròn này sang vòng tròn khác. Để làm việc với gradient, trước tiên tạo đối tượng CGGradientRef chứa mảng màu sắc và vị trí dừng. Gradient trong CGContext hỗ trợ cả không gian màu RGB và Thang độ Xám.
Tô một vùng bằng gradient yêu cầu thiết lập vùng cắt trước. Không có clip, gradient sẽ tô toàn bộ ngữ cảnh, không chỉ hình dạng mong muốn. Mẫu điển hình: thêm đường dẫn vào ngữ cảnh, gọi clip để giới hạn vùng, sau đó vẽ gradient. Mẫu này được sử dụng trong tất cả ứng dụng yêu cầu tô gradient cho các hình dạng phức tạp, từ nút bấm đến nền.
| Hàm | Mục đích | Không gian màu |
|---|---|---|
| setFillColor | Đặt màu tô | Bất kỳ (qua CGColor) |
| setStrokeColor | Đặt màu đường viền | Bất kỳ (qua CGColor) |
| drawLinearGradient | Vẽ gradient tuyến tính | RGB hoặc Thang độ Xám |
| drawRadialGradient | Vẽ gradient xuyên tâm | RGB hoặc Thang độ Xám |
CGContextAddPath thêm CGPathRef đã tạo trước đó vào ngữ cảnh để render sau. Sau khi thêm đường dẫn, nó có thể được vẽ viền (strokePath), tô (fillPath) hoặc cả hai. CGContextBeginPath bắt đầu một đường dẫn mới, xóa đường dẫn trước đó. Ngữ cảnh chỉ hỗ trợ một đường dẫn hoạt động tại một thời điểm.
CGContextMoveToPoint di chuyển điểm đường viền hiện tại đến tọa độ chỉ định. CGContextAddLineToPoint vẽ một đường thẳng từ điểm hiện tại đến điểm được chỉ định. CGContextAddCurveToPoint thêm đường cong Bézier bậc ba, CGContextAddQuadCurveToPoint thêm đường cong bậc hai. CGContextClosePath đóng đường viền bằng một đường thẳng giữa điểm cuối và điểm đầu.
Không giống UIBezierPath, nơi tất cả các phương thức này được gọi trên đối tượng đường dẫn, trong CGContext chúng được gọi trực tiếp trên ngữ cảnh. Sự khác biệt này quan trọng khi chọn cách tiếp cận: UIBezierPath thuận tiện hơn cho việc tái sử dụng đường dẫn, CGContext tốt hơn cho vẽ một lần với chi phí tối thiểu. CGContext cũng hỗ trợ đường đứt nét qua CGContextSetLineDash, đầu đường linh hoạt qua CGContextSetLineCap và kết nối qua CGContextSetLineJoin.
let ctx = UIGraphicsGetCurrentContext()!
ctx.beginPath()
ctx.moveTo(x: 50, y: 100)
ctx.addLineTo(x: 200, y: 100)
ctx.addCurveTo(x1: 250, y1: 50, x2: 300, y2: 150, x3: 350, y3: 100)
ctx.setStrokeColor(UIColor.blue.cgColor)
ctx.setLineWidth(5)
ctx.strokePath()
CGContextClip giới hạn vùng vẽ theo đường dẫn hiện tại. Tất cả các thao tác vẽ tiếp theo sẽ chỉ hiển thị trong vùng này. Đây là cơ chế cơ bản để tạo mặt nạ, bo tròn góc và tạo ranh giới tùy chỉnh cho phần tử. Vùng cắt cũng được lưu và khôi phục qua saveGState/restoreGState.
CGContextTranslateCTM dịch chuyển gốc tọa độ của hệ tọa độ ngữ cảnh. CGContextScaleCTM thu phóng trục X và Y. CGContextRotateCTM xoay hệ tọa độ quanh gốc hiện tại. Các biến đổi tích lũy — mỗi biến đổi tiếp theo được áp dụng tương đối với hệ tọa độ hiện tại. Các biến đổi tổng hợp (dịch chuyển + xoay + thu phóng) được sử dụng cho hoạt ảnh và định vị phần tử.
Kết hợp cắt và biến đổi mang lại khả năng mạnh mẽ: có thể vẽ mặt nạ phức tạp qua cắt, sau đó áp dụng biến đổi cho toàn bộ nội dung. Một ví dụ điển hình — hiển thị một phần hình ảnh ở góc với các cạnh bo tròn. Thứ tự thao tác quan trọng: đầu tiên đặt vùng cắt, sau đó áp dụng biến đổi, sau đó vẽ nội dung. Apple khuyến nghị giảm thiểu số lần thay đổi CTM (Ma trận Biến đổi Hiện tại) trong vòng lặp vẽ.
override func draw(_ rect: CGRect) {
guard let ctx = UIGraphicsGetCurrentContext() else { return }
ctx.saveGState()
// clipping region - circle
ctx.addEllipse(in: CGRect(x: 50, y: 50, width: 200, height: 200))
ctx.clip()
// content transformation
ctx.translateBy(x: 150, y: 150)
ctx.rotate(by: .pi / 4)
ctx.translateBy(x: -150, y: -150)
UIImage(named: "photo")?.draw(at: CGPoint(x: 50, y: 50))
ctx.restoreGState()
}
Hãy xem một ví dụ hoàn chỉnh về tạo biểu đồ gradient sử dụng CGContext. Biểu đồ bao gồm ba phần với màu sắc khác nhau, tô gradient và nhãn văn bản. Mã này thể hiện sự kết hợp của saveGState, clip, gradient và render văn bản trong một ngữ cảnh duy nhất.
class PieChartView: UIView {
private struct Slice {
let color: UIColor
let value: CGFloat
let label: String
}
override func draw(_ rect: CGRect) {
guard let ctx = UIGraphicsGetCurrentContext() else { return }
let slices = [
Slice(color: .systemRed, value: 0.4, label: "iOS"),
Slice(color: .systemGreen, value: 0.35, label: "Android"),
Slice(color: .systemBlue, value: 0.25, label: "Other")
]
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let radius = bounds.width * 0.35
var startAngle: CGFloat = -.pi / 2
for slice in slices {
let endAngle = startAngle + .pi * 2 * slice.value
ctx.saveGState()
ctx.moveTo(x: center.x, y: center.y)
ctx.addArc(center: center, radius: radius,
startAngle: startAngle, endAngle: endAngle, clockwise: false)
ctx.closePath()
ctx.setFillColor(slice.color.cgColor)
ctx.fillPath()
// text label
let midAngle = startAngle + endAngle / 2
let labelPos = CGPoint(
x: center.x + radius * 0.7 * cos(midAngle),
y: center.y + radius * 0.7 * sin(midAngle)
)
slice.label.draw(at: labelPos, withAttributes: [
.foregroundColor: UIColor.white,
.font: UIFont.boldSystemFont(ofSize: 14)
])
ctx.restoreGState()
startAngle = endAngle
}
}
}
Trong ví dụ này, mỗi phần của biểu đồ được vẽ trong khối saveGState/restoreGState riêng. Cách tiếp cận này đảm bảo rằng cài đặt màu sắc và đường dẫn của một phần không ảnh hưởng đến các phần khác. Phương thức addArc tạo một cung từ góc bắt đầu đến góc kết thúc, và closePath đóng phần về phía trung tâm. Nhãn văn bản được đặt ở góc giữa của phần sử dụng các hàm lượng giác cos và sin để tính vị trí.
Để cải thiện hiệu suất khi vẽ lại thường xuyên, nên lưu cache các tính toán góc và vị trí trong một mảng riêng. CGContext thực thi tất cả thao tác trên CPU, do đó các cảnh phức tạp với hàng chục phần có thể yêu cầu tối ưu hóa — ví dụ, render trước trong CGLayer hoặc sử dụng Metal để tăng tốc GPU. Đối với hầu hết tác vụ UI với 5–10 phần tử, hiệu suất của CGContext vẫn chấp nhận được.
Câu hỏi thường gặp
Gọi UIGraphicsGetCurrentContext() bên trong phương thức draw(_:) của UIView hoặc bên trong khối UIGraphicsBeginImageContextWithOptions. Đối với các lớp CALayer, sử dụng phương thức draw(in:) của CALayerDelegate, phương thức này nhận được một ngữ cảnh đã sẵn sàng.
fillPath tô phần bên trong của đường dẫn hiện tại với màu từ setFillColor. strokePath vẽ một đường dọc theo đường dẫn với các tham số setStrokeColor, setLineWidth và setLineCap. Cả hai thao tác được thực hiện tuần tự: đầu tiên tô, sau đó vẽ viền nếu cần.
Bitmap CGContext là ngữ cảnh được tạo qua CGBitmapContextCreate, vẽ vào bộ đệm bộ nhớ thay vì màn hình. Nó được sử dụng để tạo hình ảnh nền, xử lý đồ họa không hiển thị và tạo kết cấu cho Metal hoặc OpenGL.
Core Graphics không cung cấp phương thức đặt lại tất cả cài đặt. Sử dụng saveGState trước khi thay đổi và restoreGState để quay lại trạng thái ban đầu. Thay vào đó, có thể tạo ngữ cảnh mới với cùng tham số.
Trước khi gọi drawLinearGradient, hãy đặt vùng cắt qua clip trên đường dẫn mong muốn. Không có clip, gradient tô toàn bộ ngữ cảnh. Thứ tự: addPath → clip → drawLinearGradient — đảm bảo gradient bên trong hình dạng.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm