CGContext هو الكائن الرئيسي في Core Graphics لتنفيذ أوامر الرسم ثنائي الأبعاد في iOS و macOS. إنه يمثل سياقاً رسومياً يخزن الحالة الحالية: لون التعبئة، سمك الخط، التحويل، منطقة القص والخطوط. جميع عمليات الرسم — الخطوط، المستطيلات، النصوص، الصور — تُنفذ عبر CGContext. وفقاً لـ Apple Developer Documentation, 2026، يعتبر CGContext أساس جميع الرسومات النقطية والمتجهة في نظام Apple البيئي.
النقاط الرئيسية
CGContext هو نوع غير شفاف (opaque type) من إطار Core Graphics، يمثل بيئة لتنفيذ أوامر العرض ثنائي الأبعاد. يدير مكدس الحالات، مساحة الألوان الحالية، تحويل الإحداثيات ومنطقة القص. 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 و Grayscale.
تعبئة منطقة بتدرج يتطلب تعيين منطقة قص أولاً. بدون clip، سيملأ التدرج السياق بأكمله، وليس فقط الشكل المطلوب. النمط النموذجي: إضافة مسار إلى السياق، استدعاء clip لتقييد المنطقة، ثم رسم التدرج. يُستخدم هذا النمط في جميع التطبيقات التي تتطلب تعبئة تدرجية للأشكال المعقدة، من الأزرار إلى الخلفيات.
| الدالة | الغرض | مساحة الألوان |
|---|---|---|
| setFillColor | تضبط لون التعبئة | أي (عبر CGColor) |
| setStrokeColor | تضبط لون الحد | أي (عبر CGColor) |
| drawLinearGradient | ترسم تدرجاً خطياً | RGB أو Grayscale |
| drawRadialGradient | ترسم تدرجاً شعاعياً | RGB أو Grayscale |
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. بالنسبة لمعظم مهام واجهة المستخدم التي تحتوي على 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. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا