CGContext คืออ็อบเจกต์หลักของ Core Graphics สำหรับดำเนินการคำสั่งวาด 2D ใน iOS และ macOS โดยแสดงถึงบริบทกราฟิกที่เก็บสถานะปัจจุบัน: สีเติม ความหนาของเส้น การแปลง พื้นที่ตัด และแบบอักษร การดำเนินการวาดทั้งหมด — เส้น สี่เหลี่ยม ข้อความ รูปภาพ — ดำเนินการผ่าน CGContext ตาม Apple Developer Documentation, 2026 CGContext คือรากฐานของกราฟิกแรสเตอร์และเวกเตอร์ทั้งหมดในระบบนิเวศของ Apple
ประเด็นสำคัญ
CGContext คือชนิดทึบแสง (opaque type) จากเฟรมเวิร์ก Core Graphics ที่แสดงถึงสภาพแวดล้อมสำหรับดำเนินการคำสั่งเรนเดอร์ 2D โดยจัดการสแต็กสถานะ พื้นที่สีปัจจุบัน การแปลงพิกัด และพื้นที่ตัด CGContext เป็นอ็อบเจกต์เดียวที่ฟังก์ชันวาด Quartz 2D ทั้งหมดโต้ตอบด้วย — การดำเนินการกราฟิกใดๆ จะผ่านมัน
CGContext มีหลายรูปแบบขึ้นอยู่กับวัตถุประสงค์: bitmap context สำหรับทำงานกับภาพแรสเตอร์ในหน่วยความจำ PDF context สำหรับสร้างเอกสาร PDF window context สำหรับวาดบนหน้าจอ (โดย UIView ใน draw(_:)) นักพัฒนา iOS มักทำงานกับบริบทที่ได้รับจาก UIGraphicsGetCurrentContext() ภายในเมธอด draw(_:) หรือจาก UIGraphicsBeginImageContextWithOptions สำหรับการเรนเดอร์แบบออฟไลน์
บริบทใช้การตั้งค่าทั้งหมดผ่าน สแต็กสถานะ — นักพัฒนาสามารถบันทึกสถานะปัจจุบัน (saveGState) แก้ไขพารามิเตอร์ และกู้คืนสถานะก่อนหน้า (restoreGState) ซึ่งมีความสำคัญอย่างยิ่งเมื่อวาดฉากที่ซับซ้อนซึ่งองค์ประกอบต่าง ๆ ต้องการสี ความหนาของเส้น และการแปลงที่แตกต่างกัน ตาม Apple WWDC 2023 การใช้สแต็กสถานะอย่างถูกต้องช่วยเพิ่มความสามารถในการอ่านโค้ดและป้องกันสิ่งแปลกปลอมทางภาพ
CGContextSaveGState วางสำเนาของสถานะบริบทปัจจุบันลงในสแต็ก สถานะนี้รวมถึงการตั้งค่าทั้งหมด: สี ความหนาของเส้น การแปลง เงา พื้นที่ตัด แบบอักษร หลังจากแก้ไขพารามิเตอร์ การเรียก CGContextRestoreGState จะคืนค่าบริบทกลับไปยังสถานะที่บันทึกไว้ ยกเลิกการเปลี่ยนแปลงทั้งหมดระหว่างกลาง สแต็กสามารถเก็บได้สูงสุด 32 สถานะบน iOS
เมื่อวาดกลุ่มองค์ประกอบที่มีสไตล์ต่างกัน (เช่น ส่วนของแผนภูมิหลายสี) สแต็กสถานะจะช่วยลดความจำเป็นในการจดจำและกู้คืนแต่ละพารามิเตอร์ด้วยตนเอง หากไม่มี saveGState/restoreGState นักพัฒนาจะต้องรีเซ็ตสี เงา และการแปลงหลังจากแต่ละองค์ประกอบ การใช้สแต็ก ช่วยลดโค้ดและป้องกันข้อผิดพลาดในการกู้คืน
การรักษา ความสมดุล ของการบันทึกและการกู้คืนเป็นสิ่งสำคัญ — แต่ละ saveGState ต้องมี restoreGState ที่ตรงกัน การทำลายสมดุลทำให้เกิดการรั่วไหลของสถานะหรือการกู้คืนก่อนเวลาอันควร ส่งผลให้การเรนเดอร์ไม่สามารถคาดเดาได้ ตัววิเคราะห์สแตติกของ Xcode ไม่ตรวจสอบความสมดุลของสแต็ก CGContext ดังนั้นความถูกต้องจึงเป็นความรับผิดชอบของนักพัฒนา แนะนำให้จัดกลุ่มคู่ในบล็อก 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 กำหนดสีเติมสำหรับการดำเนินการถัดไป สีถูกส่งเป็น CGColorRef ซึ่งมีข้อมูลเกี่ยวกับพื้นที่สีและองค์ประกอบสี CGContextSetStrokeColorWithColor ทำงานคล้ายกันสำหรับสีเส้นขอบ Apple แนะนำให้แคชค่า CGColorRef ที่ใช้บ่อยเพื่อเพิ่มประสิทธิภาพ
CGContextDrawLinearGradient วาดเกรเดียนต์เชิงเส้นระหว่างสองจุดด้วยสีที่ระบุ CGContextDrawRadialGradient สร้างเกรเดียนต์รัศมีที่เปลี่ยนจากวงกลมหนึ่งไปยังอีกวงกลมหนึ่ง ในการทำงานกับเกรเดียนต์ อันดับแรกจะสร้างอ็อบเจกต์ CGGradientRef ที่ประกอบด้วยอาร์เรย์ของสีและตำแหน่งหยุด เกรเดียนต์ใน CGContext รองรับทั้งพื้นที่สี RGB และสเกลสีเทา
การเติมพื้นที่ด้วยเกรเดียนต์จำเป็นต้องตั้งค่าพื้นที่ตัดก่อน หากไม่มี clip เกรเดียนต์จะเติมทั้งบริบท ไม่ใช่เฉพาะรูปร่างที่ต้องการ รูปแบบทั่วไป: เพิ่มเส้นทางไปยังบริบท เรียก clip เพื่อจำกัดพื้นที่ จากนั้นวาดเกรเดียนต์ รูปแบบนี้ใช้ในแอปพลิเคชันทั้งหมดที่ต้องการการเติมเกรเดียนต์ของรูปร่างที่ซับซ้อน ตั้งแต่ปุ่มไปจนถึงพื้นหลัง
| ฟังก์ชัน | วัตถุประสงค์ | พื้นที่สี |
|---|---|---|
| setFillColor | กำหนดสีเติม | ใดๆ (ผ่าน CGColor) |
| setStrokeColor | กำหนดสีเส้นขอบ | ใดๆ (ผ่าน CGColor) |
| drawLinearGradient | วาดเกรเดียนต์เชิงเส้น | RGB หรือสเกลสีเทา |
| drawRadialGradient | วาดเกรเดียนต์รัศมี | RGB หรือสเกลสีเทา |
CGContextAddPath เพิ่ม CGPathRef ที่สร้างไว้ก่อนหน้านี้ไปยังบริบทสำหรับการเรนเดอร์ต่อไป หลังจากเพิ่มเส้นทางแล้ว สามารถวาดเส้นขอบ (strokePath) เติม (fillPath) หรือทั้งสองอย่าง CGContextBeginPath เริ่มเส้นทางใหม่ ล้างเส้นทางก่อนหน้า บริบทรองรับเพียงหนึ่งเส้นทางที่ใช้งานในแต่ละครั้ง
CGContextMoveToPoint ย้ายจุดเส้นโค้งปัจจุบันไปยังพิกัดที่ระบุ CGContextAddLineToPoint วาดเส้นตรงจากจุดปัจจุบันไปยังจุดที่กำหนด CGContextAddCurveToPoint เพิ่มเส้นโค้งเบซิเยร์กำลังสาม CGContextAddQuadCurveToPoint เพิ่มเส้นโค้งกำลังสอง CGContextClosePath ปิดเส้นโค้งด้วยเส้นตรงระหว่างจุดสุดท้ายและจุดแรก
ต่างจาก UIBezierPath ที่เมธอดทั้งหมดนี้ถูกเรียกบนอ็อบเจกต์เส้นทาง ใน CGContext จะถูกเรียกโดยตรงบนบริบท ความแตกต่างนี้สำคัญเมื่อเลือกแนวทาง: UIBezierPath สะดวกกว่าสำหรับการใช้เส้นทางซ้ำ CGContext เหมาะกว่าสำหรับการวาดครั้งเดียวที่มีโอเวอร์เฮดน้อยที่สุด CGContext ยังรองรับเส้นประผ่าน CGContextSetLineDash ปลายเส้นยืดหยุ่นผ่าน CGContextSetLineCap และการต่อผ่าน 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 จำกัดพื้นที่วาดให้อยู่ในเส้นทางปัจจุบัน การดำเนินการวาดทั้งหมดต่อไปนี้จะมองเห็นได้เฉพาะภายในพื้นที่นี้เท่านั้น นี่คือกลไกพื้นฐานสำหรับการสร้างมาสก์ การทำให้มุมโค้งมน และการสร้างขอบเขตที่กำหนดเองขององค์ประกอบ พื้นที่ตัดยังถูกบันทึกและกู้คืนผ่าน saveGState/restoreGState
CGContextTranslateCTM เลื่อนจุดกำเนิดของระบบพิกัดบริบท CGContextScaleCTM ปรับขนาดแกน X และ Y CGContextRotateCTM หมุนระบบพิกัดรอบจุดกำเนิดปัจจุบัน การแปลงสะสมกัน — แต่ละครั้งต่อไปจะถูกนำไปใช้สัมพันธ์กับระบบพิกัดปัจจุบัน การแปลงแบบผสม (การเลื่อน + การหมุน + การปรับขนาด) ใช้สำหรับแอนิเมชันและการจัดวางตำแหน่งองค์ประกอบ
การรวมการตัดและการแปลงเข้าด้วยกันให้ความสามารถที่ทรงพลัง: สามารถวาดมาสก์ที่ซับซ้อนผ่านการตัด จากนั้นใช้การแปลงกับเนื้อหาทั้งหมด ตัวอย่างทั่วไป — การแสดงส่วนหนึ่งของภาพในมุมที่มีขอบโค้งมน ลำดับการดำเนินการสำคัญ: ขั้นแรกตั้งค่าพื้นที่ตัด จากนั้นใช้การแปลง จากนั้นวาดเนื้อหา Apple แนะนำให้ลดการเปลี่ยนแปลง CTM (เมทริกซ์การแปลงปัจจุบัน) ในลูปการวาด
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()
}
มาดูตัวอย่างที่สมบูรณ์ของการสร้างแผนภูมิเกรเดียนต์โดยใช้ CGContext แผนภูมิประกอบด้วย สามส่วนที่มีสีต่างกันพร้อมการเติมเกรเดียนต์และป้ายข้อความ โค้ดแสดงการรวมกันของ saveGState, clip, เกรเดียนต์ และการเรนเดอร์ข้อความในบริบทเดียว
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
}
}
}
ในตัวอย่างนี้ แต่ละส่วนของแผนภูมิจะถูกวาดในบล็อก saveGState/restoreGState ของตัวเอง แนวทางนี้รับประกันว่าการตั้งค่าสีและเส้นทางของส่วนหนึ่งจะไม่ส่งผลกระทบต่อส่วนอื่น เมธอด addArc สร้างส่วนโค้งจากมุมเริ่มต้นถึงมุมสิ้นสุด และ closePath ปิดส่วนไปยังจุดศูนย์กลาง ข้อความป้ายถูกวางไว้ที่มุมกลางของส่วนโดยใช้ฟังก์ชันตรีโกณมิติ cos และ sin ในการคำนวณตำแหน่ง
เพื่อปรับปรุงประสิทธิภาพระหว่างการวาดซ้ำบ่อยๆ แนะนำให้แคชการคำนวณมุมและตำแหน่งในอาร์เรย์แยกต่างหาก CGContext ดำเนินการทั้งหมดบน CPU ดังนั้นฉากที่ซับซ้อนที่มีหลายสิบส่วนอาจต้องการการปรับให้เหมาะสม — เช่น การเรนเดอร์ล่วงหน้าใน CGLayer หรือการใช้ Metal สำหรับการเร่ง GPU สำหรับงาน UI ส่วนใหญ่ที่มี 5–10 องค์ประกอบ ประสิทธิภาพของ CGContext ยังคงยอมรับได้
คำถามที่พบบ่อย
เรียก UIGraphicsGetCurrentContext() ภายในเมธอด draw(_:) ของ UIView หรือภายในบล็อก UIGraphicsBeginImageContextWithOptions สำหรับเลเยอร์ CALayer ให้ใช้เมธอด draw(in:) ของ CALayerDelegate ซึ่งได้รับบริบทที่พร้อมใช้งาน
fillPath เติมพื้นที่ภายในของเส้นทางปัจจุบันด้วยสีจาก setFillColor strokePath วาดเส้นตามเส้นทางด้วยพารามิเตอร์ setStrokeColor, setLineWidth และ setLineCap การดำเนินการทั้งสองจะดำเนินการตามลำดับ: เติมก่อน จากนั้นจึงวาดเส้นขอบหากจำเป็น
Bitmap CGContext คือบริบทที่สร้างผ่าน CGBitmapContextCreate ซึ่งวาดลงในบัฟเฟอร์หน่วยความจำแทนที่จะเป็นหน้าจอ ใช้สำหรับการสร้างภาพพื้นหลัง การประมวลผลกราฟิกโดยไม่ต้องแสดงผล และสร้างเท็กซ์เจอร์สำหรับ Metal หรือ OpenGL
Core Graphics ไม่มีเมธอดในการรีเซ็ตการตั้งค่าทั้งหมด ใช้ saveGState ก่อนการเปลี่ยนแปลงและ restoreGState เพื่อกลับไปยังสถานะเดิม หรือสร้างบริบทใหม่ด้วยพารามิเตอร์เดียวกัน
ก่อนเรียก drawLinearGradient ให้ตั้งค่าพื้นที่ตัดผ่าน clip บนเส้นทางที่ต้องการ หากไม่มี clip เกรเดียนต์จะเติมทั้งบริบท ลำดับ: addPath → clip → drawLinearGradient — รับประกันเกรเดียนต์ภายในรูปร่าง
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม