UIBezierPath هي فئة من UIKit تغلف مساراً متجهاً للرسومات ثنائية الأبعاد في iOS. تجمع بين الوصف الهندسي للمحيط وإمكانيات الرسم، مما يتيح إنشاء الخطوط ومنحنيات بيزيه والأقواس والمستطيلات. على عكس CGPath، يحتوي UIBezierPath على طرق الرسم الخاصة به التي تعمل مباشرة في سياق الرسم الحالي. وفقاً لـ Apple Developer Documentation, 2026، يدعم UIBezierPath جميع عمليات Quartz 2D القياسية في غلاف موجه للكائنات.
النقاط الرئيسية
UIBezierPath هي فئة من إطار UIKit، ترث من NSObject وتنفذ NSCopying و NSSecureCoding. توفر واجهة برمجة تطبيقات Objective-C/Swift عالية المستوى لبناء ورسم المسارات المتجهة. داخلياً، يخزن UIBezierPath مرجعاً إلى CGPathRef — كائن Core Graphics — ولكنه يضيف طرقاً ملائمة للرسم وتعديل الهندسة وإدارة الأنماط.
الميزة الرئيسية لـ UIBezierPath مقارنة بالاستخدام المباشر لـ CGPath هي طرق الرسم المدمجة. استدعاء fill() أو stroke() على مثيل UIBezierPath يضبط السياق تلقائياً ويرسم المسار. تدعم الفئة أيضاً الخصائص lineWidth و lineCapStyle و lineJoinStyle و miterLimit و flatness، مما يتيح التحكم في المظهر دون كائن منفصل لتخزين الأنماط.
يُستخدم UIBezierPath في معظم تطبيقات iOS للرسم المخصص داخل UIView: من الأيقونات البسيطة إلى الرسوم البيانية المتحركة المعقدة. طريقة draw(_:) من فئة UIView هي المكان الرئيسي حيث ينشئ المطورون ويرسمون UIBezierPath. الفئة متوافقة تماماً مع Core Animation ويمكن استخدامها لتحريك المسار عبر CAShapeLayer و CABasicAnimation.
طريقة move(to:) تنقل نقطة بداية المسار إلى الإحداثيات المحددة دون رسم. هذا استدعاء إلزامي قبل بناء أي مقطع. addLine(to:) تضيف مقطعاً مستقيماً من الموضع الحالي إلى النقطة المحددة. طريقة close() تغلق المحيط برسم خط من آخر نقطة إلى أول نقطة لآخر move(to:).
UIBezierPath يوفر عدة مهيئات ملائمة للأشكال الجاهزة. init(rect:) ينشئ مستطيلاً، init(roundedRect:cornerRadius:) ينشئ مستطيلاً بزوايا مدورة. المعامل UIRectCorner يتيح تدوير الزوايا المحددة فقط، مثل topLeft و bottomRight، مع ترك الباقي مستقيماً. يُستخدم هذا على نطاق واسع لإنشاء أزرار وبطاقات مخصصة.
init(ovalIn:) ينشئ قطعاً ناقصاً محصوراً داخل المستطيل المحدد. إذا كان المستطيل مربعاً، يتحول القطع الناقص إلى دائرة. init(roundedRect:byRoundingCorners:cornerRadii:) هو الخيار الأكثر مرونة، مما يتيح تحديد أنصاف أقطار مختلفة لكل زاوية. ظهر هذا المهيئ في iOS 6 ولا يزال المعيار لإنشاء حاويات مدورة.
let path = UIBezierPath()
path.move(to: CGPoint(x: 50, y: 50))
path.addLine(to: CGPoint(x: 200, y: 50))
path.addLine(to: CGPoint(x: 200, y: 150))
path.addLine(to: CGPoint(x: 125, y: 200))
path.addLine(to: CGPoint(x: 50, y: 150))
path.close()
addQuadCurve(to:controlPoint:) تضيف منحنى بيزيه تربيعياً بنقطة تحكم واحدة. يُستخدم هذا النوع من المنحنيات للانحناءات الناعمة البسيطة عندما يكون من الضروري تقليل الحسابات. المنحنى التربيعي هو دائماً قطع مكافئ ويضمن أنه يقع داخل المثلث المكون من نقاط البداية والتحكم والنهاية.
addCurve(to:controlPoint1:controlPoint2:) تضيف منحنى بيزيه تكعيبياً بنقطتي تحكم. تُستخدم المنحنيات التكعيبية في جميع برامج تحرير المتجهات الحديثة وخطوط TrueType. توفر نقطتا التحكم درجات حرية أكبر للتحكم الدقيق في الشكل. يُوصى بهذه الطريقة لبناء محيطات عضوية ومعقدة.
عند بناء المنحنيات، من المهم الحفاظ على مبدأ الاستمرارية — النعومة عند نقطة اتصال المقاطع. للاستمرارية C1 (المماس)، يجب أن تقع نقطة تحكم المنحنى التالي على نفس خط نقطة تحكم المنحنى السابق. للاستمرارية C2 (الانحناء)، يلزم تنسيق أكثر تعقيداً، يتم توفيره تلقائياً في CAShapeLayer عند التحرك بين UIBezierPath بنفس عدد نقاط التحكم.
| الطريقة | النوع | نقاط التحكم | الدرجة |
|---|---|---|---|
| addQuadCurve | بيزيه تربيعي | 1 | 2 |
| addCurve | بيزيه تكعيبي | 2 | 3 |
| addArc | قوس | — | — |
addArc(withCenter:radius:startAngle:endAngle:clockwise:) تضيف قوساً دائرياً بالمعاملات المحددة. تُحدد الزوايا بالراديان بالنسبة لمركز الدائرة. الزاوية الصفرية تقابل الاتجاه لليمين (الساعة 3 على وجه الساعة). المعامل clockwise يحدد اتجاه المسار: true في اتجاه عقارب الساعة، false عكس عقارب الساعة. الأقواس لا غنى عنها للرسوم البيانية الدائرية وعدادات السرعة وحلقات التقدم التفاعلية.
لدائرة كاملة، استخدم startAngle = 0 و endAngle = .pi * 2. لإنشاء قطاع (كجزء من رسم بياني دائري)، استخدم addArc متبوعاً بـ addLine(to:) إلى المركز و close(). لا يحتوي UIBezierPath على طريقة addCircle مدمجة — يتم إنشاء الدائرة عبر init(ovalIn:) بمستطيل مربع أو عبر addArc بزاوية كاملة.
طريقة addArc(withCenter:radius:startAngle:endAngle:clockwise:) تنفذ تلقائياً move(to:) إلى نقطة بداية القوس إذا كان الموضع الحالي لا يتطابق معها. إذا كان الموضع الحالي مختلفاً، يتم رسم خط مستقيم بينه وبين بداية القوس. يختلف هذا السلوك عن Core Graphics، حيث يتطلب move(to:) صريحاً قبل كل محيط جديد.
let center = CGPoint(x: 150, y: 150)
let slice = UIBezierPath()
slice.move(to: center)
slice.addArc(
withCenter: center,
radius: 100,
startAngle: 0,
endAngle: .pi * 0.75,
clockwise: true
)
slice.close()
fill() تملأ المنطقة الداخلية للمسار بلون التعبئة الحالي fillColor أو اللون المحدد في سياق الرسم عبر setFill(). للمسارات ذات التقاطعات الذاتية، تحدد قاعدة التعبئة بواسطة الخاصية usesEvenOddFillRule. إذا كانت true، تُستخدم قاعدة الزوجي-الفردي: تعتبر النقطة داخل الشكل إذا كان شعاع منها يعبر المحيط عدداً فردياً من المرات.
stroke() ترسم محيط المسار بالإعدادات الحالية lineWidth و lineCapStyle و lineJoinStyle. lineCapStyle يحدد شكل نهايات المقاطع المفتوحة: .butt (قطع مستقيم)، .round (نصف دائري)، .square (مربع ببروز). lineJoinStyle يحدد شكل زوايا الوصلات: .miter (حاد)، .round (مدور)، .bevel (مشطوف).
للخطوط المتقطعة تُستخدم الخاصية setLineDash(_:count:phase:). تقبل مصفوفة من أطوال الشرط والفراغات المتناوبة، وعدد العناصر، والمرحلة الأولية. يتم عرض التقطيع أثناء تنقيط المسار — الأنماط المعقدة لا تزيد من التعقيد الهندسي للمحيط. يدعم CAShapeLayer تحريك الخاصية strokeEnd لتأثير «الرسم» من البداية إلى النهاية.
override func draw(_ rect: CGRect) {
let path = UIBezierPath(ovalIn: CGRect(x: 50, y: 50, width: 200, height: 200))
UIColor.blue.setFill()
UIColor.black.setStroke()
path.lineWidth = 4
path.fill()
path.stroke()
}
لننشئ مؤشر تقدم مخصصاً على شكل حلقة باستخدام UIBezierPath. تُبنى الحلقة من قوسين بنفس نصف القطر بلوني رسم مختلفين. القوس الأول دائرة كاملة باللون الرمادي (خلفية)، والثاني قطاع يظهر التقدم. للتحريك، يُستخدم CAShapeLayer و CABasicAnimation.
class RingView: UIView {
private let progressLayer = CAShapeLayer()
override func layoutSubviews() {
super.layoutSubviews()
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let radius = bounds.width * 0.4
let lineWidth: CGFloat = 12
let backgroundRing = UIBezierPath()
backgroundRing.addArc(withCenter: center, radius: radius,
startAngle: -.pi / 2, endAngle: .pi * 1.5, clockwise: true)
progressLayer.path = backgroundRing.cgPath
progressLayer.strokeColor = UIColor.systemBlue.cgColor
progressLayer.lineWidth = lineWidth
progressLayer.lineCap = .round
progressLayer.fillColor = UIColor.clear.cgColor
progressLayer.strokeEnd = 0
layer.addSublayer(progressLayer)
}
func animateProgress(to value: CGFloat) {
CABasicAnimation(keyPath: "strokeEnd").apply {
$0.toValue = value
$0.duration = 1.5
$0.timingFunction = CAMediaTimingFunction(name: .easeOut)
progressLayer.add($0, forKey: nil)
}
progressLayer.strokeEnd = value
}
}
في هذا المثال، يُستخدم UIBezierPath لإنشاء محيط دائري، يُمرر بعد ذلك إلى CAShapeLayer عبر الخاصية cgPath. تحريك strokeEnd من 0 إلى 1 يُظهر امتلاء الحلقة. يُستخدم هذا النهج في مؤشرات التحميل ومتتبعات اللياقة وودجات التقدم. تضمن طريقة layoutSubviews إعادة بناء المسار عند تغير حجم View.
lineCap = .round يضيف نهايات مدورة للقوس، مما يعطي المؤشر مظهراً أكثر أناقة. الخاصية strokeEnd هي خاصية قابلة للتحريك في CAShapeLayer ولا تتطلب إعادة رسم المحيط بأكمله في كل إطار. يتم التحريك بالكامل على GPU عبر Core Animation، مما يضمن 60 FPS بدون تحميل على CPU.
الأسئلة الشائعة
UIBezierPath هو غلاف موجه للكائنات حول CGPath يحتوي على طرق fill() و stroke() المدمجة. CGPath هو كائن C غير قابل للتغيير من Core Graphics يتطلب رسماً صريحاً عبر CGContext. UIBezierPath أكثر ملاءمة لـ UIKit، بينما CGPath أكثر كفاءة لإعادة الاستخدام.
استخدم init(roundedRect:byRoundingCorners:cornerRadii:) مع المعاملين .topLeft و .topRight لـ roundedRect. هذه هي الطريقة الوحيدة في UIBezierPath التي تسمح بتدوير الزوايا بشكل انتقائي دون إنشاء مسار مركب معقد.
الخاصية cgPath تُرجع CGPathRef غير قابل للتغيير يمكن تمريره إلى CAShapeLayer أو استخدامه بألوان وسماكات مختلفة. لا تؤثر تعديلات UIBezierPath (تحريك النقاط) على cgPath الذي تم الحصول عليه سابقاً حتى يتم تحديثه صراحةً.
تحقق من usesEvenOddFillRule — إذا كان المحيط يحتوي على تقاطعات ذاتية، قد تترك قاعدة الزوجي-الفردي مناطق غير معبأة. اضبط usesEvenOddFillRule = false لقاعدة التعبئة القياسية non-zero winding.
استخدم CABasicAnimation مع مفتاح المسار «path» على CAShapeLayer. يجب أن تحتوي حالتي المسار على نفس العدد من المقاطع ونقاط التحكم — وإلا سيكون التحريك متقطعاً أو لن يعمل.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.