NSAffineTransform هي فئة من AppKit (macOS) تمثل تحولاً أفينياً في الرسوم ثنائية الأبعاد. تقوم بتغليف مصفوفة 3×3 تصف مجموعة من الإزاحة، والقياس، والتدوير، والقص للإحداثيات. وفقاً لوثائق Apple (2026)، يُستخدم NSAffineTransform على نطاق واسع في Core Graphics لتحويل المسارات والنقاط وسياقات الرسوم على macOS.
النقاط الرئيسية
NSAffineTransform هي فئة من إطار العمل AppKit، متاحة فقط على macOS. تمثل تحولاً أفينياً في الفضاء ثنائي الأبعاد من خلال مصفوفة 3×3. على عكس CGAffineTransform من Core Graphics (المتاحة على iOS)، فإن NSAffineTransform هي غلاف كائني التوجه حول هيكل CGAffineTransform، مضيفةً طرقاً ملائمة للعمل مع NSBezierPath وNSPoint وNSSize.
التحول الأفيني هو تحول للمستوى يحافظ على علاقة «النقطة تقع على خط» وتوازي الخطوط. بعبارة أخرى، بعد التحول الأفيني، تبقى الخطوط المتوازية متوازية والخطوط المستقيمة مستقيمة. ومع ذلك، قد تتغير المسافات والزوايا والمساحات: القياس والتدوير والقص هي حالات خاصة من التحولات الأفينية.
على iOS، النظير لـ NSAffineTransform هو هيكل CGAffineTransform من Core Graphics. تعتمد كلتا الواجهتين على نفس النموذج الرياضي — مصفوفة تحول أفيني 3×3 حيث الصف الأخير هو دائماً [0, 0, 1]. الفرق هو أن NSAffineTransform هو فئة (مع retain/release في Objective-C)، بينما CGAffineTransform هو هيكل (نوع قيمة) في Swift وC.
NSAffineTransform يدعم أربعة أنواع أساسية من التحولات: translate (إزاحة)، scale (قياس)، rotate (تدوير)، وshear (قص). كل نوع يتم تمثيله بطريقة منفصلة ويمكن تطبيقه على كائن تحول موجود أو استخدامه لإنشاء كائن جديد.
| العملية | طريقة NSAffineTransform | المعاملات | التأثير البصري |
|---|---|---|---|
| Translate | translateXBy:yBy: | deltaX, deltaY | إزاحة الكائن على طول المحورين X و Y |
| Scale | scaleBy: / scaleXBy:yBy: | factor / scaleX, scaleY | تكبير أو تصغير الحجم |
| Rotate | rotateByDegrees: / rotateByRadians: | الزاوية بالدرجات أو الراديان | تدوير حول نقطة الأصل |
| Shear | عبر setTransformStruct | مكونات المصفوفة m12, m21 | إمالة الكائن على طول المحور |
Translate يضيف إزاحة إلى إحداثيات جميع نقاط الكائن. تحدد المعاملات deltaX و deltaY مقدار الإزاحة الأفقية والرأسية على التوالي. القيمة الموجبة لـ deltaX تُزيح لليمين، و deltaY للأعلى. لا يغير Translate شكل أو حجم الكائن — فقط موضعه.
Scale يضرب إحداثيات النقاط بعامل معين. مع القياس المنتظم (معامل واحد)، يتكبّر الكائن أو يتصغّر بشكل متناسب. مع القياس غير المنتظم، يتشوه الشكل. العامل 1.0 يترك الحجم دون تغيير، أكبر من 1.0 يكبّر، من 0.0 إلى 1.0 يصفّر. القيم السالبة تنشئ انعكاساً مرآتياً.
Rotate يدوّر الكائن حول نقطة الأصل بزاوية معينة. الزاوية الموجبة للتدوير تتوافق مع الدوران عكس اتجاه عقارب الساعة. rotateByDegrees يقبل الدرجات (0–360)، rotateByRadians يقبل الراديان (0–2π). يتم التدوير دائماً بالنسبة للنقطة (0, 0)، لذلك للتدوير حول نقطة عشوائية، تحتاج إلى دمج rotate مع translate.
يتم تمثيل التحول الأفيني في NSAffineTransform بمصفوفة 3×3 حيث الصف الأخير هو دائماً [0, 0, 1]. هذا هو التمثيل الرياضي القياسي الذي يسمح بتمثيل مجموعة من التحولات كحاصل ضرب مصفوفات. يتم تعريف هيكل المصفوفة عبر NSAffineTransformStruct، التي تحتوي على ستة مكونات مهمة: m11, m12, m21, m22, tX, tY.
المكونات m11 و m12 و m21 و m22 مسؤولة عن القياس والتدوير والقص. المكونان tX و tY مسؤولان عن الإزاحة. في التكوين القياسي (مصفوفة الوحدة)، m11 = 1، m22 = 1، والباقي يساوي 0. للحصول على القيم الحالية للمصفوفة، يُستخدم transformStruct — خاصية NSAffineTransform التي تُرجع هيكل NSAffineTransformStruct.
ضرب المصفوفات هو عملية غير تبديلية. هذا يعني أن ترتيب تطبيق التحولات مهم: translate + rotate يعطي نتيجة مختلفة عن rotate + translate. عند الدمج، يقوم NSAffineTransform بضرب المصفوفة الجديدة على اليسار بالمصفوفة الحالية: newMatrix = operation × currentMatrix. لذلك، يتم تطبيق آخر تحول مضاف أولاً.
NSAffineTransform يسمح بدمج تحولات متعددة في كائن واحد. كل استدعاء لـ translateXBy:yBy: أو scaleBy: أو rotateByDegrees: يعدل المصفوفة الداخلية — يضرب المصفوفة الحالية بمصفوفة التحول المقابل. هذا يسمح بإنشاء تحول معقد من سلسلة من العمليات البسيطة.
لـ عكس تحول مدمج، يُستخدم طريقة invert. يُنشئ المعكوس تحولاً أفينياً جديداً يلغي الأصلي: إذا طبقت التحول الأصلي على نقطة ثم المعكوس، تعود النقطة إلى موقعها الأصلي. العكس مفيد لإلغاء تحول السياق قبل رسم عناصر واجهة المستخدم التي لا ينبغي تحويلها.
للتحقق مما إذا كان التحول هو تحول الوحدة (لا يغير الإحداثيات)، استخدم الخاصية isIdentity. تحول الوحدة هو مصفوفة وحدة — الضرب بها يترك إحداثيات النقطة دون تغيير. يتم إنشاء كائن NSAffineTransform جديد كتحول وحدة. هذه الخاصية مفيدة للتحسين: إذا كان التحول هو تحول الوحدة، يمكن تخطي تطبيقه.
NSAffineTransform يمكن تطبيقه على أربعة أنواع من الكائنات: NSPoint و NSSize و NSBezierPath وسياق الرسوم NSGraphicsContext. لكل هدف يوجد طريقة منفصلة: transformPoint: و transformSize: و transformBezierPath: و set.
الـ طريقة transformPoint: تضرب مصفوفة التحول بمتجه إحداثيات النقطة (x, y, 1)، وتعيد الموضع الجديد للنقطة. تعمل transformSize: بشكل مشابه لكنها لا تأخذ في الاعتبار المكونين tX و tY — لا يتم تطبيق الإزاحة، فقط القياس والتدوير والقص. هذا مفيد لتحويل المتجهات والأحجام التي لا ينبغي إزاحتها.
الـ طريقة transformBezierPath: تُنشئ كائن NSBezierPath جديداً، حيث يتم تحويل كل نقطة وفقاً لمصفوفة NSAffineTransform. لا يتم تعديل المسار الأصلي — يتم إنشاء نسخة بإحداثيات محولة. هذه عملية مكلفة للمسارات المعقدة ذات العدد الكبير من العقد.
الـ طريقة set تطبق التحول على سياق الرسوم الحالي (NSGraphicsContext)، معدلةً CTM (مصفوفة التحول الحالية). بعد استدعاء set، سيتم تحويل كل الرسم اللاحق في هذا السياق. للتحولات المؤقتة، استخدم saveGraphicsState / restoreGraphicsState قبل وبعد set.
دعنا نلقي نظرة على أمثلة عملية لاستخدام NSAffineTransform في تطبيق macOS لتحويل الكائنات الرسومية.
هذا المثال يُنشئ NSAffineTransform يدمج الإزاحة والتدوير والقياس. أولاً، يتم إزاحة الكائن بمقدار (100, 50)، ثم تدويره 45 درجة، وأخيراً قياسه 1.5 مرة. الترتيب مهم: التدوير بعد الإزاحة يتم بالنسبة لنقطة الأصل الجديدة.
import AppKit
let transform = NSAffineTransform()
transform.translateXBy(100, yBy: 50)
transform.rotateByDegrees(45)
transform.scaleBy(1.5)
// التطبيق على نقطة
let point = NSPoint(x: 10, y: 20)
let transformedPoint = transform.transform(point)
// التطبيق على حجم
let size = NSSize(width: 100, height: 50)
let transformedSize = transform.transform(size)
هذا المثال يوضح تطبيق NSAffineTransform على سياق رسوم عبر set و concat. بعد تطبيق التحول، يتم كل الرسم اللاحق في نظام الإحداثيات المحول. حفظ حالة الرسوم عبر saveGraphicsState يسمح بالعودة إلى المصفوفة الأصلية.
func drawWithTransform(in rect: NSRect) {
guard let ctx = NSGraphicsContext.current
else { return }
ctx.saveGraphicsState()
let xfm = NSAffineTransform()
xfm.translateXBy(rect.midX, yBy: rect.midY)
xfm.rotateByDegrees(30)
xfm.scaleBy(0.8)
xfm.concat()
// الرسم في نظام إحداثيات محوّل
let path = NSBezierPath(roundedRect: NSRect(
x: -50, y: -50,
width: 100, height: 100
), xRadius: 10, yRadius: 10)
NSColor.blue.setFill()
path.fill()
ctx.restoreGraphicsState()
}
هذا المثال يوضح إنشاء نسخة من NSBezierPath بإحداثيات محولة. يبقى المسار الأصلي دون تغيير، ويمكن رسم النسخة المحولة بشكل منفصل — هذا مفيد لإنشاء عناصر متكررة في مواضع مختلفة.
func createTransformedTriangle() -> NSBezierPath {
let triangle = NSBezierPath()
triangle.move(to: NSPoint(x: 0, y: 0))
triangle.line(to: NSPoint(x: 100, y: 0))
triangle.line(to: NSPoint(x: 50, y: 100))
triangle.close()
let xfm = NSAffineTransform()
xfm.translateXBy(200, yBy: 100)
xfm.scaleXBy(1.5, yBy: 1.5)
xfm.rotateByDegrees(45)
let transformed = xfm.transform(triangle)
return transformed
}
الأسئلة الشائعة
NSAffineTransform هي فئة من AppKit (macOS فقط)، بينما CGAffineTransform هي هيكل من Core Graphics (iOS و macOS). NSAffineTransform هي غلاف كائني التوجه وتوفر طرقاً للعمل مع NSBezierPath. CGAffineTransform هي هيكل خفيف من نوع القيمة يُستخدم مع CGPath.
للتدوير حول المركز، نفذ ثلاث خطوات: 1) إزاحة إلى مركز الكائن (نقل نقطة الأصل إلى المركز)، 2) تدوير بالزاوية المطلوبة، 3) إزاحة للخلف بمتجه المركز السالب. مجموعة الإزاحة—التدوير—الإزاحة تعطي تدويراً حول نقطة عشوائية، وليس حول نقطة الأصل.
لا، NSAffineTransform متاح فقط على macOS كجزء من AppKit. على iOS، يُستخدم هيكل CGAffineTransform من Core Graphics، الذي يوفر إمكانيات مماثلة: CGAffineTransformMakeTranslation و CGAffineTransformMakeScale و CGAffineTransformMakeRotation و CGAffineTransformConcat.
استخدم الطريقة invert، التي تُرجع التحول المعكوس. إذا ضربت التحول الأصلي بالمعكوس، تحصل على مصفوفة وحدة (تحول الوحدة). للتطبيق المؤقت، استدع saveGraphicsState قبل set و restoreGraphicsState بعدها.
استخدم القياس السالب: transform.scaleXBy(-1, yBy: 1) يُنشئ انعكاساً أفقياً، بينما scaleXBy(1, yBy: -1) يُنشئ انعكاساً رأسياً. للانعكاس بالنسبة لمحور عشوائي، ادمج translate (إزاحة إلى المحور)، و scale بقيمة -1، و translate معكوس.
الخلاصة
invert يلغي أي تحول أفينيسنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.