setNeedsDisplay() هي طريقة مثيل لـ UIView تضع علامة على العرض كأنه بحاجة إلى إعادة الرسم. على عكس الاستدعاء المباشر لـ draw(_:)، فإن setNeedsDisplay() تضع فقط علامة dirty للعرض وتعيد التحكم فوراً. يعالج النظام هذا الطلب في أقرب دورة عرض، مما يسمح بدمج عدة استدعاءات في عملية رسم واحدة. وفقاً لوثائق Apple (2025)، فإن إعادة الرسم المؤجلة عبر setNeedsDisplay() هي الطريقة الصحيحة الوحيدة لتحديث محتوى UIView مخصص.
الخلاصة
setNeedsDisplay() هي طريقة UIView تشير للنظام أن محتوى العرض قديم ويحتاج إلى إعادة عرض. الطريقة لا تأخذ معاملات وتعيد void. بعد الاستدعاء، يتم وضع علامة على العرض كـ dirty، وفي أقرب دورة رسم يستدعي النظام draw(_:) لهذا العرض.
السمة الرئيسية هي عدم التزامن. الاستدعاءات المتعددة المتتالية لـ setNeedsDisplay() لنفس العرض لن تؤدي إلى استدعاءات متعددة لـ draw(_:) — يقوم النظام بدمجها في عملية واحدة. هذا مهم جداً للأداء: إذا تغيرت خاصية العرض عدة مرات في الإطار، يتم استدعاء draw(_:) مرة واحدة فقط.
آلية إعادة الرسم المؤجلة تعتمد على دورة عرض UIKit. عند استدعاء setNeedsDisplay()، يقوم النظام بتعيين العلامة الداخلية layer.needsDisplay إلى true. في نهاية run loop الحالي، يمر النظام عبر جميع الطبقات dirty ويستدعي draw(_:) لكل منها.
run loop في UIKit يتضمن مرحلة تحديث يتم خلالها معالجة العروض dirty. في هذه المرحلة، يتم استدعاء layoutSubviews() إذا لزم الأمر، ثم draw(_:) لجميع العروض المحددة. بعد اكتمال الرسم، يتم تمرير النتيجة إلى GPU عبر Core Animation للتركيب.
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(_:) تأخذ معامل rect من نوع CGRect، مما يحدد منطقة إعادة الرسم. هذا يسمح بإعادة رسم الجزء المتغير فقط من العرض، بدلاً من العرض بأكمله. يصبح فرق الأداء ملحوظاً عند العمل مع عروض كبيرة حيث تؤثر التغييرات على منطقة صغيرة.
| الخاصية | setNeedsDisplay() | setNeedsDisplay(_:) |
|---|---|---|
| المعامل | لا يوجد | CGRect — منطقة إعادة الرسم |
| منطقة إعادة الرسم | كل bounds العرض | فقط المستطيل المحدد |
| الأداء | أقل في العروض الكبيرة | أعلى في التغييرات الجزئية |
| معامل rect في draw(_:) | كل bounds | المستطيل المحدد |
| متى تستخدم | تغيير المحتوى بالكامل | تغييرات نقطية (مؤشر، تمييز) |
للرسوميات المعقدة، يُوصى برسم المشهد بأكمله في سياق خارج الشاشة أولاً، ثم استدعاء setNeedsDisplay(_:) فقط للمنطقة المتغيرة. هذه تقنية قياسية في تطبيقات الرسم ومحررات الرسوميات على iOS.
displayIfNeeded() هي طريقة متزامنة تبدأ فوراً إعادة رسم جميع العروض dirty في التسلسل الهرمي، دون انتظار نهاية run loop. على عكس setNeedsDisplay() التي تضع علامة فقط، فإن displayIfNeeded() تفرض التنفيذ الفوري لـ draw(_:).
توصي Apple باستخدام displayIfNeeded() فقط في الحالات القصوى عندما تكون نتيجة الرسم مطلوبة فوراً — على سبيل المثال، قبل التقاط لقطة شاشة أو أثناء التوليد المتزامن للصور لـ UIGraphicsImageRenderer. في السيناريوهات العادية، النموذج غير المتزامن عبر setNeedsDisplay() هو الأفضل لأنه لا يحجب الخيط الرئيسي.
// غير متزامن — موصى به
someView.setNeedsDisplay()
// متزامن — فقط عند الضرورة
someView.setNeedsDisplay()
someView.displayIfNeeded() // draw(_:) يتم استدعاؤه فوراً
استدعاء displayIfNeeded() بدون setNeedsDisplay() سابق لن يكون له تأثير — الطريقة تعالج فقط العروض المحددة بالفعل كـ dirty. إذا لم يكن أي عرض بحاجة إلى إعادة رسم، يكتمل displayIfNeeded() دون استدعاء draw(_:).
الاستدعاءات المتكررة لـ setNeedsDisplay() يمكن أن تقلل الأداء، خاصة أثناء الرسوم المتحركة. دعنا نلقي نظرة على استراتيجيات التحسين الرئيسية.
بدلاً من استدعاء setNeedsDisplay() عند كل تغيير لخاصية فردية، قم بتجميع التغييرات واستدعاء الطريقة مرة واحدة بعد تطبيق جميع التحديثات. على سبيل المثال، عند تغيير لون وحجم وموضع عنصر — استدع setNeedsDisplay() مرة واحدة بعد تعيين جميع الخصائص.
إذا كان يمكن تحريك خاصية عبر CALayer (backgroundColor, opacity, position, transform)، استخدم Core Animation بدلاً من draw(_:). خصائص CALayer يتم تحديثها على GPU دون استدعاء draw(_:)، مما يوفر 60 إطاراً في الثانية دون تحميل على CPU.
لتجميع عدة تغييرات في تمريرة عرض واحدة، استخدم CATransaction. هذا يسمح بدمج عدة استدعاءات setNeedsDisplay() في عملية draw(_:) واحدة في نهاية المعاملة.
CATransaction.begin()
CATransaction.setDisableActions(true)
view1.setNeedsDisplay()
view2.setNeedsDisplay()
view3.setNeedsDisplay()
CATransaction.commit()
لا تفرط في استخدام displayIfNeeded() — إعادة الرسم المتزامنة تحجب الخيط الرئيسي ويمكن أن تسبب تساقط الإطارات. استخدمها فقط للعمليات حيث تكون نتيجة الرسم حرجة قبل انتهاء run loop الحالي.
الأسئلة الشائعة
لا، الاستدعاء المباشر لـ draw(_:) ممنوع. setNeedsDisplay() تضع علامة على العرض بشكل صحيح كـ dirty، ويقوم النظام باستدعاء draw(_:) في اللحظة المناسبة من دورة العرض. الاستدعاء المباشر لـ draw(_:) يتجاهل آلية التخزين المؤقت ويمكن أن يؤدي إلى حالة غير متناسقة.
يتم دمج الاستدعاءات المتعددة لـ setNeedsDisplay() لنفس العرض بواسطة النظام في استدعاء واحد لـ draw(_:). يحدث هذا لأن الطريقة تضع فقط dirty flag، الذي يتم مسحه فقط بعد التنفيذ الفعلي لـ draw(_:) في نهاية run loop.
setNeedsDisplay() يطلب إعادة رسم محتوى العرض (استدعاء draw(_:)). setNeedsLayout() يطلب إعادة ترتيب العروض الفرعية (استدعاء layoutSubviews()). layoutSubviews() يمكن أن يؤدي إلى تغييرات في الحجم، والتي بدورها قد تؤدي إلى إعادة رسم.
يجب استخدام setNeedsDisplay(_:) للتغييرات النقطية: حركة المؤشر، تغيير لون منطقة محددة، تحديث جزء من رسم بياني. هذا يحد من منطقة إعادة الرسم ويحسن الأداء مقارنة بإعادة رسم العرض بأكمله.
افتراضياً، تغيير frame أو bounds لا يستدعي setNeedsDisplay(). النظام فقط يحرك العرض. إذا كانت إعادة الرسم مطلوبة عند تغيير الحجم، قم بتعيين الخاصية contentMode إلى .redraw — عندها سيقوم UIKit تلقائياً باستدعاء setNeedsDisplay() عند كل تغيير في bounds.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.