CGContext trong iOS — nó là gì, các phương thức và cách quản lý ngữ cảnh vẽ

Tác giả: IT Sectr Đã đăng: 2026-07-21 Thời gian đọc: 9 phút

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à ngữ cảnh đồ họa qua đó tất cả các lệnh Core Graphics 2D được thực thi.
  • setStrokeColorsetFillColor quản lý màu của đường và phần tô trong ngữ cảnh.
  • addPathdrawPath cho phép vẽ các đường viền CGPath tùy ý.
  • clip(to:) đặt vùng cắt để giới hạn vẽ trong một khu vực cụ thể.
  • saveGStaterestoreGState lưu và khôi phục ngăn xếp trạng thái ngữ cảnh.

CGContext là gì?

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.

Quản lý trạng thái: saveGState và restoreGState

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.

Sử dụng thực tế của ngăn xếp

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.

swift
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()
}

Màu sắc và Tô màu

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.

Gradient trong CGContext

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àmMục đíchKhông gian màu
setFillColorĐặt màu tôBất kỳ (qua CGColor)
setStrokeColorĐặt màu đường viềnBất kỳ (qua CGColor)
drawLinearGradientVẽ gradient tuyến tínhRGB hoặc Thang độ Xám
drawRadialGradientVẽ gradient xuyên tâmRGB hoặc Thang độ Xám

Vẽ đường dẫn và đường thẳng

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.

Đường thẳng và đường cong theo đ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.

swift
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()

Vùng cắt và Biến đổi

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.

Biến đổi tọa độ

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ẽ.

swift
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()
}

Ví dụ vẽ trong CGContext

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.

swift
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

Làm thế nào để lấy CGContext hiện tại trong iOS?

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.

Sự khác biệt giữa fillPath và strokePath là gì?

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à gì?

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.

Làm thế nào để đặt lại tất cả cài đặt CGContext?

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ố.

Tại sao gradient không hiển thị bên trong hình dạng?

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

  • CGContext là đối tượng trung tâm của Core Graphics cho tất cả thao tác vẽ 2D trên iOS và macOS.
  • saveGStaterestoreGState quản lý ngăn xếp trạng thái ngữ cảnh, ngăn ngừa tác dụng phụ.
  • setFillColorsetStrokeColor đặt màu tô và đường viền qua CGColorRef.
  • addPath, strokePath, fillPath làm việc với các đường viền CGPath tùy ý.
  • clip giới hạn vùng vẽ theo đường dẫn hiện tại cho mặt nạ và bo tròn.
  • translateCTM, scaleCTM, rotateCTM biến đổi hệ tọa độ ngữ cảnh.
  • Tất cả thao tác CGContext chạy trên CPU — đối với đồ họa phức tạp, sử dụng CGLayer hoặc Metal.

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.

Thảo luận dự án

Đọc thêm