setNeedsDisplay(): جوهر الطريقة وآلية إعادة الرسم في iOS

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

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

الخلاصة

  • setNeedsDisplay() — طلب إعادة رسم مؤجل لـ UIView، لا يستدعي draw(_:) بشكل متزامن
  • dirty flag — علامة داخلية للعرض يتم تعيينها بواسطة setNeedsDisplay() ويتم مسحها بعد draw(_:)
  • setNeedsDisplay(_:) — تحميل زائد مع CGRect لإعادة رسم جزئي للمنطقة المحددة فقط
  • displayIfNeeded() — إعادة رسم متزامن إجباري، يستخدم للتحديثات الفورية
  • دورة العرض — يعالج النظام العروض dirty في نهاية run loop، مما يجمع عدة طلبات في تمريرة واحدة

ما هو setNeedsDisplay()

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

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

كيف تعمل إعادة الرسم المؤجلة

آلية إعادة الرسم المؤجلة تعتمد على دورة عرض UIKit. عند استدعاء setNeedsDisplay()، يقوم النظام بتعيين العلامة الداخلية layer.needsDisplay إلى true. في نهاية run loop الحالي، يمر النظام عبر جميع الطبقات dirty ويستدعي draw(_:) لكل منها.

Run loop وخطوات العرض

run loop في UIKit يتضمن مرحلة تحديث يتم خلالها معالجة العروض dirty. في هذه المرحلة، يتم استدعاء layoutSubviews() إذا لزم الأمر، ثم draw(_:) لجميع العروض المحددة. بعد اكتمال الرسم، يتم تمرير النتيجة إلى GPU عبر Core Animation للتركيب.

swift
class ProgressView: UIView {
    var progress: CGFloat = 0 {
        didSet {
            setNeedsDisplay()
        }
    }

    override func draw(_ rect: CGRect) {
        super.draw(rect)
        guard let ctx = UIGraphicsGetCurrentContext() else { return }

        // الخلفية
        ctx.setFillColor(UIColor.lightGray.cgColor)
        ctx.fill(rect)

        // شريط التقدم
        let fillRect = CGRect(x: rect.minX, y: rect.minY,
                              width: rect.width * progress, height: rect.height)
        ctx.setFillColor(UIColor.systemGreen.cgColor)
        ctx.fill(fillRect)
    }
}

في هذا المثال، progressView يحدّث progress، وكل setter يستدعي تلقائياً setNeedsDisplay(). ينتظر النظام نهاية run loop ويستدعي draw(_:) بقيمة progress الفعلية. إذا تغير progress ثلاث مرات في الإطار، يتم تنفيذ draw(_:) مرة واحدة فقط — بأحدث قيمة.

setNeedsDisplay() مقابل setNeedsDisplay(_:)

الطريقة setNeedsDisplay(_:) تأخذ معامل rect من نوع CGRect، مما يحدد منطقة إعادة الرسم. هذا يسمح بإعادة رسم الجزء المتغير فقط من العرض، بدلاً من العرض بأكمله. يصبح فرق الأداء ملحوظاً عند العمل مع عروض كبيرة حيث تؤثر التغييرات على منطقة صغيرة.

الخاصيةsetNeedsDisplay()setNeedsDisplay(_:)
المعامللا يوجدCGRect — منطقة إعادة الرسم
منطقة إعادة الرسمكل bounds العرضفقط المستطيل المحدد
الأداءأقل في العروض الكبيرةأعلى في التغييرات الجزئية
معامل rect في draw(_:)كل boundsالمستطيل المحدد
متى تستخدمتغيير المحتوى بالكاملتغييرات نقطية (مؤشر، تمييز)

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

displayIfNeeded() ضد setNeedsDisplay()

displayIfNeeded() هي طريقة متزامنة تبدأ فوراً إعادة رسم جميع العروض dirty في التسلسل الهرمي، دون انتظار نهاية run loop. على عكس setNeedsDisplay() التي تضع علامة فقط، فإن displayIfNeeded() تفرض التنفيذ الفوري لـ draw(_:).

توصي Apple باستخدام displayIfNeeded() فقط في الحالات القصوى عندما تكون نتيجة الرسم مطلوبة فوراً — على سبيل المثال، قبل التقاط لقطة شاشة أو أثناء التوليد المتزامن للصور لـ UIGraphicsImageRenderer. في السيناريوهات العادية، النموذج غير المتزامن عبر setNeedsDisplay() هو الأفضل لأنه لا يحجب الخيط الرئيسي.

swift
    // غير متزامن — موصى به
someView.setNeedsDisplay()

    // متزامن — فقط عند الضرورة
someView.setNeedsDisplay()
someView.displayIfNeeded() // draw(_:) يتم استدعاؤه فوراً

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

تحسين استدعاءات setNeedsDisplay()

الاستدعاءات المتكررة لـ setNeedsDisplay() يمكن أن تقلل الأداء، خاصة أثناء الرسوم المتحركة. دعنا نلقي نظرة على استراتيجيات التحسين الرئيسية.

تجميع التغييرات عبر نموذج

بدلاً من استدعاء setNeedsDisplay() عند كل تغيير لخاصية فردية، قم بتجميع التغييرات واستدعاء الطريقة مرة واحدة بعد تطبيق جميع التحديثات. على سبيل المثال، عند تغيير لون وحجم وموضع عنصر — استدع setNeedsDisplay() مرة واحدة بعد تعيين جميع الخصائص.

استخدام CALayer للخصائص القابلة للتحريك

إذا كان يمكن تحريك خاصية عبر CALayer (backgroundColor, opacity, position, transform)، استخدم Core Animation بدلاً من draw(_:). خصائص CALayer يتم تحديثها على GPU دون استدعاء draw(_:)، مما يوفر 60 إطاراً في الثانية دون تحميل على CPU.

التحديث الدفعي عبر CATransaction

لتجميع عدة تغييرات في تمريرة عرض واحدة، استخدم CATransaction. هذا يسمح بدمج عدة استدعاءات setNeedsDisplay() في عملية draw(_:) واحدة في نهاية المعاملة.

swift
CATransaction.begin()
CATransaction.setDisableActions(true)

view1.setNeedsDisplay()
view2.setNeedsDisplay()
view3.setNeedsDisplay()

CATransaction.commit()

لا تفرط في استخدام displayIfNeeded() — إعادة الرسم المتزامنة تحجب الخيط الرئيسي ويمكن أن تسبب تساقط الإطارات. استخدمها فقط للعمليات حيث تكون نتيجة الرسم حرجة قبل انتهاء run loop الحالي.

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

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

لا، الاستدعاء المباشر لـ draw(_:) ممنوع. setNeedsDisplay() تضع علامة على العرض بشكل صحيح كـ dirty، ويقوم النظام باستدعاء draw(_:) في اللحظة المناسبة من دورة العرض. الاستدعاء المباشر لـ draw(_:) يتجاهل آلية التخزين المؤقت ويمكن أن يؤدي إلى حالة غير متناسقة.

ماذا يحدث مع الاستدعاءات المتعددة لـ setNeedsDisplay()؟

يتم دمج الاستدعاءات المتعددة لـ setNeedsDisplay() لنفس العرض بواسطة النظام في استدعاء واحد لـ draw(_:). يحدث هذا لأن الطريقة تضع فقط dirty flag، الذي يتم مسحه فقط بعد التنفيذ الفعلي لـ draw(_:) في نهاية run loop.

كيف يختلف setNeedsDisplay() عن setNeedsLayout()؟

setNeedsDisplay() يطلب إعادة رسم محتوى العرض (استدعاء draw(_:)). setNeedsLayout() يطلب إعادة ترتيب العروض الفرعية (استدعاء layoutSubviews()). layoutSubviews() يمكن أن يؤدي إلى تغييرات في الحجم، والتي بدورها قد تؤدي إلى إعادة رسم.

متى تستخدم setNeedsDisplay(_:)؟

يجب استخدام setNeedsDisplay(_:) للتغييرات النقطية: حركة المؤشر، تغيير لون منطقة محددة، تحديث جزء من رسم بياني. هذا يحد من منطقة إعادة الرسم ويحسن الأداء مقارنة بإعادة رسم العرض بأكمله.

هل تغيير frame يستدعي setNeedsDisplay() تلقائياً؟

افتراضياً، تغيير frame أو bounds لا يستدعي setNeedsDisplay(). النظام فقط يحرك العرض. إذا كانت إعادة الرسم مطلوبة عند تغيير الحجم، قم بتعيين الخاصية contentMode إلى .redraw — عندها سيقوم UIKit تلقائياً باستدعاء setNeedsDisplay() عند كل تغيير في bounds.

الملخص

  • setNeedsDisplay() — طريقة غير متزامنة لـ UIView لطلب إعادة رسم مؤجلة عن طريق تعيين dirty flag
  • setNeedsDisplay(_:) — تحميل زائد مع CGRect لإعادة رسم جزئي للمنطقة المحددة فقط، مما يحسن الأداء
  • displayIfNeeded() — طريقة متزامنة لإعادة رسم فورية للعروض dirty، تستخدم فقط عند الضرورة القصوى
  • Run loop — يعالج النظام العروض dirty في نهاية الدورة الحالية، مما يجمع عدة استدعاءات في تمريرة draw(_:) واحدة
  • CALayer — للخصائص القابلة للتحريك (position, opacity) استخدم Core Animation بدلاً من draw(_:) — إنها أسرع بكثير
  • CATransaction — تجمع عدة تغييرات في دورة عرض واحدة، مما يقلل عدد استدعاءات draw(_:)
  • contentMode = .redraw — يفعّل الاستدعاء التلقائي لـ setNeedsDisplay() عند تغيير bounds العرض

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

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

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

اقرأ أيضًا