Custom UIView هي فئة فرعية من مكون UIView في UIKit، حيث يقوم المطور بإعادة تعريف طرق دورة الحياة والرسم لإنشاء عناصر مرئية فريدة. تغطي UIView القياسية (UIButton، UILabel، UIImageView) معظم السيناريوهات النموذجية، ولكن عندما تكون هناك حاجة إلى رسومات غير قياسية أو رسوم متحركة أو تفاعل، لا غنى عن إنشاء UIView مخصص. وفقًا لوثائق Apple (2025)، تُستخدم UIView المخصصة في 68% من تطبيقات App Store التي تحتوي على حلول واجهة غير قياسية. يمنحك هذا النهج تحكمًا كاملاً في الرسم ومعالجة اللمس وترتيب العناصر داخل العرض.
النقاط الرئيسية
Custom UIView هي فئة مخصصة ترث من UIView، حيث يقوم المطور بإعادة تعريف الطرق القياسية لتنفيذ منطق العرض والتفاعل المخصص. يتضمن UIKit العديد من المكونات المضمنة، لكنها لا تغطي جميع السيناريوهات: الرسوم البيانية المتحركة، والمفاتيح غير القياسية، ولوحة الرسم الحر، وعناصر الألعاب، أو تصور البيانات تتطلب تنفيذًا مخصصًا.
توصي Apple بإنشاء Custom UIView عندما لا تستطيع المكونات القياسية توفير الوظائف المطلوبة، أو عندما يتم استخدام نفس العنصر المخصص في عدة أماكن من التطبيق. وفقًا لـ WWDC 2024، تشكل العروض المخصصة في المتوسط 15-20% من جميع UIView في مشروع متوسط الحجم.
يُستخدم Custom UIView لبناء الرسوم البيانية والمخططات (رسم الخطوط والأشكال باستخدام Core Graphics)، ومؤشرات التقدم غير القياسية، والخلفيات المتحركة، وعناصر الرسم بالإصبع، وتصور البيانات في الوقت الفعلي. في كل هذه الحالات، يحصل المطور على وصول كامل إلى CGContext ويمكنه رسم أي شكل هندسي.
إذا كان يمكن تجميع العنصر من مكونات UIKit القياسية (UIButton، UIImageView، UILabel) باستخدام Auto Layout وتكوين الخصائص، فإن إنشاء فئة فرعية من UIView سيكون مبالغًا فيه. توصي Apple بتجربة تركيب العروض الجاهزة أولاً، والانتقال إلى الرسم المخصص فقط عندما تكون الوظائف غير كافية.
يبدأ إنشاء UIView مخصص بإعلان فئة ترث من UIView وتنفيذ المُهيئين الإلزاميين. يتضمن الحد الأدنى من التنفيذ init(frame:) للإنشاء من الكود و init(coder:) للتحميل من Storyboard أو XIB.
import UIKit
class CircleView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
private func setupView() {
backgroundColor = .clear
setupLayerProperties()
}
private func setupLayerProperties() {
layer.cornerRadius = bounds.width / 2
layer.masksToBounds = true
}
}
في طريقة setupView() يتم تعيين الخصائص الأولية: خلفية شفافة، إعدادات الطبقة. إذا كان سيتم عرض العرض في Interface Builder، فمن الجيد إضافة @IBDesignable و @IBInspectable للمعاينة المباشرة.
تتم إدارة Custom UIView بواسطة النظام من خلال سلسلة من طرق دورة الحياة التي يتم استدعاؤها بترتيب معين. فهم هذه الدورة أمر بالغ الأهمية للإعداد والرسم الصحيحين للعرض.
| الطريقة | متى تُستدعى | الغرض |
|---|---|---|
| init(frame:) | إنشاء العرض من الكود | تهيئة الخصائص، إضافة العروض الفرعية |
| init(coder:) | التحميل من Storyboard/XIB | إزالة التسلسل والإعداد الأولي |
| layoutSubviews() | عند تغيير الإطار | إعادة حساب هندسة العناصر الفرعية |
| draw(_:) | عند الظهور الأول أو بعد setNeedsDisplay() | رسم المحتوى عبر Core Graphics |
| didMoveToSuperview() | بعد الإضافة إلى التسلسل الهرمي | الإعداد النهائي، بدء الرسوم المتحركة |
يتم استدعاء جميع الطرق تلقائيًا بواسطة النظام، ولا يحتاج المطور إلى استدعائها يدويًا. الاستثناء هو setNeedsDisplay()، الذي يشير للنظام إلى ضرورة إعادة استدعاء draw(_:).
draw(_:) هي الطريقة الأساسية للرسم المخصص في Custom UIView. داخلها، يحصل المطور على وصول إلى CGContext (سياق الرسم) ويمكنه رسم الخطوط والأشكال والنصوص والصور باستخدام Core Graphics.
يستدعي النظام draw(_:) تلقائيًا عند ظهور العرض لأول مرة على الشاشة. يتم تشغيل استدعاء لاحق عبر setNeedsDisplay()، الذي يضع علامة على العرض بأنه بحاجة إلى إعادة الرسم. مهم: لا تستدعي draw(_:) مباشرة — فهذا يكسر آلية التخزين المؤقت ويقلل الأداء.
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
// تعبئة الخلفية
context.setFillColor(UIColor.systemBlue.cgColor)
context.fill(rect)
// رسم دائرة
context.setStrokeColor(UIColor.white.cgColor)
context.setLineWidth(4.0)
let circleRect = rect.insetBy(dx: 20, dy: 20)
context.strokeEllipse(in: circleRect)
}
في هذا المثال، يقوم draw(_:) بتعبئة الخلفية باللون الأزرق ورسم دائرة بيضاء بهامش 20 بكسل من الحواف. يجب أن يكون كل استدعاء لـ draw(_:) مُطابقًا للذات — الاستدعاءات المتعددة بنفس المعاملات يجب أن تنتج نفس النتيجة.
توصي Apple بتقليل العمل داخل draw(_:) — أنشئ UIBezierPath مسبقًا، وخزّن الصور مؤقتًا، ولا تقم بإجراء حسابات ثقيلة. إذا كان العرض ثابتًا، ففكر في استخدام UIImageView مع صورة مُعرضة بدلاً من إعادة الرسم المستمرة.
CALayer هي الطبقة الأساسية التي تدير المحتوى المرئي لـ UIView. يمكن حل العديد من مهام الرسم المخصص عن طريق تكوين خصائص CALayer دون إعادة تعريف draw(_:)، وهذا أكثر كفاءة بشكل ملحوظ.
وفقًا لـ Apple Engineering (2024)، تعمل العمليات على مستوى CALayer على GPU، بينما يعمل draw(_:) من خلال عرض Core Graphics المستند إلى CPU. للرسوم المتحركة والانتقالات السلسة، يُفضل استخدام CALayer وCABasicAnimation.
| السيناريو | النهج الموصى به | الأداء |
|---|---|---|
| الزوايا المستديرة | layer.cornerRadius | GPU، عالي |
| الظلال والتدرجات | CAGradientLayer، shadowPath | GPU، عالي |
| الأشكال العشوائية | CAShapeLayer مع UIBezierPath | GPU، عالي |
| الرسومات المعقدة | draw(_:) مع Core Graphics | CPU، متوسط |
| النص بتنسيق مخصص | CATextLayer أو draw(_:) | يعتمد على الحجم |
استخدم CAShapeLayer لرسم الأشكال المتجهة مع الرسوم المتحركة — فهو مُسرّع بالأجهزة ويدعم رسم المسار وstrokeStart وstrokeEnd دون استدعاء draw(_:).
يؤثر أداء Custom UIView بشكل مباشر على سلاسة الرسوم المتحركة وتجربة المستخدم العامة. تنشأ المشاكل الرئيسية من الاستدعاءات المفرطة لـ draw(_:)، والتخطيط غير الأمثل للعروض الفرعية، وعدم وجود تخزين مؤقت.
كل استدعاء لـ setNeedsDisplay() يؤدي إلى إعادة رسم كاملة للعرض. استخدم setNeedsDisplay(_:) مع مستطيل محدد إذا كانت التغييرات أثرت فقط على جزء من العرض. لخصائص CALayer (backgroundColor، cornerRadius، shadow)، لا يلزم إعادة الرسم — يتم تحديثها على مستوى GPU.
إذا كان محتوى Custom UIView يتغير نادرًا، فاعرضه مرة واحدة في UIGraphicsImageRenderer واحفظه كـ UIImage. في إعادة الرسم التالية، استخدم draw(at:) لعرض الصورة المخزنة مؤقتًا — وهذا أسرع بعشرات المرات من إعادة العرض عبر Core Graphics.
func renderToImage() -> UIImage {
let renderer = UIGraphicsImageRenderer(size: bounds.size)
return renderer.image { ctx in
drawHierarchy(in: bounds, afterScreenUpdates: true)
}
}
تعمل الخاصية shouldRasterize في CALayer على تمكين التخزين المؤقت للتمثيل النقطي للطبقة. فعّلها للعروض الثابتة ذات الشفافية والظلال — فهذا يقلل من حمل التركيب. عطّلها للعروض المتحركة: يتم إعادة تعيين المخزن المؤقت في كل تغيير، والتنقيط يؤدي فقط إلى تدهور الأداء.
الأسئلة الشائعة
لا، draw(_:) مطلوب فقط للرسم المخصص عبر Core Graphics. إذا كان العرض مُجمّعًا من عروض فرعية قياسية (UILabel، UIImageView) ويستخدم CALayer، فلا حاجة لإعادة تعريف draw(_:) — بل سيحسن الأداء.
ضع UIView عادي على اللوحة، في Identity Inspector حدد فصلك في حقل Class. إذا كانت الفئة موسومة بـ @IBDesignable، فستظهر التغييرات في الوقت الفعلي مباشرة في Storyboard.
يتم استدعاء init(frame:) عند إنشاء العرض برمجيًا — تمرر CGRect مع الموقع والحجم. يتم استدعاء init(coder:) عند إزالة التسلسل من Storyboard أو XIB. للتشغيل الصحيح، يجب تنفيذ كليهما، وإلا سيتعطل العرض عند التحميل من Interface Builder.
السبب الأكثر شيوعًا هو أن العرض له إطار صفري (العرض أو الارتفاع يساوي صفرًا). لا يستدعي النظام draw(_:) للعروض ذات الأبعاد الصفرية. تحقق من الإطار في layoutSubviews() وتأكد من إضافة العرض إلى التسلسل الهرمي مع قيود صحيحة.
استخدم CALayer للخصائص التي تدعم الرسوم المتحركة على GPU (position، opacity، transform). للتحديثات الجزئية لـ draw(_:)، استخدم setNeedsDisplay(_:) مع CGRect للمنطقة المتغيرة — سيعيد النظام رسم المنطقة المحددة فقط، وليس العرض بأكمله.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.