إدارة الصور في React Native

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث

يوفر React Native طريقة موحدة لإدارة الصور وأصول الوسائط (media asset) الأخرى في تطبيقات iOS وAndroid الخاصّة بك. لإضافة صورة ثابتة (Static Image) إلى تطبيقك، ضعها في مكان ما في شجرة الشيفرة المصدريّة الخاصّة بمشروعك البرمجيّ، ثم أشِر إليها على النحو التالي:

<Image source={require('./my-icon.png')} />

يُقرَّر (أو يُحلّ: resolve) اسم الصورة بنفس الطريقة التي تُقرَّر بها وحدات JavaScript. في المثال أعلاه، سيبحث المُجمع (bundler) عن صورةٍ باسم ‎my-icon.png‎ في نفس المجلد الذي يوجد فيه المكوّن الذي يستورِد الصورة. وإذا كانت لديك صورة باسم ‎my-icon.ios.png‎ وصورة ‎my-icon.android.png‎، فسيختار المجمع الملف الصحيح حسب المنصة.

يمكنك كذلك استخدام اللاحقين ‎@2x‎ و‎@3x‎ لتوفير صور لكثافات شاشات (screen densities) مختلفة. لنقل أنّ مشروعك يحتوي على بنية الملفات التالية:

.
├── button.js
└── img
    ├── check.png
    ├── check@2x.png
    └── check@3x.png

…مع شيفرة ‎button.js‎ كالتالي:

<Image source={require('./img/check.png')} />

سيُجمع المُجمّع (bundler) الصورة المناسبة حسب كثافة شاشة الجهاز. إذ ستُستخدَم مثلًا الصورة ‎check@2x.png‎ على هاتف iPhone 7، بينما ستُستخدَم ‎check@3x.png‎ على iPhone 7 Plus أو Nexus 5. إذا لم توجد صورة مطابقة لكثافة الشاشة، فستُختار أقرب صورة مناسبة.

قد تحتاج في Windows إلى إعادة تشغيل المجمّع عند إضافة صور جديدة إلى مشروعك.

من الفوائد التي ستحصل عليها:

  1. آلية العمل نفسها على iOS وAndroid.
  2. تتواجد الصور في مجلدِ شيفرة JavaScript نفسه. مما يوفّر مكوّنات مستقلّة.
  3. لا وجود لمجال أسماء عام (global namespace)، لذا لا داعي للقلق بشأن تصادمات الأسماء (name collisions).
  4. الصور المستخدمَة في تطبيقك هي التي تجمّع فقط، وستُتَجاهل الصور التي لا حاجة لك بها.
  5. لا تتطلب إضافة الصور وتغييرها إعادة تجميع التطبيق (app recompilation)، فقط حدّث المحاكي كما العادة.
  6. يعرف المُجمع أبعاد الصورة، ولا حاجة لتكرارها في الشيفرة.
  7. يمكن توزيع الصور كحزم npm.

يجب أن يكون اسم الصورة داخل الدّالة ‎require‎ ثابتًا لكي يعمل الاستيراد بشكل صحيح:

// جيد
<Image source={require('./my-icon.png')} />;

// سيئ
var icon = this.props.active
  ? 'my-icon-active'
  : 'my-icon-inactive';
<Image source={require('./' + icon + '.png')} />;

// جيد
var icon = this.props.active
  ? require('./my-icon-active.png')
  : require('./my-icon-inactive.png');
<Image source={icon} />;

لاحظ أن مصادر الصور التي تُستورَد بهذه الطريقة تتضمن معلومات الحجم (العرض ‎width‎ والارتفاع ‎height‎) للصورة. إن احتجت إلى تغيير حجم الصورة ديناميكيًا (أي عبر ‎flex‎) ، فقد تحتاج إلى تعيين القيمتين ‎{ width: undefined, height: undefined }‎ للخاصيّة ‎style‎ يدويًا.

موارد أخرى ثابتة غير الصور (Static Non-Image Resources)

يمكن استخدام بنية ‎require‎ الموضحة أعلاه لتضمين ملفات الصوت، أو الفيديو، أو المستندات في مشروعك أيضًا. معظم أنواع الملفات الشائعة مدعومةٌ بما في ذلك ‎.mp3 و ‎.wav و‎.mp4 و‎.mov و‎.html و‎‎.pdf‎‎ انظر صفحة إعدادات المُجمع الافتراضيّة للحصول على القائمة الكاملة.

يُمكنك إضافة الدعم لأنواع ملفّات أخرى عبر إنشاء ملفّ إعداداتٍ للمُجمّع.

تنبيه: يجب على مقاطع الفيديو استخدام تموضعٍ مطلق (absolute positioning) بدلاً من ‎flexGrow‎، وذلك لأنّ معلومات الحجم لا تمرَّر حاليًا إلى الأصول الأخرى غير الصور. هذا القيد غير موجود على مقاطع الفيديو التي تُربَط مباشرة داخل Xcode أو في مجلد الأصول (Assets folder) لنظام Android.

موارد الصور من التطبيقات الهجينة (Hybrid App's Resources)

إن كنت تبني تطبيقًا هجينًا (ذو أجزاءٍ من واجهة المستخدم مكتوبة بإطار React Native، وأجزاءٍ أخرى مكتوبة بلغة المنصّة الأصيلة) فيُمكنك استخدام الصور المُجمعة مسبقًا داخل التطبيق.

بالنسبة للصور المضمَّنة في فهارس أصول Xcode أو في مجلد Android القابل للسحب (drawable folder)، فاستخدم اسم الصورة دون امتداد (extension):

<Image
  source={{ uri: 'app_icon' }}
  style={{ width: 40, height: 40 }}
/>

بالنسبة للصور الموجودة في مجلد الأصول (assets folder) في Android، فاستخدم بنية ‎‎asset:/‎‎:

<Image
  source={{ uri: 'asset:/app_icon.png' }}
  style={{ width: 40, height: 40 }}
/>

لاحظ أنّ هذه الطرائق لا توفر أي فحوصاتِ سلامة (safety checks). عليك أن تضمن بنفسك أنّ هذه الصور متوفرة في التطبيق. كما يجب عليك تحديد أبعاد الصورة يدويًا.

جلب الصور عبر الشبكة (Network Images)

لن تكون العديد من الصور التي ستعرضها في تطبيقك جاهزة ومتوفّرة أثناء تجميع التطبيق (compile time)، وقد تحتاج إلى تحميل بعض الصور ديناميكيًا لتخفيض حجم التطبيق. وعلى عكس الموارد الثابتة فمن الضروريّ تحديد أبعاد صورتك يدويًا. ويوصى بشدّة باعتماد بروتوكول https أيضًا لتلبية متطلبات App Transport Security على iOS.

// جيد
<Image source={{uri: 'https://reactjs.org/logo-og.png'}}
       style={{width: 400, height: 400}} />

// سيئ
<Image source={{uri: 'https://reactjs.org/logo-og.png'}} />

طلبات الشبكة لجلب الصور

إذا كنت ترغب في تحديد معلومات إضافيّةٍ مثل فعل HTTP (أي GET، أو POST، أو PUT وغيرها) أو ترويساتٍ (Headers) أو جسم بياناتٍ (Body) مع طلب الصورة، فيمكنك ذلك عن طريق تعريف هذه الخاصيّات على كائن ‎source‎:

<Image
  source={{
    uri: 'https://reactjs.org/logo-og.png',
    method: 'POST',
    headers: {
      Pragma: 'no-cache'
    },
    body: 'Your Body goes here'
  }}
  style={{ width: 400, height: 400 }}
/>

التعامل مع بيانات صور مرمزة مستلمة من الطلبية

قد تجلب أحيانًا بيانات صور مشفّرة أو مرمزة بترميز محدَّد (encoded image data، أي بيان صورة خام) كرد على الطلبات التي تُرسَل إلى واجهات REST البرمجيّة. يمكنك اعتماد بنية ‎'data:'‎ لاستخدام هذه الصور. ولا بدّ من تحديد أبعاد صورتك يدويًا كما هو الحال مع موارد الشبكة.

ملاحظة: يُنصَح باعتماد هذه الطريقة للصور الديناميكية والصغيرة جدًا فقط، كالأيقونات التي تُحفَظ في قاعدة بيانات مثلًا.

//  تتضمن العرض والارتفاع على الأقل  
<Image
  style={{
    width: 51,
    height: 51,
    resizeMode: 'contain'
  }}
  source={{
    uri:
      'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADMAAAAzCAYAAAA6oTAqAAAAEXRFWHRTb2Z0d2FyZQBwbmdjcnVzaEB1SfMAAABQSURBVGje7dSxCQBACARB+2/ab8BEeQNhFi6WSYzYLYudDQYGBgYGBgYGBgYGBgYGBgZmcvDqYGBgmhivGQYGBgYGBgYGBgYGBgYGBgbmQw+P/eMrC5UTVAAAAABJRU5ErkJggg=='
  }}
/>

التحكم في التخبئة (Cache Control) في نظام iOS

في بعض الحالات، قد ترغب في عرض صورةٍ ما فقط إذا كانت موجودة في مخزن التخبئة المحليّ (كصورة ذات دقّة منخفضة حتى تتوفر دقّةٌ أعلى). وفي حالات أخرى، قد لا يهمك ما إذا كانت الصورة قديمة، ولا مشكلة في عرض صورة قديمة لتوفير بعض طلبيات المراسلة عبر الشبكة (bandwidth). توفّر الخاصية ‎cache‎ الموجودة داخل الخاصيّة ‎source‎ القدرةَ على التّحكم في كيفية تعامل نظام الشبكة مع نظام التخبئة. لكن تذكّر أنّ هذه الميّزة متاحة على نظام iOS فقط.

تقبل الخاصيّة ‎cache‎ القيم التالية:

  • default‎: استخدم الاستراتيجية الافتراضية للمنصّة الأصيلة.
  • reload‎: ستُحمَّل البيانات الخاصة بعنوان URL من المصدر الأصلي. لا يجب استخدام أي بيانات مُخبَّئةٍ حاليةٍ لتلبية طلب تحميل عنوان URL.
  • force-cache‎: ستُستخدَم البيانات المخبَّئة لتلبية الطلب بغضّ النظر عن عمرِها أو تاريخ انتهاء صلاحيّتها. إذا لم توجد أي بياناتٍ مطابقةٍ للطلب في مخزن التخبئة، فستُحمَّل البيانات من المصدر الأصلي.
  • only-if-cached‎: ستُستخدَم البيانات المخبَّئة لتلبية الطلب بغضّ النظر عن عمرِها أو تاريخ انتهاء صلاحيّتها. إذا لم توجد أي بياناتٍ مطابقةٍ للطلب في مخزن التخبئة، فلن تُجرى أي محاولةٍ لتحميل البيانات من المصدر الأصلي. وسيُعدّ ذلك فشلًا في تحميل البيانات.
<Image
  source={{
    uri: 'https://reactjs.org/logo-og.png',
    cache: 'only-if-cached'
  }}
  style={{ width: 400, height: 400 }}
/>

صور نظام الملفات المحلي (Local Filesystem Images)

انظر توثيق واجهة CameraRoll البرمجيّة لمثالٍ على كيفيّة استخدام الموارد المحلية الموجودة خارج ‎Images.xcassets‎.

جلب أفضل صورة ممكنة من معرض الصور (Camera Roll)

يحفظ نظام التشغيل iOS أحجامًا متعددة للصورة نفسها في معرض الصور، ومن المهم اختيار أقرب صورة مناسبة قدر الإمكان لأسباب تتعلق بالأداء. إذ لا حاجة لاستخدام صورةٍ بأبعاد 3264 × 2448 ذات الجودة الكاملة كمصدرٍ عند عرض صورةٍ مصغرة ذات الأبعاد 200 × 200 مثلًا. إذا وُجدت صورة تطابق الأبعاد المعطاة، فسيختارها React Native، وإلا فستُستخدَم أوّل صورة يُعثَر عليها بأبعاد أكبر بنسبة 50‎%‎‎‎ على الأقل لتجنب التمويه (blur) عند تغيير حجم الصورة من حجم قريب. يتم كل هذا افتراضيًّا، لذا لا داعي للقلق حول كتابة الشيفرة الشاقّة (والمُعرَّضة للأخطاء) للقيام بذلك بنفسك.

سبب عدم تغيير حجم الصور كلها تلقائيًّا

في مُتصفّح الويب إذا لم توفِّر حجمًا للصورة فسيُصيِّر المتصفح عنصرًا بأبعاد 0x0، ثمّ سيُنزِّل الصورة ثم سيعرضها حسب الحجم الصحيح. المشكلة الكبرى في هذا السلوك هي أن واجهة المستخدم الخاصة بك ستتشوَّه أثناء تحميل الصور، ما يجعل تجربة المستخدم سيئة للغاية.

هذا السلوك غير مُطبّقٍ في React Native عن عمد. يُطالب هذا المطورَ بِبَذل مزيدٍ من الجهد لمعرفة أبعاد (أو نسبة الأبعاد [aspect ratio]) الصورة مُسبقًا، لكنّ مطوّري React Native مؤمنون بأنّ ذلك سيؤدي إلى تجربة مستخدم أفضل. يمكن تحديد حجم الصور الثابتة التي تُحمَّل من حزمة التطبيق من خلال بنية ‎require('./my-icon.png')‎ تلقائيًا لأنّ أبعادها معروفة فورًا في وقت الوصل (mounting).

مثلًا، يُمكن لنتيجة ‎require('./my-icon.png')‎‎ أن تكون ما يلي:

{"__packager_asset":true,"uri":"my-icon.png","width":591,"height":573}

تمرير مصدر الصورة على شكل كائن (Source as an object)

في React Native، أحد القرارات المثيرة للاهتمام هو أن الخاصيّة ‎src‎ تسمى ‎source‎ ولا تأخذ سلسلة نصيّة، بل كائنًا يحتوي على خاصيّة باسم ‎uri‎:

<Image source={{uri: 'something.jpg'}} />

على جانب البنية التحتيّة (من وجهة نظر مُطوّري إطار React Native)، السبب هو أنّ هذا يسمح لنا بإضافة بياناتٍ وصفيةٍ (metadata) لهذا الكائن. على سبيل المثال، إذا كنت تستخدم الاستدعاء ‎require('./my-icon.png')‎، فسيُضيف إطار العمل معلوماتٍ حول موقعه وحجمه الفعليين (لكن لا تعتمد على هذه الميّزة، فقد تتغير مستقبلًا!). إضافة إلى أنّ هذه الطريقة مفيدة كذلك لإضافة ميّزات أخرى مستقبلًا بسهولة، فقد يُضيف مطورو الإطار مثلًا دعم دمج الصور (sprites) في مرحلة ما إلى الإطار، فبدلاً من إخراج ‎{uri: ...}‎، يمكن للإطار إخراج ما يلي:

{uri: ..., crop: {left: 10, top: 50, width: 20, height: 40}}

وبذلك سيدعم عمليّة دمج الصور (spriting) في جميع الأماكن دون حاجةٍ لتغيير الشيفرة المكتوبة مسبقًا.

أمّا على جانب المستخدم، فسيتيح لك هذا إضافة تعليماتٍ (annotate) إلى الكائن بخاصيّات مفيدة كأبعاد الصورة لحساب الحجم الذي ستُعرَض به. ولا تتردد في استخدام الكائن كهيكل بياناتٍ (data structure) لتخزين المزيد من المعلومات حول صورتك.

إنشاء صورة خلفية (Background Image)

إضافة ميّزة ‎background-image‎ الموجودة على الويب لتحديد صورة خلفيّة من أكثر الطلبات الشائعة من طرف المطورين. للتعامل مع حالة الاستخدام هذه، يمكنك استخدام المكوّن <ImageBackground>‎، الذي يحتوي على نفس الخاصيّات الموجودة في مكوّن <Image>، مع إمكانيّة إضافة أي عدد من الأبناء إليه ليعمل كصورة خلفيّة.

قد لا ترغب في استخدام المكوّن <ImageBackground> في بعض الحالات لأنّه بسيط جدًا. انظر توثيق <ImageBackground> لمزيد من المعلومات. وأنشئ مكوّنًا خاصًّا بك عند الحاجة.

return (
  <ImageBackground source={...} style={{width: '100%', height: '100%'}}>
    <Text>Inside</Text>
  </ImageBackground>
);

لاحظ أنه يجب عليك تحديد خاصيّتي العرض والارتفاع لنمط المكوّن.

أنماط تدوير أركان الإطار (Border Radius Styles) في نظام iOS

لاحظ أن الخاصيّات التالية الخاصّة بتحديد تدوير أركان الإطار الخاصّة بالزوايا التاليّة تُتَجاهل على مكوّن الصورة في نظام iOS:

  • borderTopLeftRadius
  • borderTopRightRadius
  • borderBottomLeftRadius
  • borderBottomRightRadius

فك الترميز خارج السلسلة (Off-thread Decoding)

يمكن أن يستغرق فك ترميز الصور أكثر من إطارٍ (frame) واحدٍ زمني. وهذا أحد المصادر الرئيسية لفشل الأطُر (أو سقوط الأطر: frame drops) على الويب لأنّ الترميز يُفكّ على السلسلة الرئيسيّة (main thread). في React Native، يُفك ترميز الصور على سلسلة مختلفة. عمليًّا، ستحتاج بالفعل إلى معالجة حالة انتظار تنزيل الصورة، لذا لا يتطلب عرض صورة بديلة (placeholder) لعدد قليل من الإطارات أثناء فك الترميز أي تغيير في الشيفرة.

مصادر