draw(_:) و drawRect — ما هما، الاستدعاء والتجاوز

المؤلف: IT Sectr نُشر: 2026-07-20 وقت القراءة: 7 دق

draw(_:) / drawRect هي طريقة من فئة UIView (في Swift) ونظيرتها في Objective-C (drawRect:)، المسؤولة عن عرض محتوى العرض باستخدام Core Graphics. يستدعي النظام هذه الطريقة تلقائياً عند أول ظهور للعرض على الشاشة وبعد استدعاء setNeedsDisplay(). وفقاً لوثائق Apple (2025)، فإن draw(_:) هي الطريقة الوحيدة التي يمكن للمطور فيها الوصول إلى سياق الرسوم للشاشة الحالية للرسم المخصص. يتيح تجاوز draw(_:) السيطرة الكاملة على مظهر المكون — من الأشكال الهندسية البسيطة إلى الرسوم المتحركة المعقدة.

النقاط الرئيسية

  • draw(_:) — طريقة UIView للرسم المخصص عبر Core Graphics، لا تُستدعى مباشرة من قبل المطور
  • drawRect: — مقابل draw(_:) في Objective-C، مختلف نحوياً، متطابق وظيفياً
  • CGContext — سياق الرسوم المتاح داخل draw(_:) لجميع عمليات الرسم
  • setNeedsDisplay() — الطريقة الصحيحة الوحيدة لطلب إعادة رسم draw(_:) من جانب المطور
  • UIGraphicsGetCurrentContext() — دالة للحصول على السياق الحالي داخل draw(_:)، خطوة إلزامية قبل الرسم

ما هو draw(_:) / drawRect

draw(_:) هي طريقة实例 من UIView يستدعيها UIKit لعرض محتوى العرض. داخل هذه الطريقة، يحصل المطور على وصول إلى سياق الرسوم CGContext ويستخدم واجهة برمجة Core Graphics لرسم الخطوط والتعبئات والنصوص والصور. تقوم drawRect: في Objective-C بنفس الوظيفة ولكن بصيغة مختلفة: المعامل الوحيد هو CGRect يحدد المنطقة المراد إعادة رسمها.

وفقاً لـ Apple Engineering (2024)، تعمل draw(_:) من خلال عرض Bitmap Graphics Context المعتمد على CPU، مما يوفر أقصى مرونة ولكنه يتطلب موارد أكثر مقارنة بـ CALayer. يتم اتخاذ قرار استخدام draw(_:) بناءً على تعقيد الرسوم ومتطلبات الأداء.

توقيع الطريقة في Swift و Objective-C

في Swift، يتم تعريف الطريقة كـ override func draw(_ rect: CGRect)، حيث rect هو المستطيل الذي يحتاج إلى إعادة رسم. في Objective-C، التوقيع هو - (void)drawRect:(CGRect)rect. قد يكون معامل rect أصغر من bounds العرض أثناء إعادة الرسم الجزئي عبر setNeedsDisplay(_:).

objective-c
- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
    CGContextFillRect(context, rect);
}

متى يستدعي النظام draw(_:)

يستدعي النظام draw(_:) تلقائياً في سيناريوهات محددة بدقة. يساعد فهم هذه المحفزات في تجنب عمليات إعادة الرسم غير الضرورية وتحسين أداء العرض. فيما يلي جميع حالات الاستدعاء التلقائي للطريقة.

  • العرض الأول — عند إضافة العرض لأول مرة إلى التسلسل الهرمي ويصبح مرئياً على الشاشة
  • setNeedsDisplay() — بعد استدعاء هذه الطريقة، يعيد النظام الرسم أثناء دورة الرسم التالية
  • setNeedsDisplay(_:) — نفس ما سبق، ولكن مع تحديد مستطيل معين لإعادة الرسم
  • contentMode — عند تغيير bounds، إذا كان contentMode يتطلب إعادة رسم (مثل .redraw)
  • setNeedsLayout() — في بعض الحالات، بعد إعادة ترتيب العروض الفرعية، قد تكون إعادة الرسم ضرورية

تحذر وثائق Apple: لا تستدعِ draw(_:) مباشرة أبداً. النظام نفسه يقرر متى ينفذ الرسم، والاستدعاء المباشر يعطل آلية التخزين المؤقت الداخلية. لطلب إعادة الرسم، استخدم دائماً setNeedsDisplay() أو setNeedsDisplay(_:).

كيفية تجاوز draw(_:) في Swift

يبدأ تجاوز draw(_:) في Swift بالحصول على سياق الرسوم ثم استدعاءات Core Graphics اللاحقة. يُنصح بإنشاء طرق منفصلة للكتل المنطقية للرسم — مما يحسن قابلية قراءة الكود واختباره.

swift
override func draw(_ rect: CGRect) {
    super.draw(rect)

    guard let context = UIGraphicsGetCurrentContext() else { return }

    // Line parameters
    context.setStrokeColor(UIColor.darkGray.cgColor)
    context.setLineWidth(2.0)

    // Drawing a triangle
    context.move(to: CGPoint(x: rect.midX, y: rect.minY + 10))
    context.addLine(to: CGPoint(x: rect.maxX - 10, y: rect.maxY - 10))
    context.addLine(to: CGPoint(x: rect.minX + 10, y: rect.maxY - 10))
    context.closePath()
    context.strokePath()
}

في هذا المثال، يرسم draw(_:) مثلثاً بمخطط رمادي داكن بسمك 2 بكسل. يوصي Apple باستدعاء super.draw(rect) في بداية الطريقة للحفاظ على منطق الرسم الأصلي، على الرغم من أن تنفيذ draw(_:) الافتراضي في UIView فارغ.

قاعدة التماثل لـ draw(_:)

يجب أن يعطي كل استدعاء لـ draw(_:) نتيجة متطابقة مع نفس بيانات الإدخال. يتيح ذلك للنظام تخزين النتيجة مؤقتاً وعدم استدعاء draw(_:) مرة أخرى إذا لم يتغير محتوى العرض. لا تستخدم قيماً عشوائية أو وقت النظام أو طلبات الشبكة داخل draw(_:).

drawRect: في Objective-C والاختلاف عن draw(_:)

drawRect: هي تاريخياً أول إصدار للطريقة، ظهرت في iOS 2.0 مع Objective-C. في Swift، تمت إعادة تسمية الطريقة إلى draw(_:) باستخدام المعامل الخارجي _. وظيفياً، الطريقتان متطابقتان: كلتاهما تستقبل CGRect للمنطقة المراد إعادة رسمها وتستخدم UIGraphicsGetCurrentContext() للوصول إلى سياق الرسوم.

الخاصيةdraw(_:) (Swift)drawRect: (Objective-C)
التوقيعoverride func draw(_ rect: CGRect)- (void)drawRect:(CGRect)rect
استدعاء supersuper.draw(rect)[super drawRect:rect]
السياقUIGraphicsGetCurrentContext()UIGraphicsGetCurrentContext()
معامل rectrect: CGRectCGRect rect
الأداءمتطابقمتطابق

عند ترحيل مشروع من Objective-C إلى Swift، تعد إعادة تسمية الطريقة إحدى المهام الأولى. يوفر Xcode محولاً تلقائياً، لكن drawRect: يتطلب تحديثاً يدوياً إلى draw(_:). وفقاً لـ Apple (2024)، فإن إصدار Swift draw(_:) مفضل للمشاريع الجديدة.

تحسين الرسم في draw(_:)

يتم تنفيذ draw(_:) على CPU، ويمكن أن يتسبب التنفيذ غير الأمثل في انخفاض معدل الإطارات وضعف الأداء. توصي Apple بعدة طرق مجربة لتسريع الرسم.

قلل عدد عمليات الرسم

كل عملية Core Graphics (move(to:), addLine(to:), strokePath) لها تكلفة إضافية. قم بتجميع العمليات واستخدم CGPath للأشكال المعقدة — يتم إنشاء المسار مرة واحدة وإعادة استخدامه في كل استدعاء لـ draw(_:).

استخدم UIBezierPath للكائنات المتجهية

UIBezierPath هو غلاف Objective-C حول CGPath يوفر واجهة برمجة بسيطة لإنشاء الأشكال. أنشئ UIBezierPath مسبقاً (مثلاً في المُهيئ) واستدعِ ببساطة fill() أو stroke() داخل draw(_:).

swift
private let starPath: UIBezierPath = {
    let path = UIBezierPath()
    // Building a star shape
    path.move(to: CGPoint(x: 50, y: 0))
    for i in 1...5 {
        let angle = CGFloat(i) * 4 * CGFloat.pi / 5
        path.addLine(to: CGPoint(x: 50 + 40 * cos(angle),
                               y: 50 + 40 * sin(angle)))
    }
    path.close()
    return path
}()

override func draw(_ rect: CGRect) {
    UIColor.systemYellow.setFill()
    starPath.fill()
}

الأخطاء الشائعة عند العمل مع draw(_:)

غالباً ما يرتكب المطورون نفس الأخطاء عند تجاوز draw(_:). تساعد معرفة هذه الأنماط في تجنب الأخطاء وتدهور الأداء. دعنا نلقي نظرة على المشكلات الأكثر شيوعاً وحلولها.

  • الاستدعاء المباشر لـ draw(_:) — لا تستدعِ draw(_:) مباشرة أبداً. استخدم setNeedsDisplay() لطلب إعادة الرسم. الاستدعاء المباشر يعطل التخزين المؤقت وقد يؤدي إلى عرض غير صحيح.
  • الحسابات الثقيلة داخل draw(_:) — يجب أن يكون draw(_:) خفيفاً قدر الإمكان. أنشئ UIBezierPath والصور والأشياء الثقيلة الأخرى خارج الطريقة، مع تهيئتها مرة واحدة.
  • إنشاء كائنات داخل draw(_:) — إنشاءات UIColor و UIFont و UIGraphicsImageRenderer داخل draw(_:) تخلق حملاً غير ضروري على الذاكرة. انقل إنشاء الكائنات إلى خصائص الفئة.
  • تجاهل معامل rect — يحدد rect المنطقة التي تحتاج إلى إعادة رسم. الرسم خارج rect يتم تجاهله من قبل النظام ولكنه يستهلك الموارد. تحقق من التقاطع مع rect قبل الرسم.
  • عدم استدعاء super.draw(rect) — على الرغم من أن تنفيذ UIView فارغ، يوصي Apple باستدعاء super.draw(rect) للتوافق مع التغييرات المستقبلية في UIKit.

باتباع هذه القواعد، ستضمن رسماً مخصصاً ثابتاً وسريعاً في أي مشروع iOS. قم بتحليل draw(_:) عبر Instruments (Core Animation) لرؤية وقت التنفيذ الفعلي والاختناقات.

الأسئلة الشائعة

هل يمكن استدعاء draw(_:) مباشرة؟

لا، الاستدعاء المباشر لـ draw(_:) محظور من قبل وثائق Apple. يدير النظام دورة الرسم بنفسه. لطلب إعادة الرسم، استخدم setNeedsDisplay()، الذي يحدد العرض بشكل صحيح على أنه يحتاج إلى تحديث في أقرب دورة عرض.

كيف يختلف drawRect: عن draw(_:)?

وظيفياً هاتان الطريقتان متطابقتان. drawRect: يُستخدم في Objective-C، draw(_:) في Swift. كلتاهما تستقبل CGRect لمنطقة إعادة الرسم وتستخدمان نفس سياق Core Graphics عبر UIGraphicsGetCurrentContext().

لماذا لا يتم استدعاء draw(_:) لـ UIView فارغ؟

يحسن Apple العرض: إذا لم يكن لدى UIView draw(_:) مُعاد تعريفه، لا ينشئ النظام سياق bitmap له. هذا يوفر الذاكرة. إذا كان التجاوز موجوداً ولكن الطريقة لا تُستدعى — تحقق من أن إطار العرض غير صفري وأن العرض مرئي.

كم مرة يستدعي النظام draw(_:)?

فقط عند الحاجة: العرض الأول، بعد setNeedsDisplay()، عند تغيير bounds مع contentMode = .redraw. في الحالة الثابتة، لا يتم استدعاء draw(_:) مرة أخرى، مما يوفر موارد CPU والبطارية.

هل أحتاج إلى استدعاء super.draw(rect) في Swift؟

يوصي Apple باستدعاء super.draw(rect) في بداية الطريقة المُعاد تعريفها. على الرغم من أن التنفيذ الحالي لـ UIView فارغ، فإن استدعاء super يضمن التوافق مع الإصدارات المستقبلية من UIKit وهو ممارسة جيدة.

الملخص

  • draw(_:) / drawRect — طريقة الرسم الرئيسية لـ UIView في Swift و Objective-C، تُستدعى تلقائياً بواسطة النظام
  • CGContext — سياق Core Graphics المتاح داخل draw(_:) عبر UIGraphicsGetCurrentContext()
  • setNeedsDisplay() — الطريقة الصحيحة لطلب إعادة الرسم؛ الاستدعاء المباشر لـ draw(_:) محظور
  • التماثل — يجب أن يعطي draw(_:) نفس النتيجة مع نفس بيانات الإدخال للتخزين المؤقت المناسب
  • UIBezierPath — أنشئ المسارات خارج draw(_:) لتقليل حمل CPU في كل استدعاء
  • معامل rect — يحتوي على منطقة إعادة الرسم؛ استخدم التقاطع للتحسين — لا ترسم خارجه
  • التحليل — تحقق من أداء draw(_:) عبر Instruments Core Animation لتحديد العمليات البطيئة

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا