أمثلة على تكوين UIViewControllers باستخدام RouteComposer

في مقال سابق ، تحدثت عن النهج الذي نستخدمه لإنشاء والتنقل بين وحدات التحكم في العرض في العديد من التطبيقات التي أعمل عليها ، والتي نتج عنها ، نتيجة لذلك ، مكتبة RouteComposer منفصلة. تلقيت قدرًا كبيرًا من التعليقات اللطيفة على المقالة السابقة وبعض النصائح العملية ، التي دفعتني إلى كتابة مقال آخر يشرح كيفية تكوين المكتبة. تحت القطع ، سأحاول أن أجد بعض التكوينات الأكثر شيوعًا.



كيف يوزع جهاز التوجيه التكوين


للبدء ، ضع في اعتبارك كيفية تحليل جهاز التوجيه للتكوين الذي كتبته. خذ المثال من المقالة السابقة:


let productScreen = StepAssembly(finder: ClassFinder(options: [.current, .visible]), factory: ProductViewControllerFactory()) .using(UINavigationController.pushToNavigation()) .from(SingleContainerStep(finder: NilFinder(), factory: NavigationControllerFactory())) .using(GeneralAction.presentModally()) .from(GeneralStep.current()) .assemble() 

سوف يمر جهاز التوجيه بسلسلة من الخطوات بدءًا من الخطوة الأولى ، وحتى إحدى الخطوات (باستخدام Finder المقدم) "تفيد" أن UIViewController المطلوب UIViewController بالفعل على المكدس. (على سبيل المثال ، GeneralStep.current() ضمان وجود GeneralStep.current() في حزمة وحدة التحكم). بعد ذلك سيبدأ جهاز التوجيه في الرجوع للخلف عبر سلسلة من الخطوات لإنشاء UIViewController المطلوبة باستخدام UIViewController المقدمة ودمجها باستخدام Action المحددة. بفضل التحقق من النوع حتى في مرحلة التجميع ، في معظم الأحيان ، لن تتمكن من استخدام UITabBarController.addTab غير المتوافقة مع Fabric المقدم (أي أنك لن تتمكن من استخدام UITabBarController.addTab في وحدة تحكم UITabBarController.addTab التي أنشأها NavigationControllerFactory ).


إذا كنت تتخيل التكوين الموضح أعلاه ، فعندئذٍ إذا كان لديك فقط ProductViewController على الشاشة ، فسيتم تنفيذ الخطوات التالية:


  1. لن ClassFinder على ProductViewController وسيستمر جهاز التوجيه
  2. لن يجد NilFinder أبدًا أي شيء وسيستمر جهاز التوجيه
  3. ستعطي GeneralStep.current دائمًا أعلى UIViewController على المكدس.
  4. UIViewController العثور على بدء UIViewController ، سيعود جهاز التوجيه
  5. ينشئ UINavigationController باستخدام `NavigationControllerFactory
  6. سيظهر بشكل مشروط باستخدام GeneralAction.presentModally
  7. ProductViewController ProductViewController ProductViewControllerFactory
  8. يدمج ProductViewController تم إنشاؤه في UINavigationController السابق باستخدام UINavigationController.pushToNavigation
  9. الانتهاء من التنقل

ملاحظة: يجب أن يكون من المفهوم أنه في الواقع من المستحيل إظهار UINavigationController موحد بدون بعض UINavigationController بداخله. لذلك ، سيتم تنفيذ الخطوات 5-8 بواسطة جهاز التوجيه بترتيب مختلف قليلاً. ولكن لا يجب التفكير في الأمر. يتم وصف التكوين بالتسلسل.


من الممارسات الجيدة عند كتابة التكوين افتراض أن المستخدم يمكن أن يكون موجودًا في أي مكان في تطبيقك في الوقت الحالي ، وفجأة ، يتلقى رسالة دفع مع طلب للوصول إلى الشاشة التي تصفها ، ومحاولة الإجابة على السؤال - "كيف يجب أن يتصرف التطبيق؟ ؟ "،" كيف يتصرف Finder في التكوين الذي أصفه؟ ". إذا تم أخذ جميع هذه الأسئلة في الاعتبار ، فستحصل على تهيئة مضمونة لتظهر للمستخدم الشاشة المطلوبة أينما كان. وهذا هو المطلب الرئيسي للتطبيقات الحديثة من الفرق المشاركة في التسويق وجذب المستخدمين (إشراكهم) .


StackIteratingFinder وخياراته:


يمكنك تنفيذ مفهوم Finder بأي طريقة تعتقد أنها مقبولة للغاية. ومع ذلك ، فإن أسهل طريقة هي التكرار من خلال الرسم البياني لوحدات التحكم في العرض على الشاشة. لتبسيط هذا الهدف ، توفر المكتبة StackIteratingFinder والتطبيقات المختلفة التي ستتولى هذه المهمة. عليك فقط الإجابة على السؤال - هل هذا هو UIViewController الذي تتوقعه.


للتأثير على سلوك StackIteratingFinder بأجزاء الرسم البياني (المكدس) لوحدات التحكم في العرض التي تريدها أن تبحث عنها ، يمكنك تحديد مجموعة من SearchOptions عند إنشائها. ويجب أن يسكنوا بمزيد من التفصيل:


  • current : وحدة تحكم العرض الأعلى على المكدس. (الذي هو rootViewController UIWindow أو الذي يظهر بشكل مشروط في الأعلى)
  • visible : إذا كان UIViewController عبارة عن حاوية ، فابحث في UINavigationController آه المرئي (على سبيل المثال: UINavigationController يحتوي دائمًا على UINavigationController مرئي ، قد يكون لدى UISplitController واحدة أو اثنتين اعتمادًا على كيفية تقديمه.
  • contained : في حالة كون UIViewController حاوية ، ابحث في جميع وحدات UINavigationController المتداخلة (على سبيل المثال: UINavigationController جميع وحدات التحكم في العرض الخاصة بـ UINavigationController بما في ذلك وحدة التحكم المرئية)
  • presenting : ابحث أيضًا في كل UIViewController آه تحت UIViewController (إذا كان هناك بالطبع)
  • presented : ابحث في StackIteratingFinder عن الخيار المقدم (بالنسبة لـ StackIteratingFinder لا يكون هذا الخيار منطقيًا ، لأنه يبدأ دائمًا من الأعلى)

قد يوضح الشكل التالي الشرح أعلاه أكثر وضوحًا:


أوصي بالتعرف على مفهوم الحاويات في مقال سابق.


مثال إذا كنت تريد من Finder يبحث عن AccountViewController في المكدس بأكمله ولكن فقط بين AccountViewController المرئي ، فيجب كتابة هذا على النحو التالي:


 ClassFinder<AccountViewController, Any?>(options: [.current, .visible, .presenting]) 

ملحوظة: إذا كانت الإعدادات المقدمة لسبب ما قليلة ، فبإمكانك دائمًا كتابة تطبيق Finder الخاص بك بسهولة. سيكون أحد الأمثلة في هذه المقالة.


دعونا ننتقل في الواقع إلى الأمثلة.


أمثلة على التكوينات مع التفسيرات


لدي rootViewController معين ، وهو rootViewController UIWindow ، وأريد HomeViewController بـ HomeViewController معين في نهاية التنقل:


 let screen = StepAssembly( finder: ClassFinder<HomeViewController, Any?>(), factory: XibFactory()) .using(GeneralAction.replaceRoot()) .from(GeneralStep.root()) .assemble() 

XibFactory بتحميل HomeViewController من ملف HomeViewController.xib الخاص بـ HomeViewController.xib


لا تنس أنه إذا كنت تستخدم تطبيقات مجردة من Finder and Factory معًا ، فيجب عليك تحديد نوع UIViewController والسياق لواحد على الأقل من الكيانات - ClassFinder<HomeViewController, Any?>


ماذا يحدث إذا قمت في المثال أعلاه باستبدال GeneralStep.root بـ GeneralStep.current ؟


سيعمل التكوين حتى يتم استدعاؤه في اللحظة التي يكون فيها أي UIViewController مشروط على الشاشة. في هذه الحالة ، لن يتمكن GeneralAction.replaceRoot من استبدال وحدة تحكم الجذر ، نظرًا لوجود وحدة تحكم مشروطة فوقه ، وسيبلغ جهاز التوجيه عن خطأ. إذا كنت تريد أن يعمل هذا التكوين على أي حال ، فأنت بحاجة إلى أن توضح للموجه أنك تريد تطبيق GeneralAction.replaceRoot بشكل خاص على الجذر UIViewController . ثم سيقوم جهاز التوجيه بإزالة جميع UIViewControllers الممثلة بشكل موحد وسيعمل التكوين في أي موقف.


أريد عرض بعض AccountViewController ، إذا كان لا يزال يظهر بشكل جيد ، داخل أي UINavigationController وهو موجود حاليًا على الشاشة في مكان ما (حتى إذا كان UINavigationController هذا تحت بعض UINavigationController المشروط):


 let screen = StepAssembly( finder: ClassFinder<AccountViewController, Any?>(), factory: XibFactory()) .using(UINavigationController.pushToNavigation()) .from(SingleStep(ClassFinder<UINavigationController, Any?>(), NilFactory())) .from(GeneralStep.current()) .assemble() 

ماذا يعني NilFactory في هذا التكوين؟ من خلال ذلك ، تخبر جهاز التوجيه أنه إذا لم يتمكن من العثور على أي UINavigationController على الشاشة ، فأنت لا تريده أن يقوم بإنشائه وفقط لعدم القيام بأي شيء في هذه الحالة. بالمناسبة ، نظرًا لأن هذا NilFactory ، لا يمكنك استخدام Action بعده.


أرغب في عرض بعض AccountViewController ، إذا لم يتم عرضه بالفعل ، داخل أي UINavigationController والذي يوجد حاليًا في مكان ما على الشاشة ، وإذا لم يكن الأمر كذلك ، قم بإنشائه وعرضه بشكل مشروط:


 let screen = StepAssembly( finder: ClassFinder<AccountViewController, Any?>(), factory: XibFactory()) .using(UINavigationController.PushToNavigation()) .from(SwitchAssembly<UINavigationController, Any?>() .addCase(expecting: ClassFinder<UINavigationController, Any?>(options: .visible)) //   -    .assemble(default: { //      return ChainAssembly() .from(SingleContainerStep(finder: NilFinder(), factory: NavigationControllerFactory())) .using(GeneralAction.presentModally()) .from(GeneralStep.current()) .assemble() }) ).assemble() 

أريد أن أظهر UITabBarController تحتوي على HomeViewController و AccountViewController مع استبدالها بالجذر الحالي:


 let tabScreen = SingleContainerStep( finder: ClassFinder(), factory: CompleteFactoryAssembly(factory: TabBarControllerFactory()) .with(XibFactory<HomeViewController, Any?>(), using: UITabBarController.addTab()) .with(XibFactory<AccountViewController, Any?>(), using: UITabBarController.addTab()) .assemble()) .using(GeneralAction.replaceRoot()) .from(GeneralStep.root()) .assemble() 

هل يمكنني استخدام UIViewControllerTransitioningDelegate المخصص مع الإجراء GeneralAction.presentModally :


 let transitionController = CustomViewControllerTransitioningDelegate() //     .using(GeneralAction.PresentModally(transitioningDelegate: transitionController)) 

أريد أن أذهب إلى AccountViewController ، أينما كان المستخدم ، في علامة تبويب أخرى أو حتى في نوع من نافذة مشروطة:


 let screen = StepAssembly( finder: ClassFinder<AccountViewController, Any?>(), factory: NilFactory()) .from(tabScreen) .assemble() 

لماذا نستخدم NilFactory ؟ لا نحتاج إلى إنشاء AccountViewController إذا لم يتم العثور عليه. سيتم بناؤه في تكوين tabScreen . نراها أعلاه.


أريد أن ForgotPasswordViewController ، ولكن ، بالتأكيد ، بعد LoginViewController داخل UINavigationController :


 let loginScreen = StepAssembly( finder: ClassFinder<LoginViewController, Any?>(), factory: XibFactory()) .using(UINavigationController.pushToNavigation()) .from(NavigationControllerStep()) .using(GeneralAction.presentModally()) .from(GeneralStep.current()) .assemble() let forgotPasswordScreen = StepAssembly( finder: ClassFinder<ForgotPasswordViewController, Any?>(), factory: XibFactory()) .using(UINavigationController.pushToNavigation()) .from(loginScreen.expectingContainer()) .assemble() 

يمكنك استخدام التهيئة في المثال للتنقل في ForgotPasswordViewController و LoginViewController


لماذا expectingContainer في المثال أعلاه؟


نظرًا لأن إجراء pushToNavigation يتطلب وجود UINavigationController وفي التكوين بعده ، تسمح لنا طريقة expectingContainer UINavigationController بتجنب خطأ في التجميع من خلال التأكد من أننا نحرص على أنه عندما يصل جهاز التوجيه إلى loginScreen في loginScreen ، سيكون UINavigationController موجودًا.


ماذا يحدث إذا قمت في التكوين أعلاه باستبدال GeneralStep.current بـ GeneralStep.root ؟


ستعمل ، ولكن نظرًا لأنك تخبر جهاز التوجيه أنك تريده أن يبدأ في بناء سلسلة من UIViewController الجذر ، إذا تم فتح أي UIViewController مشروط فوقه ، فسيخفيها جهاز التوجيه قبل البدء في بناء السلسلة.


يحتوي UITabBarController على HomeViewController يحتوي على HomeViewController و BagViewController تبويب. أريد أن يتمكن المستخدم من التبديل بينها باستخدام الرموز الموجودة على علامات التبويب كالمعتاد. ولكن إذا قمت بالاتصال بالتكوين برمجيًا (على سبيل المثال ، قام المستخدم بالنقر فوق "الانتقال إلى حقيبة" داخل HomeViewController ) ، فيجب ألا يقوم التطبيق بتبديل علامة التبويب ، ولكن إظهار BagViewController موحد.


هناك 3 طرق لتحقيق ذلك في التكوين:


  1. قم بتعيين StackIteratingFinder للبحث فقط في تلك المرئية باستخدام [.current ، .visible]
  2. استخدم NilFinder مما يعني أن جهاز التوجيه لن يعثر أبدًا على BagViewController في BagViewController وسيعمل دائمًا على إنشائه. ومع ذلك ، فإن هذا النهج له تأثير جانبي - على سبيل المثال ، إذا تم تقديم مستخدم موجود بالفعل في BagViewController بشكل موحد ، وعلى سبيل المثال ، ينقر على رابط عالمي يجب أن BagViewController ، فلن يعثر عليه جهاز التوجيه وسيعمل على إنشاء مثيل آخر BagViewController فوقه مشروط. قد لا يكون هذا ما تريده.
  3. قم بتغيير ClassFinder الصغير بحيث يجد فقط BagViewController معروض بشكل مشروط ويتجاهل الباقي ، BagViewController بالفعل في التكوين.

 struct ModalBagFinder: StackIteratingFinder { func isTarget(_ viewController: BagViewController, with context: Any?) -> Bool { return viewController.presentingViewController != nil } } let screen = StepAssembly( finder: ModalBagFinder(), factory: XibFactory()) .using(UINavigationController.pushToNavigation()) .from(NavigationControllerStep()) .using(GeneralAction.presentModally()) .from(GeneralStep.current()) .assemble() 

بدلا من الاستنتاج


آمل أن تكون طرق تكوين جهاز التوجيه أكثر وضوحًا إلى حد ما. كما قلت ، نستخدم هذا النهج في 3 تطبيقات ولم نواجه حتى الآن موقفًا لا يتسم بالمرونة الكافية. لا تستخدم المكتبة ، وكذلك تنفيذ جهاز التوجيه المقدم إليها ، أي حيل موضوعية مع وقت التشغيل وتتبع جميع مفاهيم Cocoa Touch بالكامل ، مما يساعد فقط على كسر عملية التكوين إلى خطوات وتنفيذها في التسلسل المحدد واختبارها مع إصدارات iOS 9 إلى 12. بالإضافة إلى ذلك ، هذا النهج يتناسب مع جميع الأنماط المعمارية التي تنطوي على العمل مع مكدس UIViewController (MVC ، MVVM ، VIP ، RIB ، VIPER ، إلخ.)


سأكون سعيدا لتعليقاتكم واقتراحاتكم. خاصة إذا كنت تعتقد أنه من المفيد التطرق إلى بعض الجوانب بمزيد من التفاصيل. ربما يحتاج مفهوم السياقات إلى توضيح.

Source: https://habr.com/ru/post/ar428990/


All Articles