الفرق بين المراجعتين لصفحة: «ReactNative/native components ios»

من موسوعة حسوب
إضافة الصّفحة
 
طلا ملخص تعديل
 
(6 مراجعات متوسطة بواسطة 3 مستخدمين غير معروضة)
سطر 1: سطر 1:
<noinclude>{{DISPLAYTITLE: مكونات واجهة المستخدم الأصيلة في React Native}}</noinclude>
<noinclude>{{DISPLAYTITLE: مكونات واجهة المستخدم الأصيلة لنظام iOS في React Native}}</noinclude>
هناك الكثير من أدوات واجهة المستخدم (UI widgets) الأصيلة الجاهزة للاستخدام في أحدث التطبيقات، بعضها جزء من المنصّة، والبعض الآخر متاح كمكتبات تابعة لجهات طرف ثالث (third-party libraries)، إضافة إلى ما تستخدمه أنت كذلك. يحتوي React Native على العديد من مكوّنات المنصّة الأكثر أهمية، وهي جاهزة للاستخدام، مثل <code>[[ReactNative/scrollview|ScrollView]]</code> و<code>[[ReactNative/textinput|TextInput]]</code>، ولكن ليس جميعها، وبالطبع، فهذا لا يشمل تلك التي قد تكون كتبتها بنفسك. لحسن الحظ، من السهل جدًا تغليف هذه المكونات الحالية لإدماجها بسلاسة مع تطبيق React Native الخاص بك.


هناك الكثير من أدوات واجهة المستخدم (UI widgets) الأصيلة الجاهزة للاستخدام في أحدث التطبيقات، بعضها جزء من المنصّة، والبعض الآخر متاح كمكتبات تابعة لجهات طرف ثالث (third-party libraries)، إضافة إلى ما تستخدمه أنت كذلك. يحتوي React Native على العديد من مكوّنات المنصّة الأكثر أهمية، وهي جاهزة للاستخدام، مثل ScrollView وTextInput، ولكن ليس جميعها، وبالطبع، فهذا لا يشمل تلك التي قد تكون كتبتها بنفسك. لحسن الحظ، من السهل جدًا تغليف هذه المكونات الحالية لإدماجها بسلاسة مع تطبيق React Native الخاص بك.
على غرار صفحة [[ReactNative/native modules ios|الوحدات الأصيلة]]، فهذا الدليل كذلك أكثر تقدمًا، ويَفترِض أنك معتاد إلى حد ما على برمجة iOS. سيوضح لك هذا الدليل كيفية إنشاء مكون واجهة مستخدم أصيل، وسيرشدك إلى كتابة مجموعة فرعيّة من مكون <code>MapView</code> الموجود في مكتبة React Native الأساسية (the core React Native library).
 
على غرار صفحة الوحدات الأصيلة، فهذا الدليل كذلك أكثر تقدمًا، ويَفترِض أنك معتاد إلى حد ما على برمجة iOS. سيوضح لك هذا الدليل كيفية إنشاء مكون واجهة مستخدم أصيل، وسيرشدك إلى كتابة مجموعة فرعيّة من مكون MapView الموجود في مكتبة React Native الأساسية (the core React Native library).


== مثال MapView لنظام iOS ==
== مثال MapView لنظام iOS ==
لنفترض أننا نريد إضافة خريطة تفاعلية إلى تطبيقنا، لنستخدم مكتبة [https://developer.apple.com/library/prerelease/mac/documentation/MapKit/Reference/MKMapView_Class/index.html MKMapView]، سنحتاج فقط إلى جعلها قابلة للاستخدام من جهة JavaScript.
لنفترض أننا نريد إضافة خريطة تفاعلية إلى تطبيقنا، لنستخدم مكتبة [https://developer.apple.com/library/prerelease/mac/documentation/MapKit/Reference/MKMapView_Class/index.html <code>MKMapView</code>]، سنحتاج فقط إلى جعلها قابلة للاستخدام من جهة JavaScript.


تُنشأ العروض الأصيلة وتُعالَج (أو تُدارُ) بواسطة أصناف فرعية من الصنف RCTViewManager. تشبه هذه الصفات الفرعية في وظيفتها مُتحكّماتِ العروض (view controllers)، لكنّها أساسًا مفردة (singletons)، أي أن الجسر يُنشئ نسخة واحدة فقط من كل منها. وتُوفّر الوصول إلى العروض الأصيلة للصنف RCTUIManager، الذي يُفوِّضها مرة أخرى لتعيين وتحديث خاصيات العروض حسب الضرورة. عادةً ما تكون أصناف RCTViewManager هي المفوَّضَةُ لتمثيل العروض (delegates for the views)، مُرسِلةً الأحداثَ إلى JavaScript مُجدّدًا عبر الجسر.
تُنشأ الواجهات أو العروض الأصيلة وتُعالَج (أو تُدارُ) بواسطة أصناف فرعية من الصنف <code>RCTViewManager</code>. تشبه هذه الصفات الفرعية في وظيفتها مُتحكّماتِ الواجهات (view controllers)، لكنّها أساسًا مفردة (singletons)، أي أن الجسر يُنشئ نسخة واحدة فقط من كل منها. وتُوفّر الوصول إلى الواجهات الأصيلة للصنف <code>RCTUIManager</code>، الذي يُفوِّضها مرة أخرى لتعيين وتحديث خاصيات الواجهات حسب الضرورة. عادةً ما تكون أصناف <code>RCTViewManager</code> هي المفوَّضَةُ لتمثيل الواجهات (delegates for the views)، مُرسِلةً الأحداثَ إلى JavaScript مُجدّدًا عبر الجسر.


عمليّة توفير الوصول إلى عرضٍ بسيطة:
عمليّة توفير الوصول إلى واجهة بسيطة:


* أنشئ صنفًا فرعيًّا من RCTViewManager لإنشاء مُعالجٍ لإدارة مُكوِّنك.
* أنشئ صنفًا فرعيًّا من <code>RCTViewManager</code> لإنشاء مُعالجٍ لإدارة مُكوِّنك.
* أضف ماكرو التعليم (marker macro) لتصدير الوحدة بالسطر ‎‎<code>RCT_EXPORT_MODULE()</code>‎‎.
* أضف ماكرو التعليم (marker macro) لتصدير الوحدة بالسطر ‎‎<code>RCT_EXPORT_MODULE()</code>‎‎.
* اكتب التابع ‎‎<code>-(UIView *)view</code>‎‎.
* اكتب التابع ‎‎<code>-(UIView *)view</code>‎‎.


<syntaxhighlight lang="javascript">
<syntaxhighlight lang="objective-c">
// RNTMapManager.m
// RNTMapManager.m
#import <MapKit/MapKit.h>
#import <MapKit/MapKit.h>
سطر 37: سطر 36:
</syntaxhighlight>
</syntaxhighlight>


'''ملاحظة:''' لا تحاول تعيين الخاصيّة ‎‎<code>frame</code>‎‎ أو ‎‎<code>backgroundColor</code>‎‎على نسخة UIView التي وفَّرْتَ الوصول إليها من خلال التابع ‎‎<code>-view</code>‎‎. سيكتب React Native فوق القيم التي تُعيِّنها بواسطة صنفك المُخصَّص للتوافق مع خاصيّات JavaScript المسؤولة عن تخطيط (layout) مكوِّنِك. إذا كنت بحاجة إلى دقة التحكم هذه، فقد يكون من الأفضل تغليف نسخةِ الصنف UIView الذي تريد تصميمه في صنف UIView آخر وإعادة الصّنف UIView المُغلَّف بدلاً من ذلك. انظر  [https://github.com/facebook/react-native/issues/2948 هذه الصفحة] للاستزادة.
'''ملاحظة:''' لا تحاول تعيين الخاصيّة ‎‎<code>frame</code>‎‎ أو ‎‎<code>backgroundColor</code>‎‎على نسخة <code>UIView</code> التي وفَّرْتَ الوصول إليها من خلال التابع ‎‎<code>-view</code>‎‎. سيكتب React Native فوق القيم التي تُعيِّنها بواسطة صنفك المُخصَّص للتوافق مع خاصيّات JavaScript المسؤولة عن تخطيط (layout) مكوِّنِك. إذا كنت بحاجة إلى دقة التحكم هذه، فقد يكون من الأفضل تغليف نسخةِ الصنف <code>UIView</code> الذي تريد تصميمه في صنف <code>UIView</code> آخر وإعادة الصّنف <code>UIView</code> المُغلَّف بدلاً من ذلك. انظر  [https://github.com/facebook/react-native/issues/2948 هذه الصفحة] للاستزادة.


في المثال أعلاه، قمنا باستعمال البادئة RNT في اسم الصنف الذي أنشأناه. تُستخدَم البادئات لتجنب تضارب الأسماء (name collisions) مع الأطر الأخرى. تستخدم أطر عمل Apple بادئات مؤلفة من حرفين، ويستخدم React Native المقطع RCT كبادئة. نوصي باستخدام بادئة من ثلاثة أحرف بخلاف RCT في أصنافك الخاصّة لتفادي تضارب الأسماء.
في المثال أعلاه، قمنا باستعمال البادئة <code>RNT</code> في اسم الصنف الذي أنشأناه. تُستخدَم البادئات لتجنب تضارب الأسماء (name collisions) مع الأطر الأخرى. تستخدم أطر عمل Apple بادئات مؤلفة من حرفين، ويستخدم React Native المقطع <code>RCT</code> كبادئة. نوصي باستخدام بادئة من ثلاثة أحرف بخلاف <code>RCT</code> في أصنافك الخاصّة لتفادي تضارب الأسماء.


بعد ذلك، ستحتاج فقط إلى القليل من شيفرة JavaScript لجعله مكونَ React جاهز للاستخدام:
بعد ذلك، ستحتاج فقط إلى القليل من شيفرة JavaScript لجعله مكونَ React جاهز للاستخدام:
سطر 60: سطر 59:
}
}
</syntaxhighlight>
</syntaxhighlight>
تأكد من استخدام RNTMap هنا. نريد طَلَب (أو استيراد: require) المُعالج هنا، والذي سيُوفِّر الوصول إلى عرض المُعالج لاستخدامه في JavaScript.
تأكد من استخدام <code>RNTMap</code> هنا. نريد طَلَب (أو استيراد require) المُعالج (manager) هنا، والذي سيُوفِّر الوصول إلى واجهة المُعالج لاستخدامه في JavaScript.


'''ملاحظة''': عند التصيير (rendering)، لا تنس تمديد العرض كي لا تظهر شاشةٌ فارغة.
'''ملاحظة''': عند التصيير (rendering)، لا تنس تمديد الواجهة كي لا تظهر شاشةٌ فارغة.
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="javascript">
   render() {
   render() {
سطر 69: سطر 68:
</syntaxhighlight>
</syntaxhighlight>


أصبح هذا المكون الآن عرضَ خريطة أصيلٍ كاملِ الوظائف قابلٍ للاستخدام في JavaScript، مع دعم كامل المميّزات كالقَرْصِ للتكبير وغيرها من الإيماءات الأصيلة. لكن للأسف لا يمكننا التحكم به من JavaScript حتى الآن.
أصبح هذا المكون الآن واجهة خريطة أصيلٍ كاملِ الوظائف قابلٍ للاستخدام في JavaScript، مع دعم كامل المميّزات كالقَرْصِ للتكبير وغيرها من الإيماءات الأصيلة. لكن للأسف لا يمكننا التحكم به من JavaScript حتى الآن.


==الخاصيّات==
==الخاصيات==
أول ما يمكننا القيام به لجعل هذا المكوّن قابلًا للاستخدام أكثر هو نقل بعض الخاصيّات الأصيلة عبر الجسر. لنقل أننا نريد التمكّن من تعطيل التكبير وتحديد المنطقة المرئية. يُمكن تعطيل التكبير بقيمة منطقيّة بسيطة، لذا سنضيف هذا السطر:
أول ما يمكننا القيام به لجعل هذا المكوّن قابلًا للاستخدام أكثر هو نقل بعض الخاصيّات الأصيلة عبر الجسر. لنقل أننا نريد التمكّن من تعطيل التكبير وتحديد المنطقة المرئية. يُمكن تعطيل التكبير بقيمة منطقيّة بسيطة، لذا سنضيف هذا السطر:
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="objective-c">
// RNTMapManager.m
// RNTMapManager.m
RCT_EXPORT_VIEW_PROPERTY(zoomEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(zoomEnabled, BOOL)
</syntaxhighlight>
</syntaxhighlight>


لاحظ أننا نحدّد النّوع بوضوح على أنّه قيمة منطقيّة (‎‎<code>BOOL</code>‎‎) لأنّ React Native يستخدم RCTConvert خلف الكواليس لتحويل العديد من أنواع البيانات المختلفة عند نقل البيانات عبر الجسر، وسوف تعرض القيم السيئة رسائل خطأِ صندوقٍ أحمر "RedBox"لإعلامك بوجود مشكلة حالًا. وإن سار كل شيء على ما يُرام كهذه الحالة، فسيعتني هذا الماكرو بالأمر.
لاحظ أننا نحدّد النّوع بوضوح على أنّه قيمة منطقيّة ‎‎<code>BOOL</code>‎‎) لأنّ React Native يستخدم <code>RCTConvert</code> خلف الكواليس لتحويل العديد من أنواع البيانات المختلفة عند نقل البيانات عبر الجسر، وسوف تعرض القيم السيئة رسائل خطأِ صندوقٍ أحمر "RedBox" لإعلامك بوجود مشكلة حالًا. وإن سار كل شيء على ما يُرام كهذه الحالة، فسيعتني هذا الماكرو بالأمر.


لتعطيل التكبير الآن، عيِّن الخاصيّة في JavaScript:
لتعطيل التكبير الآن، عيِّن الخاصيّة في JavaScript:
سطر 85: سطر 84:
<MapView zoomEnabled={false} style={{flex: 1}} />
<MapView zoomEnabled={false} style={{flex: 1}} />
</syntaxhighlight>
</syntaxhighlight>
لتوثيق الخاصيات (والقيم التي تقبلها) لمكوّن MapView الخاص بنا، سنضيف مكونًا مُغلِّفًا (wrapper component) وسنوثّق الواجهة باستعمال خاصيّة PropTypes في React:
لتوثيق الخاصيات (والقيم التي تقبلها) لمكوّن <code>MapView</code> الخاص بنا، سنضيف مكونًا مُغلِّفًا (wrapper component) وسنوثّق الواجهة باستعمال خاصيّة <code>PropTypes</code> في React:
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="javascript">
// MapView.js
// MapView.js
import PropTypes from 'prop-types';
import PropTypes from 'prop-types';
import React from 'react';
import React from 'react';
import {requireNativeComponent} from 'react-native';
import { requireNativeComponent } from 'react-native';


class MapView extends React.Component {
class MapView extends React.Component {
سطر 102: سطر 101:
   * A Boolean value that determines whether the user may use pinch
   * A Boolean value that determines whether the user may use pinch
   * gestures to zoom in and out of the map.
   * gestures to zoom in and out of the map.
  * قيمة منطقية للسّماح للمستخدم بالقَرْصِ للتكبير والتصغير أو منعه من ذلك
   */
   */
   zoomEnabled: PropTypes.bool,
   zoomEnabled: PropTypes.bool
};
};


سطر 111: سطر 109:
module.exports = MapView;
module.exports = MapView;
</syntaxhighlight>
</syntaxhighlight>
لدينا الآن مكوّن مُغلَّف وموثَّق سهلُ الاستخدام. لاحظ أننا غيَّرنا معامل الدالة ‎‎<code>requireNativeComponent</code>‎‎ الثاني من ‎‎<code>null</code>‎‎ إلى المكوّن MapView المُغلِّف الجديد. يسمح هذا للبنية التحتيّة بالتحقق من أنّ أنواع propTypes تتطابق مع الخاصيات الأصيلة لتقليل فرص عدم التطابق بين شيفرة Objective-C وشيفرة JavaScript.
لدينا الآن مكوّن مُغلَّف وموثَّق سهلُ الاستخدام. لاحظ أننا غيَّرنا معامل الدالة ‎‎<code>requireNativeComponent</code>‎‎ الثاني من ‎‎<code>null</code>‎‎ إلى المكوّن <code>MapView</code> المُغلِّف الجديد. يسمح هذا للبنية التحتيّة بالتحقق من أنّ أنواع propTypes تتطابق مع الخاصيات الأصيلة لتقليل فرص عدم التطابق بين شيفرة Objective-C وشيفرة JavaScript.


بعد ذلك، لِنُضِف الخاصيّة ‎‎<code>region</code>‎‎ الأعقَد. نبدأ بإضافة الشيفرة الأصيلة:
بعد ذلك، لِنُضِف الخاصيّة ‎‎<code>region</code>‎‎ الأعقَد. نبدأ بإضافة الشيفرة الأصيلة:
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="objective-c">
// RNTMapManager.m
// RNTMapManager.m
RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, MKMapView)
RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, MKMapView)
سطر 121: سطر 119:
}
}
</syntaxhighlight>
</syntaxhighlight>
هذا أعقد من حالة BOOL البسيطة سابقًا. لدينا الآن نوعٌ باسم MKCoordinateRegion يحتاج إلى دالّة تحويل (conversion function)، ولدينا شيفرة مخصصّة لتحريك العرض عند ضبط المنطقة (region) من JavaScript. داخل جسم الدالّة التي نُقدِّمها، يشير json إلى القيمة الأولية (raw value) التي مُرِّرَت من JavaScript. هناك أيضًا متغيّرُ ‎‎<code>view</code>‎‎ يتيح لنا الوصول إلى نسخة مُعالج (أو مُدير) العرض، ونستخدم ‎‎<code>defaultView</code>‎‎ لإعادة تعيين الخاصية إلى القيمة الافتراضية إذا أرسلت شيفرة JavaScript القيمة ‎‎<code>null</code>‎‎.
هذا أعقد من حالة <code>BOOL</code> البسيطة سابقًا. لدينا الآن نوعٌ باسم <code>MKCoordinateRegion</code> يحتاج إلى دالّة تحويل (conversion function)، ولدينا شيفرة مخصصّة لتحريك الواجهة عند ضبط المنطقة (region) من JavaScript. داخل جسم الدالّة التي نُقدِّمها، يشير <code>json</code> إلى القيمة الأولية (raw value) التي مُرِّرَت من JavaScript. هناك أيضًا متغيّرُ ‎‎<code>view</code>‎‎ يتيح لنا الوصول إلى نسخة مُعالج (أو مُدير) الواجهة، ونستخدم ‎‎<code>defaultView</code>‎‎ لإعادة تعيين الخاصية إلى القيمة الافتراضية إذا أرسلت شيفرة JavaScript القيمة ‎‎<code>null</code>‎‎.


يمكنك كتابة أي دالّة تحويلٍ تريدها لعَرضِك، إليك إجراء MKCoordinateRegion عبر فئةٍ (category) على RCTConvert. يستخدم هذا فئةً موجودة بالفعل من ReactNative باسم ‎‎<code>RCTConvert+CoreLocation</code>‎‎:
يمكنك كتابة أي دالّة تحويلٍ تريدها لعَرضِك، إليك إجراء <code>MKCoordinateRegion</code> عبر فئةٍ (category) على <code>RCTConvert</code>. يستخدم هذا فئةً موجودة بالفعل من ReactNative باسم ‎‎<code>RCTConvert+CoreLocation</code>‎‎:
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="objective-c">
// RNTMapManager.m
// RNTMapManager.m


#import "RCTConvert+Mapkit.m"
#import "RCTConvert+Mapkit.h"


// RCTConvert+Mapkit.h
// RCTConvert+Mapkit.h
سطر 167: سطر 165:
دوال التحويل هذه مُصمَّمةٌ لمعالجة بيانات JSON بأمان، والتي قد ترميها JavaScript عبر عرض رسائل أخطاء الصندوق الأحمر وعند إرجاع قيم التهيئة القياسية (standard initialization values) عند فقدان المفاتيح أو أخطاء تطوير الأخرى.
دوال التحويل هذه مُصمَّمةٌ لمعالجة بيانات JSON بأمان، والتي قد ترميها JavaScript عبر عرض رسائل أخطاء الصندوق الأحمر وعند إرجاع قيم التهيئة القياسية (standard initialization values) عند فقدان المفاتيح أو أخطاء تطوير الأخرى.


لإكمال دعم الخاصيّة ‎‎<code>region</code>‎‎، نحتاج إلى توثيقها باستخدام propTypes (أو سنحصل على رسالة خطأ أن الخاصية الأصيلة غير مُوثَّقة)، بعدها يُمكننا ضبطها مثل أي خاصيّة أخرى:
لإكمال دعم الخاصيّة ‎‎<code>region</code>‎‎، نحتاج إلى توثيقها باستخدام <code>propTypes</code> (أو سنحصل على رسالة خطأ أن الخاصية الأصيلة غير مُوثَّقة)، بعدها يُمكننا ضبطها مثل أي خاصيّة أخرى:
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="javascript">
// MapView.js
// MapView.js
سطر 183: سطر 181:
   * The region is defined by the center coordinates and the span of
   * The region is defined by the center coordinates and the span of
   * coordinates to display.
   * coordinates to display.
 
   *المنطقة التي ستُعرَض في الخريطة
   * المنطقة التي ستُعرَض في الخريطة
   *
   *
   * تُحدَّد المنطقة عبر إحداثيات المركز ومجالها
   * تُحدَّد المنطقة عبر إحداثيات المركز ومجالها
   */
   */
   region: PropTypes.shape({
   region: PropTypes.shape({
     /**
     /**  
    * إحداثيات مركز الخريطة
      * إحداثيات مركز الخريطة
    * Coordinates for the center of the map.
     */
     */
     latitude: PropTypes.number.isRequired,
     latitude: PropTypes.number.isRequired,
سطر 197: سطر 195:
     /**
     /**
     * Distance between the minimum and the maximum latitude/longitude
     * Distance between the minimum and the maximum latitude/longitude
    * to be displayed.
     * المسافة بين الحد الأدنى والأقصى لكل من خطي العرض والطول
     * المسافة بين الحد الأدنى والأقصى لكل من خطي العرض والطول
     */
     */
سطر 224: سطر 223:
يمكنك هنا أن ترى أن شكل المنطقة واضح في توثيق JavaScript، مثاليًّا يمكننا توليد جزء من هذه الشيفرة تلقائيًّا، لكن هذه الميّزة غير متوفّرة حاليًّا.
يمكنك هنا أن ترى أن شكل المنطقة واضح في توثيق JavaScript، مثاليًّا يمكننا توليد جزء من هذه الشيفرة تلقائيًّا، لكن هذه الميّزة غير متوفّرة حاليًّا.


أحيانًا، سيكون للمكوّن الأصيل بعض الخاصيات الخاصة التي لا تريد أن تكون جزءًا من واجهة برمجة التطبيقات لمكون React ذو العلاقة. مثلًا، يحتوي Switch على معالج onChange مخصَّص للحدث الأصيل الأوليّ (raw native event)، ويُوفّر الوصولَ إلى خاصيّة المعالجة onValueChange التي تُستدعى بالقيمة المنطقية فقط بدلاً من الحدث الأوليّ. ولأنّك لا تريد أن تكون هذه الخاصيات (الأصيلة حصرًا) جزءًا من واجهة برمجة التطبيقات، فأنت لا تريد وضعها في أنواع propTypes، ولكن إذا لم تفعل فستحصل على خطأ. الحل هو ببساطة إضافتها إلى الخيار ‎‎<code>nativeOnly</code>‎‎، مثلًا:
أحيانًا، سيكون للمكوّن الأصيل بعض الخاصيات الخاصة التي لا تريد أن تكون جزءًا من واجهة برمجة التطبيقات لمكون React ذو العلاقة. مثلًا، يحتوي <code>Switch</code> على معالج <code>onChange</code> مخصَّص للحدث الأصيل الأوليّ (raw native event)، ويُوفّر الوصولَ إلى خاصيّة المعالجة <code>onValueChange</code> التي تُستدعى بالقيمة المنطقية فقط بدلاً من الحدث الأوليّ. ولأنّك لا تريد أن تكون هذه الخاصيات (الأصيلة حصرًا) جزءًا من واجهة برمجة التطبيقات، فأنت لا تريد وضعها في أنواع <code>propTypes</code>، ولكن إذا لم تفعل فستحصل على خطأ. الحل هو ببساطة إضافتها إلى الخيار ‎‎<code>nativeOnly</code>‎‎، مثلًا:
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="javascript">
var RCTSwitch = requireNativeComponent('RCTSwitch', Switch, {
var RCTSwitch = requireNativeComponent('RCTSwitch', Switch, {
   nativeOnly: {onChange: true},
   nativeOnly: { onChange: true }
});
});
</syntaxhighlight>
</syntaxhighlight>
سطر 234: سطر 233:
لدينا الآن مكوّن خريطة أصيل يمكننا التحكم فيه بسهولة من JavaScript، لكن كيف نتعامل مع أحداث المستخدم (User events)، مثل القرص للتكبير أو للتصغير أو المسح (panning) لتغيير المنطقة المرئية؟
لدينا الآن مكوّن خريطة أصيل يمكننا التحكم فيه بسهولة من JavaScript، لكن كيف نتعامل مع أحداث المستخدم (User events)، مثل القرص للتكبير أو للتصغير أو المسح (panning) لتغيير المنطقة المرئية؟


إلى الآن، قمنا فقط بإرجاع نسخة MKMapView من تابع ‎‎<code>-(UIView *)view</code>‎‎ الخاص بمعالج الإدارة (manager). لا يمكننا إضافة خاصيات جديدة إلى MKMapView لذلك علينا إنشاء صنف فرعيّ جديد من MKMapView، والذي سنستخدمه لعرضنا. يُمكننا بعد ذلك إضافة دالة ردّ نداءٍ (callback) باسم ‎‎<code>onRegionChange</code>‎‎ إلى هذا الصنف الفرعيّ:
إلى الآن، قمنا فقط بإرجاع نسخة <code>MKMapView</code> من تابع ‎‎<code>-(UIView *)view</code>‎‎ الخاص بمعالج الإدارة (manager). لا يمكننا إضافة خاصيات جديدة إلى <code>MKMapView</code> لذلك علينا إنشاء صنف فرعيّ جديد من <code>MKMapView</code>، والذي سنستخدمه لواجهتنا. يُمكننا بعد ذلك إضافة دالة ردّ نداءٍ (callback) باسم ‎‎<code>onRegionChange</code>‎‎ إلى هذا الصنف الفرعيّ:
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="objective-c">
// RNTMapView.h
// RNTMapView.h


سطر 257: سطر 256:
</syntaxhighlight>
</syntaxhighlight>


لاحظ أن جميع أحداث RCTBubblingEventBlock يجب أن تكون مسبوقة بالمقطع on. بعد ذلك، صرِّح (declare) عن خاصية معالجة أحداث (event handler property) على RNTMapManager، واجعلها مفوَّضةً لجميع العروض التي توفّر الوصول إليها، وأعِد توجيه الأحداث إلى JavaScript عن طريق استدعاء كتلة معالج الأحداث (event handler block) من العرض الأصيل.
لاحظ أن جميع أحداث <code>RCTBubblingEventBlock</code> يجب أن تكون مسبوقة بالمقطع <code>on</code>. بعد ذلك، صرِّح (declare) عن خاصية معالجة أحداث (event handler property) على <code>RNTMapManager</code>، واجعلها مفوَّضةً لجميع الواجهات التي توفّر الوصول إليها، وأعِد توجيه الأحداث إلى JavaScript عن طريق استدعاء كتلة معالج الأحداث (event handler block) من الواجهة الأصيلة.
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="objective-c">
// RNTMapManager.m
// RNTMapManager.m


سطر 265: سطر 264:


#import "RNTMapView.h"
#import "RNTMapView.h"
#import "RCTConvert+Mapkit.m"
#import "RCTConvert+Mapkit.h"


@interface RNTMapManager : RCTViewManager <MKMapViewDelegate>
@interface RNTMapManager : RCTViewManager <MKMapViewDelegate>
سطر 310: سطر 309:
</syntaxhighlight>
</syntaxhighlight>


في التابع المفوَّض ‎‎<code>-mapView:regionDidChangeAnimated:</code>‎‎ تُستدعى كتلة معالج الأحداث على العرض المتوافق مع بيانات المنطقة. ينتج عن استدعاء كتلة معالج الأحداث onRegionChange استدعاءُ نفس خاصيّة رد النداء في JavaScript. تُستدعى دالّة رد النداء هذه مع الحدث الأولي، والذي نقوم بمعالجته عادةً في المكوِّن المُغلِّف لتبسيط واجهة برمجة التطبيقات:
تُستدعى في التابع المفوَّض ‎‎<code>-mapView:regionDidChangeAnimated:</code>‎‎ كتلة معالج الأحداث على الواجهة المتوافقة مع بيانات المنطقة، وينتج عن استدعاء كتلة معالج الأحداث <code>onRegionChange</code> استدعاءُ نفس خاصيّة رد النداء في JavaScript. تُستدعى دالّة رد النداء هذه مع الحدث الأولي، والذي نقوم بمعالجته عادةً في المكوِّن المُغلِّف لتبسيط واجهة برمجة التطبيقات:
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="javascript">
// MapView.js
// MapView.js


سطر 334: سطر 334:
MapView.propTypes = {
MapView.propTypes = {
   /**
   /**
   * دالة رد نداء تُستدعى باستمرار عندما يسحب المستخدم الخريطة
   * Callback that is called continuously when the user is dragging the map.
* دالة رد نداء تُستدعى باستمرار عندما يسحب المستخدم الخريطة
   */
   */
   onRegionChange: PropTypes.func,
   onRegionChange: PropTypes.func,
سطر 344: سطر 345:
class MyApp extends React.Component {
class MyApp extends React.Component {
   onRegionChange(event) {
   onRegionChange(event) {
     // تعامل مع القيمة
     // Do stuff with event.region.latitude, etc.
  // تعامل مع القيمة
     // event.region.latitude
     // event.region.latitude
     // وغيرها من القيم
     // وغيرها من القيم
سطر 366: سطر 368:
}
}
</syntaxhighlight>
</syntaxhighlight>
== التعامل مع عدة واجهات أصيلة ==
يمكن أن يكون لواجهة React Native عدة أبناء في شجرة الواجهات، مثلًا:<syntaxhighlight lang="javascript">
<View>
  <MyNativeView />
  <MyNativeView />
  <Button />
</View>
</syntaxhighlight>في هذا المثال يغلف الصنف <code>MyNativeView</code> المكون <code>NativeComponent</code> ويقدم الدوال التي ستستدعى على منصة iOS. يعرَّف <code>MyNativeView</code> في  <code>MyNativeView.ios.js</code> ويحوي دوال الوكيل الموجودة في <code>NativeComponent.</code>
عندما يتفاعل المستخدم مع المكون بالنقر على الزر يتغير لون الخلفية <code>backgroundColor</code> الخاص بالواجهة <code>MyNativeView</code>، في هذه الحالة لن يعلم <code>UIManager</code> أي مكون <code>MyNativeView</code> يجب أن يعالج وأي منها سيغير <code>backgroundColor</code>، ستحل هذه المشكلة بالطريقة التالية:<syntaxhighlight lang="javascript">
<View>
  <MyNativeView ref={this.myNativeReference} />
  <MyNativeView ref={this.myNativeReference2} />
  <Button
    onPress={() => {
      this.myNativeReference.callNativeMethod();
    }}
  />
</View>
</syntaxhighlight>الأى يحوي المكون مرجعًا إلى <code>MyNativeView</code> محدّد مما يسمح لنا باستخدام نسخة محددة من <code>MyNativeView</code>. الآن يستطيع الزر أن يعرف لأي <code>MyNativeView</code> يجب أن يغير <code>backgroundColor</code> .في هذا المثال لنفرض أن <code>callNativeMethod</code> هي التي تغير <code>backgroundColor</code>.
سيحوي <code>MyNativeView.ios.js</code> شيفرة كما يلي:<syntaxhighlight lang="javascript">
class MyNativeView extends React.Component {
  callNativeMethod = () => {
    UIManager.dispatchViewManagerCommand(
      ReactNative.findNodeHandle(this),
      UIManager.getViewManagerConfig('RNCMyNativeView').Commands
        .callNativeMethod,
      []
    );
  };
  render() {
    return <NativeComponent ref={NATIVE_COMPONENT_REF} />;
  }
}
</syntaxhighlight><code>callNativeMethod</code> هي دالتنا المخصصة في iOS والتي تغير <code>backgroundColor</code> كمثال، والتي تعرض من خلال <code>MyNativeView</code>. تستخدم هذه الدالة <code>UIManager.dispatchViewManagerCommand</code> والتي تحتاج ثلاثة معاملات:
* <code>(nonnull NSNumber \*)reactTag</code> : الرقم المعرف لواجهة react.
* <code>commandID:(NSInteger)commandID</code> : الرقم المعرف للدالة الأصيلةالتي يجب استدعاؤها.
* <code>commandArgs:(NSArray<id> \*)commandArgs</code> : معاملات الدالة الأصيلة التي يمكن أن نمررها من JS  إلى native.
إليك الملف <code>RNCMyNativeViewManager.m</code>:<syntaxhighlight lang="javascript">
#import <React/RCTViewManager.h>
#import <React/RCTUIManager.h>
#import <React/RCTLog.h>
RCT_EXPORT_METHOD(callNativeMethod:(nonnull NSNumber*) reactTag) {
    [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *,UIView *> *viewRegistry) {
        NativeView *view = viewRegistry[reactTag];
        if (!view || ![view isKindOfClass:[NativeView class]]) {
            RCTLogError(@"Cannot find NativeView with tag #%@", reactTag);
            return;
        }
        [view callNativeMethod];
    }];
}
</syntaxhighlight>تعرَّف <code>callNativeMethod</code> في الملف <code>RNCMyNativeViewManager.m،</code> وتحوي معاملًا واحدًا فقط هو <code>(nonnull NSNumber*) reactTag</code>. ستجد هذه الدالة المصدرة علاضًا معينًا يستخدم <code>addUIBlock</code> ويحوي المعامل <code>viewRegistry</code> ويعيد المكون اعتمادًا على <code>reactTag</code> مما يسمح له باستدعاء الدالة على المكون الصحيح.
==الأنماط (Styles)==
==الأنماط (Styles)==
لأنّ جميع عروض React الأصيلة الخاصة بنا هي أصناف فرعية من الصنف UIView، فإنّ معظم سمات الأنماط (style attributes) ستعمل كالمتوقّع تلقائيًّا. لكن ستحتاج بعض المكونات إلى نمط افتراضي، مثلًا، UIDatePicker يحتاج إلى حجم ثابت. هذا النمط الافتراضي مهم لتعمل خوارزمية التخطيط كما يجب، لكننا نريد أيضًا التمكّن من الكتابة فوق النمط الافتراضي وتغييره عند استخدام المكوّن. يقوم المكوّن DatePickerIOS بهذا عن طريق تغليف المكون الأصيل في عرض إضافي يتميّز بتنسيق مرن، وباستخدام نمط ثابت (الذي يولَّد بثوابت تُمرَّر من الجزء الأصيل) على المكون الأصيل الداخلي:
لأنّ جميع واجهات [[React]] الأصيلة (native react views) الخاصة بنا هي أصناف فرعية من الصنف <code>UIView</code>، فإنّ معظم سمات الأنماط (style attributes) ستعمل كالمتوقّع تلقائيًّا. لكن ستحتاج بعض المكونات إلى نمط افتراضي، مثلًا، <code>UIDatePicker</code> يحتاج إلى حجم ثابت. هذا النمط الافتراضي مهم لتعمل خوارزمية التخطيط كما يجب، لكننا نريد أيضًا التمكّن من الكتابة فوق النمط الافتراضي وتغييره عند استخدام المكوّن. يقوم المكوّن <code>DatePickerIOS</code> بهذا عن طريق تغليف المكون الأصيل في واجهة إضافية تتميّز بتنسيق مرن، وباستخدام نمط ثابت (الذي يولَّد بثوابت تُمرَّر من الجزء الأصيل) على المكون الأصيل الداخلي:
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="javascript">
// DatePickerIOS.ios.js
// DatePickerIOS.ios.js
سطر 393: سطر 456:
   },
   },
});
});
</syntaxhighlight>
</syntaxhighlight>
تُصدَّر ثوابت RCTDatePickerIOSConsts من الجزء الأصيل عبر الحصول على الإطار (frame) الفعلي للمكون الأصيل كما يلي:
تُصدَّر ثوابت <code>RCTDatePickerIOSConsts</code> من الجزء الأصيل عبر الحصول على الإطار (frame) الفعلي للمكون الأصيل كما يلي:
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="objective-c">
// RCTDatePickerManager.m
// RCTDatePickerManager.m


سطر 414: سطر 478:
}
}
</syntaxhighlight>
</syntaxhighlight>
غطَّى هذا الدليل العديد من جوانب تجسير المكونات الأصيلة المخصصة، ولكن هناك الكثير مما قد تحتاج إلى أخذه على عين الاعتبار، مثل الخطافات المخصصة (custom hooks) لإدراج العروض الفرعية وتخطيطها. إذا أردت التعمق أكثر، انظر الشيفرة المصدريّة لبعض من [https://github.com/facebook/react-native/blob/master/React/Views مكوّنات React Native].
غطَّى هذا الدليل العديد من جوانب تجسير المكونات الأصيلة المخصصة، ولكن هناك الكثير مما قد تحتاج إلى أخذه على عين الاعتبار، مثل الخطافات المخصصة (custom hooks) لإدراج الواجهات الفرعية وتخطيطها. إذا أردت التعمق أكثر، انظر [https://github.com/facebook/react-native/blob/master/React/Views الشيفرة المصدريّة] لبعض من مكوّنات React Native.


== مصادر ==
== مصادر ==
* [https://facebook.github.io/react-native/docs/native-components-ios صفحة Native UI Components في توثيق React Native الرسمي.]
* [https://facebook.github.io/react-native/docs/native-components-ios صفحة Native UI Components في توثيق React Native الرسمي.]
[[تصنيف:ReactNative]]
[[تصنيف:ReactNative]]
[[تصنيف:React Native Docs]]

المراجعة الحالية بتاريخ 13:50، 9 أكتوبر 2021

هناك الكثير من أدوات واجهة المستخدم (UI widgets) الأصيلة الجاهزة للاستخدام في أحدث التطبيقات، بعضها جزء من المنصّة، والبعض الآخر متاح كمكتبات تابعة لجهات طرف ثالث (third-party libraries)، إضافة إلى ما تستخدمه أنت كذلك. يحتوي React Native على العديد من مكوّنات المنصّة الأكثر أهمية، وهي جاهزة للاستخدام، مثل ScrollView وTextInput، ولكن ليس جميعها، وبالطبع، فهذا لا يشمل تلك التي قد تكون كتبتها بنفسك. لحسن الحظ، من السهل جدًا تغليف هذه المكونات الحالية لإدماجها بسلاسة مع تطبيق React Native الخاص بك.

على غرار صفحة الوحدات الأصيلة، فهذا الدليل كذلك أكثر تقدمًا، ويَفترِض أنك معتاد إلى حد ما على برمجة iOS. سيوضح لك هذا الدليل كيفية إنشاء مكون واجهة مستخدم أصيل، وسيرشدك إلى كتابة مجموعة فرعيّة من مكون MapView الموجود في مكتبة React Native الأساسية (the core React Native library).

مثال MapView لنظام iOS

لنفترض أننا نريد إضافة خريطة تفاعلية إلى تطبيقنا، لنستخدم مكتبة MKMapView، سنحتاج فقط إلى جعلها قابلة للاستخدام من جهة JavaScript.

تُنشأ الواجهات أو العروض الأصيلة وتُعالَج (أو تُدارُ) بواسطة أصناف فرعية من الصنف RCTViewManager. تشبه هذه الصفات الفرعية في وظيفتها مُتحكّماتِ الواجهات (view controllers)، لكنّها أساسًا مفردة (singletons)، أي أن الجسر يُنشئ نسخة واحدة فقط من كل منها. وتُوفّر الوصول إلى الواجهات الأصيلة للصنف RCTUIManager، الذي يُفوِّضها مرة أخرى لتعيين وتحديث خاصيات الواجهات حسب الضرورة. عادةً ما تكون أصناف RCTViewManager هي المفوَّضَةُ لتمثيل الواجهات (delegates for the views)، مُرسِلةً الأحداثَ إلى JavaScript مُجدّدًا عبر الجسر.

عمليّة توفير الوصول إلى واجهة بسيطة:

  • أنشئ صنفًا فرعيًّا من RCTViewManager لإنشاء مُعالجٍ لإدارة مُكوِّنك.
  • أضف ماكرو التعليم (marker macro) لتصدير الوحدة بالسطر ‎‎RCT_EXPORT_MODULE()‎‎.
  • اكتب التابع ‎‎-(UIView *)view‎‎.
// RNTMapManager.m
#import <MapKit/MapKit.h>

#import <React/RCTViewManager.h>

@interface RNTMapManager : RCTViewManager
@end

@implementation RNTMapManager

RCT_EXPORT_MODULE(RNTMap)

- (UIView *)view
{
  return [[MKMapView alloc] init];
}

@end

ملاحظة: لا تحاول تعيين الخاصيّة ‎‎frame‎‎ أو ‎‎backgroundColor‎‎على نسخة UIView التي وفَّرْتَ الوصول إليها من خلال التابع ‎‎-view‎‎. سيكتب React Native فوق القيم التي تُعيِّنها بواسطة صنفك المُخصَّص للتوافق مع خاصيّات JavaScript المسؤولة عن تخطيط (layout) مكوِّنِك. إذا كنت بحاجة إلى دقة التحكم هذه، فقد يكون من الأفضل تغليف نسخةِ الصنف UIView الذي تريد تصميمه في صنف UIView آخر وإعادة الصّنف UIView المُغلَّف بدلاً من ذلك. انظر هذه الصفحة للاستزادة.

في المثال أعلاه، قمنا باستعمال البادئة RNT في اسم الصنف الذي أنشأناه. تُستخدَم البادئات لتجنب تضارب الأسماء (name collisions) مع الأطر الأخرى. تستخدم أطر عمل Apple بادئات مؤلفة من حرفين، ويستخدم React Native المقطع RCT كبادئة. نوصي باستخدام بادئة من ثلاثة أحرف بخلاف RCT في أصنافك الخاصّة لتفادي تضارب الأسماء.

بعد ذلك، ستحتاج فقط إلى القليل من شيفرة JavaScript لجعله مكونَ React جاهز للاستخدام:

// MapView.js

import { requireNativeComponent } from 'react-native';

// requireNativeComponent automatically resolves 'RNTMap' to 'RNTMapManager'
module.exports = requireNativeComponent('RNTMap');

// MyApp.js

import MapView from './MapView.js';

...

render() {
  return <MapView style={{ flex: 1 }} />;
}

تأكد من استخدام RNTMap هنا. نريد طَلَب (أو استيراد require) المُعالج (manager) هنا، والذي سيُوفِّر الوصول إلى واجهة المُعالج لاستخدامه في JavaScript.

ملاحظة: عند التصيير (rendering)، لا تنس تمديد الواجهة كي لا تظهر شاشةٌ فارغة.

  render() {
    return <MapView style={{flex: 1}} />;
  }

أصبح هذا المكون الآن واجهة خريطة أصيلٍ كاملِ الوظائف قابلٍ للاستخدام في JavaScript، مع دعم كامل المميّزات كالقَرْصِ للتكبير وغيرها من الإيماءات الأصيلة. لكن للأسف لا يمكننا التحكم به من JavaScript حتى الآن.

الخاصيات

أول ما يمكننا القيام به لجعل هذا المكوّن قابلًا للاستخدام أكثر هو نقل بعض الخاصيّات الأصيلة عبر الجسر. لنقل أننا نريد التمكّن من تعطيل التكبير وتحديد المنطقة المرئية. يُمكن تعطيل التكبير بقيمة منطقيّة بسيطة، لذا سنضيف هذا السطر:

// RNTMapManager.m
RCT_EXPORT_VIEW_PROPERTY(zoomEnabled, BOOL)

لاحظ أننا نحدّد النّوع بوضوح على أنّه قيمة منطقيّة ‎‎BOOL‎‎) لأنّ React Native يستخدم RCTConvert خلف الكواليس لتحويل العديد من أنواع البيانات المختلفة عند نقل البيانات عبر الجسر، وسوف تعرض القيم السيئة رسائل خطأِ صندوقٍ أحمر "RedBox" لإعلامك بوجود مشكلة حالًا. وإن سار كل شيء على ما يُرام كهذه الحالة، فسيعتني هذا الماكرو بالأمر.

لتعطيل التكبير الآن، عيِّن الخاصيّة في JavaScript:

// MyApp.js
<MapView zoomEnabled={false} style={{flex: 1}} />

لتوثيق الخاصيات (والقيم التي تقبلها) لمكوّن MapView الخاص بنا، سنضيف مكونًا مُغلِّفًا (wrapper component) وسنوثّق الواجهة باستعمال خاصيّة PropTypes في React:

// MapView.js
import PropTypes from 'prop-types';
import React from 'react';
import { requireNativeComponent } from 'react-native';

class MapView extends React.Component {
  render() {
    return <RNTMap {...this.props} />;
  }
}

MapView.propTypes = {
  /**
   * A Boolean value that determines whether the user may use pinch
   * gestures to zoom in and out of the map.
   */
  zoomEnabled: PropTypes.bool
};

var RNTMap = requireNativeComponent('RNTMap', MapView);

module.exports = MapView;

لدينا الآن مكوّن مُغلَّف وموثَّق سهلُ الاستخدام. لاحظ أننا غيَّرنا معامل الدالة ‎‎requireNativeComponent‎‎ الثاني من ‎‎null‎‎ إلى المكوّن MapView المُغلِّف الجديد. يسمح هذا للبنية التحتيّة بالتحقق من أنّ أنواع propTypes تتطابق مع الخاصيات الأصيلة لتقليل فرص عدم التطابق بين شيفرة Objective-C وشيفرة JavaScript.

بعد ذلك، لِنُضِف الخاصيّة ‎‎region‎‎ الأعقَد. نبدأ بإضافة الشيفرة الأصيلة:

// RNTMapManager.m
RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, MKMapView)
{
  [view setRegion:json ? [RCTConvert MKCoordinateRegion:json] : defaultView.region animated:YES];
}

هذا أعقد من حالة BOOL البسيطة سابقًا. لدينا الآن نوعٌ باسم MKCoordinateRegion يحتاج إلى دالّة تحويل (conversion function)، ولدينا شيفرة مخصصّة لتحريك الواجهة عند ضبط المنطقة (region) من JavaScript. داخل جسم الدالّة التي نُقدِّمها، يشير json إلى القيمة الأولية (raw value) التي مُرِّرَت من JavaScript. هناك أيضًا متغيّرُ ‎‎view‎‎ يتيح لنا الوصول إلى نسخة مُعالج (أو مُدير) الواجهة، ونستخدم ‎‎defaultView‎‎ لإعادة تعيين الخاصية إلى القيمة الافتراضية إذا أرسلت شيفرة JavaScript القيمة ‎‎null‎‎.

يمكنك كتابة أي دالّة تحويلٍ تريدها لعَرضِك، إليك إجراء MKCoordinateRegion عبر فئةٍ (category) على RCTConvert. يستخدم هذا فئةً موجودة بالفعل من ReactNative باسم ‎‎RCTConvert+CoreLocation‎‎:

// RNTMapManager.m

#import "RCTConvert+Mapkit.h"

// RCTConvert+Mapkit.h

#import <MapKit/MapKit.h>
#import <React/RCTConvert.h>
#import <CoreLocation/CoreLocation.h>
#import <React/RCTConvert+CoreLocation.h>

@interface RCTConvert (Mapkit)

+ (MKCoordinateSpan)MKCoordinateSpan:(id)json;
+ (MKCoordinateRegion)MKCoordinateRegion:(id)json;

@end

@implementation RCTConvert(MapKit)

+ (MKCoordinateSpan)MKCoordinateSpan:(id)json
{
  json = [self NSDictionary:json];
  return (MKCoordinateSpan){
    [self CLLocationDegrees:json[@"latitudeDelta"]],
    [self CLLocationDegrees:json[@"longitudeDelta"]]
  };
}

+ (MKCoordinateRegion)MKCoordinateRegion:(id)json
{
  return (MKCoordinateRegion){
    [self CLLocationCoordinate2D:json],
    [self MKCoordinateSpan:json]
  };
}

@end

دوال التحويل هذه مُصمَّمةٌ لمعالجة بيانات JSON بأمان، والتي قد ترميها JavaScript عبر عرض رسائل أخطاء الصندوق الأحمر وعند إرجاع قيم التهيئة القياسية (standard initialization values) عند فقدان المفاتيح أو أخطاء تطوير الأخرى.

لإكمال دعم الخاصيّة ‎‎region‎‎، نحتاج إلى توثيقها باستخدام propTypes (أو سنحصل على رسالة خطأ أن الخاصية الأصيلة غير مُوثَّقة)، بعدها يُمكننا ضبطها مثل أي خاصيّة أخرى:

// MapView.js

MapView.propTypes = {
  /**
   * A Boolean value that determines whether the user may use pinch
   * gestures to zoom in and out of the map.
   */
  zoomEnabled: PropTypes.bool,

  /**
   * The region to be displayed by the map.
   *
   * The region is defined by the center coordinates and the span of
   * coordinates to display.
   *المنطقة التي ستُعرَض في الخريطة
   *
   * تُحدَّد المنطقة عبر إحداثيات المركز ومجالها
   */
  region: PropTypes.shape({
    /** 
      * إحداثيات مركز الخريطة
     * Coordinates for the center of the map.
     */
    latitude: PropTypes.number.isRequired,
    longitude: PropTypes.number.isRequired,

    /**
     * Distance between the minimum and the maximum latitude/longitude
     * to be displayed.
     * المسافة بين الحد الأدنى والأقصى لكل من خطي العرض والطول
     */
    latitudeDelta: PropTypes.number.isRequired,
    longitudeDelta: PropTypes.number.isRequired,
  }),
};

// MyApp.js

render() {
  var region = {
    latitude: 37.48,
    longitude: -122.16,
    latitudeDelta: 0.1,
    longitudeDelta: 0.1,
  };
  return (
    <MapView
      region={region}
      zoomEnabled={false}
      style={{ flex: 1 }}
    />
  );
}

يمكنك هنا أن ترى أن شكل المنطقة واضح في توثيق JavaScript، مثاليًّا يمكننا توليد جزء من هذه الشيفرة تلقائيًّا، لكن هذه الميّزة غير متوفّرة حاليًّا.

أحيانًا، سيكون للمكوّن الأصيل بعض الخاصيات الخاصة التي لا تريد أن تكون جزءًا من واجهة برمجة التطبيقات لمكون React ذو العلاقة. مثلًا، يحتوي Switch على معالج onChange مخصَّص للحدث الأصيل الأوليّ (raw native event)، ويُوفّر الوصولَ إلى خاصيّة المعالجة onValueChange التي تُستدعى بالقيمة المنطقية فقط بدلاً من الحدث الأوليّ. ولأنّك لا تريد أن تكون هذه الخاصيات (الأصيلة حصرًا) جزءًا من واجهة برمجة التطبيقات، فأنت لا تريد وضعها في أنواع propTypes، ولكن إذا لم تفعل فستحصل على خطأ. الحل هو ببساطة إضافتها إلى الخيار ‎‎nativeOnly‎‎، مثلًا:

var RCTSwitch = requireNativeComponent('RCTSwitch', Switch, {
  nativeOnly: { onChange: true }
});

الأحداث (Events)

لدينا الآن مكوّن خريطة أصيل يمكننا التحكم فيه بسهولة من JavaScript، لكن كيف نتعامل مع أحداث المستخدم (User events)، مثل القرص للتكبير أو للتصغير أو المسح (panning) لتغيير المنطقة المرئية؟

إلى الآن، قمنا فقط بإرجاع نسخة MKMapView من تابع ‎‎-(UIView *)view‎‎ الخاص بمعالج الإدارة (manager). لا يمكننا إضافة خاصيات جديدة إلى MKMapView لذلك علينا إنشاء صنف فرعيّ جديد من MKMapView، والذي سنستخدمه لواجهتنا. يُمكننا بعد ذلك إضافة دالة ردّ نداءٍ (callback) باسم ‎‎onRegionChange‎‎ إلى هذا الصنف الفرعيّ:

// RNTMapView.h

#import <MapKit/MapKit.h>

#import <React/RCTComponent.h>

@interface RNTMapView: MKMapView

@property (nonatomic, copy) RCTBubblingEventBlock onRegionChange;

@end

// RNTMapView.m

#import "RNTMapView.h"

@implementation RNTMapView

@end

لاحظ أن جميع أحداث RCTBubblingEventBlock يجب أن تكون مسبوقة بالمقطع on. بعد ذلك، صرِّح (declare) عن خاصية معالجة أحداث (event handler property) على RNTMapManager، واجعلها مفوَّضةً لجميع الواجهات التي توفّر الوصول إليها، وأعِد توجيه الأحداث إلى JavaScript عن طريق استدعاء كتلة معالج الأحداث (event handler block) من الواجهة الأصيلة.

// RNTMapManager.m

#import <MapKit/MapKit.h>
#import <React/RCTViewManager.h>

#import "RNTMapView.h"
#import "RCTConvert+Mapkit.h"

@interface RNTMapManager : RCTViewManager <MKMapViewDelegate>
@end

@implementation RNTMapManager

RCT_EXPORT_MODULE()

RCT_EXPORT_VIEW_PROPERTY(zoomEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(onRegionChange, RCTBubblingEventBlock)

RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, MKMapView)
{
    [view setRegion:json ? [RCTConvert MKCoordinateRegion:json] : defaultView.region animated:YES];
}

- (UIView *)view
{
  RNTMapView *map = [RNTMapView new];
  map.delegate = self;
  return map;
}

#pragma mark MKMapViewDelegate

- (void)mapView:(RNTMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
  if (!mapView.onRegionChange) {
    return;
  }

  MKCoordinateRegion region = mapView.region;
  mapView.onRegionChange(@{
    @"region": @{
      @"latitude": @(region.center.latitude),
      @"longitude": @(region.center.longitude),
      @"latitudeDelta": @(region.span.latitudeDelta),
      @"longitudeDelta": @(region.span.longitudeDelta),
    }
  });
}
@end

تُستدعى في التابع المفوَّض ‎‎-mapView:regionDidChangeAnimated:‎‎ كتلة معالج الأحداث على الواجهة المتوافقة مع بيانات المنطقة، وينتج عن استدعاء كتلة معالج الأحداث onRegionChange استدعاءُ نفس خاصيّة رد النداء في JavaScript. تُستدعى دالّة رد النداء هذه مع الحدث الأولي، والذي نقوم بمعالجته عادةً في المكوِّن المُغلِّف لتبسيط واجهة برمجة التطبيقات:

// MapView.js

class MapView extends React.Component {
  _onRegionChange = (event) => {
    if (!this.props.onRegionChange) {
      return;
    }

    // معالجة الحدث الأوليّ
    this.props.onRegionChange(event.nativeEvent);
  }
  render() {
    return (
      <RNTMap
        {...this.props}
        onRegionChange={this._onRegionChange}
      />
    );
  }
}
MapView.propTypes = {
  /**
   * Callback that is called continuously when the user is dragging the map.
 * دالة رد نداء تُستدعى باستمرار عندما يسحب المستخدم الخريطة
   */
  onRegionChange: PropTypes.func,
  ...
};

// MyApp.js

class MyApp extends React.Component {
  onRegionChange(event) {
    // Do stuff with event.region.latitude, etc.
   // تعامل مع القيمة
    // event.region.latitude
    // وغيرها من القيم
  }

  render() {
    var region = {
      latitude: 37.48,
      longitude: -122.16,
      latitudeDelta: 0.1,
      longitudeDelta: 0.1,
    };
    return (
      <MapView
        region={region}
        zoomEnabled={false}
        onRegionChange={this.onRegionChange}
      />
    );
  }
}

التعامل مع عدة واجهات أصيلة

يمكن أن يكون لواجهة React Native عدة أبناء في شجرة الواجهات، مثلًا:

<View>
  <MyNativeView />
  <MyNativeView />
  <Button />
</View>

في هذا المثال يغلف الصنف MyNativeView المكون NativeComponent ويقدم الدوال التي ستستدعى على منصة iOS. يعرَّف MyNativeView في MyNativeView.ios.js ويحوي دوال الوكيل الموجودة في NativeComponent. عندما يتفاعل المستخدم مع المكون بالنقر على الزر يتغير لون الخلفية backgroundColor الخاص بالواجهة MyNativeView، في هذه الحالة لن يعلم UIManager أي مكون MyNativeView يجب أن يعالج وأي منها سيغير backgroundColor، ستحل هذه المشكلة بالطريقة التالية:

<View>
  <MyNativeView ref={this.myNativeReference} />
  <MyNativeView ref={this.myNativeReference2} />
  <Button
    onPress={() => {
      this.myNativeReference.callNativeMethod();
    }}
  />
</View>

الأى يحوي المكون مرجعًا إلى MyNativeView محدّد مما يسمح لنا باستخدام نسخة محددة من MyNativeView. الآن يستطيع الزر أن يعرف لأي MyNativeView يجب أن يغير backgroundColor .في هذا المثال لنفرض أن callNativeMethod هي التي تغير backgroundColor. سيحوي MyNativeView.ios.js شيفرة كما يلي:

class MyNativeView extends React.Component {
  callNativeMethod = () => {
    UIManager.dispatchViewManagerCommand(
      ReactNative.findNodeHandle(this),
      UIManager.getViewManagerConfig('RNCMyNativeView').Commands
        .callNativeMethod,
      []
    );
  };

  render() {
    return <NativeComponent ref={NATIVE_COMPONENT_REF} />;
  }
}

callNativeMethod هي دالتنا المخصصة في iOS والتي تغير backgroundColor كمثال، والتي تعرض من خلال MyNativeView. تستخدم هذه الدالة UIManager.dispatchViewManagerCommand والتي تحتاج ثلاثة معاملات:

  • (nonnull NSNumber \*)reactTag : الرقم المعرف لواجهة react.
  • commandID:(NSInteger)commandID : الرقم المعرف للدالة الأصيلةالتي يجب استدعاؤها.
  • commandArgs:(NSArray<id> \*)commandArgs : معاملات الدالة الأصيلة التي يمكن أن نمررها من JS إلى native.

إليك الملف RNCMyNativeViewManager.m:

#import <React/RCTViewManager.h>
#import <React/RCTUIManager.h>
#import <React/RCTLog.h>

RCT_EXPORT_METHOD(callNativeMethod:(nonnull NSNumber*) reactTag) {
    [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *,UIView *> *viewRegistry) {
        NativeView *view = viewRegistry[reactTag];
        if (!view || ![view isKindOfClass:[NativeView class]]) {
            RCTLogError(@"Cannot find NativeView with tag #%@", reactTag);
            return;
        }
        [view callNativeMethod];
    }];

}

تعرَّف callNativeMethod في الملف RNCMyNativeViewManager.m، وتحوي معاملًا واحدًا فقط هو (nonnull NSNumber*) reactTag. ستجد هذه الدالة المصدرة علاضًا معينًا يستخدم addUIBlock ويحوي المعامل viewRegistry ويعيد المكون اعتمادًا على reactTag مما يسمح له باستدعاء الدالة على المكون الصحيح.

الأنماط (Styles)

لأنّ جميع واجهات React الأصيلة (native react views) الخاصة بنا هي أصناف فرعية من الصنف UIView، فإنّ معظم سمات الأنماط (style attributes) ستعمل كالمتوقّع تلقائيًّا. لكن ستحتاج بعض المكونات إلى نمط افتراضي، مثلًا، UIDatePicker يحتاج إلى حجم ثابت. هذا النمط الافتراضي مهم لتعمل خوارزمية التخطيط كما يجب، لكننا نريد أيضًا التمكّن من الكتابة فوق النمط الافتراضي وتغييره عند استخدام المكوّن. يقوم المكوّن DatePickerIOS بهذا عن طريق تغليف المكون الأصيل في واجهة إضافية تتميّز بتنسيق مرن، وباستخدام نمط ثابت (الذي يولَّد بثوابت تُمرَّر من الجزء الأصيل) على المكون الأصيل الداخلي:

// DatePickerIOS.ios.js

import { UIManager } from 'react-native';
var RCTDatePickerIOSConsts = UIManager.RCTDatePicker.Constants;
...
  render: function() {
    return (
      <View style={this.props.style}>
        <RCTDatePickerIOS
          ref={DATEPICKER}
          style={styles.rkDatePickerIOS}
          ...
        />
      </View>
    );
  }
});

var styles = StyleSheet.create({
  rkDatePickerIOS: {
    height: RCTDatePickerIOSConsts.ComponentHeight,
    width: RCTDatePickerIOSConsts.ComponentWidth,
  },
});

تُصدَّر ثوابت RCTDatePickerIOSConsts من الجزء الأصيل عبر الحصول على الإطار (frame) الفعلي للمكون الأصيل كما يلي:

// RCTDatePickerManager.m

- (NSDictionary *)constantsToExport
{
  UIDatePicker *dp = [[UIDatePicker alloc] init];
  [dp layoutIfNeeded];

  return @{
    @"ComponentHeight": @(CGRectGetHeight(dp.frame)),
    @"ComponentWidth": @(CGRectGetWidth(dp.frame)),
    @"DatePickerModes": @{
      @"time": @(UIDatePickerModeTime),
      @"date": @(UIDatePickerModeDate),
      @"datetime": @(UIDatePickerModeDateAndTime),
    }
  };
}

غطَّى هذا الدليل العديد من جوانب تجسير المكونات الأصيلة المخصصة، ولكن هناك الكثير مما قد تحتاج إلى أخذه على عين الاعتبار، مثل الخطافات المخصصة (custom hooks) لإدراج الواجهات الفرعية وتخطيطها. إذا أردت التعمق أكثر، انظر الشيفرة المصدريّة لبعض من مكوّنات React Native.

مصادر