تحكم البصل. نحن تقسيم الشاشات إلى أجزاء

يحظى التصميم الذري وتصميم النظام بشعبية في التصميم: هذا عندما يتكون كل شيء من مكونات ، من أدوات التحكم إلى الشاشات. ليس من الصعب على المبرمج كتابة عناصر تحكم منفصلة ، ولكن ماذا تفعل مع الشاشات بأكملها؟


دعونا نلقي نظرة على مثال العام الجديد:


  • دعنا نلتصق بكل شيء
  • مقسمة إلى وحدات تحكم: حدد التنقل ، القالب والمحتوى ؛
  • إعادة استخدام الرمز للشاشات الأخرى.


كل ذلك في حفنة


تتحدث شاشة هذه السنة الجديدة عن ساعات عمل مطاعم البيتزا الخاصة. إنها بسيطة للغاية ، لذلك لن تكون جريمة جعلها وحدة تحكم واحدة:



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


لذلك ، من المعقول تقسيمه إلى أجزاء واستخدامه للشاشات الأخرى. سلطت الضوء على ثلاثة:


  • الملاحة
  • قالب به مساحة للمحتوى ومكان لإجراءات أسفل الشاشة ،
  • محتوى فريد من نوعه في المركز.

حدد كل جزء في UIViewController الخاصة به.


حاوية الملاحة


الأمثلة الأكثر لفتا للحاويات الملاحة هي UINavigationController و UITabBarController . يشغل كل واحد شريطًا على الشاشة تحت أدوات التحكم الخاصة به ، ويترك المساحة المتبقية لـ UIViewController آخر.


في حالتنا ، سيكون هناك حاوية لجميع شاشات الوسائط مع زر إغلاق واحد فقط.


ما هي النقطة؟

إذا كنا نريد نقل الزر إلى اليمين ، فسنحتاج فقط إلى تغييره في وحدة تحكم واحدة.


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



يمكنك استخدام container view لفصل وحدات التحكم: ستقوم بإنشاء UIView في الأصل وإدراج UIView التحكم التابعة فيه.



قم بتمديد container view إلى حافة الشاشة. سيتم تطبيق Safe area تلقائيًا على وحدة التحكم التابعة:



نمط الشاشة


المحتوى واضح على الشاشة: الصورة ، العنوان ، النص. يبدو أن الزر جزء منه ، ولكن المحتوى ديناميكي على أجهزة iPhone مختلفة ، ويتم إصلاح الزر. يظهر نظامان لهما مهام مختلفة: أحدهما يعرض المحتوى ، والآخر يقوم بتضمينه ومحااذاته. يجب أن يتم تقسيمها إلى وحدتي تحكم.



الأول هو المسؤول عن تخطيط الشاشة: يجب أن يكون المحتوى مركَّزًا ، ويكون الزر مسمرًا أسفل الشاشة. والثاني رسم المحتوى.



بدون قالب ، جميع وحدات التحكم متشابهة ، ولكن العناصر الرقص.

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


 // OnboardingViewController.swift protocol OnboardingViewControllerDatasource { var supportingViews: [UIView] { get } } // NewYearContentViewController.swift extension NewYearContentViewController: OnboardingViewControllerDatasource { var supportingViews: [UIView] { return [view().doneButton] } } 

لماذا عرض ()؟

UIViewController أن تقرأ عن كيفية UIView مع UIViewController في مقالتي الأخيرة ، المراقب المالي ، خذ الأمور بسهولة! نحن نخرج الكود في UIView.


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



يمكنك الحصول على عناصر من المحتوى وإضافتها إلى القالب في مرحلة الإعداد لـ UIStoryboardSegue :


 // OnboardingViewController.swift override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if let buttonsDatasource = segue.destination as? OnboardingViewControllerDatasource { view().supportingViews = buttonsDatasource.supportingViews } } 

في المجموعة ، نضيف عناصر تحكم إلى UIStackView :


 // OnboardingView.swift var supportingViews: [UIView] = [] { didSet { for view in supportingViews { stackView.addArrangedSubview(view) } } } 

ونتيجة لذلك ، تم تقسيم وحدة التحكم الخاصة بنا إلى ثلاثة أجزاء: التنقل والقالب والمحتوى. في الصورة ، يظهر كل container view باللون الرمادي:



حجم تحكم ديناميكي


وحدة التحكم في المحتوى لها الحد الأقصى لحجمها ، فهي محدودة بسبب constraints الداخلية.


يضيف Container view ثوابت بناءً على Autoresizing mask ، Autoresizing mask مع الأبعاد الداخلية للمحتوى. يتم حل المشكلة في التعليمات البرمجية: في وحدة تحكم المحتوى ، تحتاج إلى الإشارة إلى أنه لا يتأثر بالثوابت من Autoresizing mask :


 // NewYearContentViewController.swift override func loadView() { super.loadView() view.translatesAutoresizingMaskIntoConstraints = false } 


هناك خطوتان أخريان لـ Interface Builder:


الخطوة 1. حدد Intrinsic size لـ UIView . ستظهر القيم الحقيقية بعد الإطلاق ، لكن في الوقت الحالي سنضع أي قيم مناسبة.



الخطوة 2. بالنسبة لوحدة التحكم في المحتوى ، حدد Simulated Size . قد لا تتطابق مع حجم الماضي.


حدثت أخطاء في التخطيط ، فماذا أفعل؟

تحدث الأخطاء عندما يتعذر على AutoLayout معرفة كيفية تحليل العناصر بالحجم الحالي.


في أغلب الأحيان ، تزول المشكلة بعد تغيير أولويات الثابت. تحتاج إلى وضعها حتى يتسنى لأحد UIView التوسع / العقد أكثر من الآخرين.


نقسم إلى أجزاء والكتابة في التعليمات البرمجية


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


تظهر ثلاث مهام في طريقنا:


  1. افصل كل وحدة تحكم في UIStoryboard الخاصة بها.
  2. رفض container view ، أضف وحدات التحكم إلى الحاويات في التعليمات البرمجية.
  3. ربط كل شيء مرة أخرى.

تقاسم UIStoryboard


تحتاج إلى إنشاء جهازي UIStoryboard ونسخ وحدة التحكم في التنقل ولصقها في وحدة التحكم في القالب. Embed segue ، ولكن سيتم نقل container view مع القيود التي تم تكوينها. يجب حفظ القيود ، ويجب استبدال container view بـ UIView العادية.


أسهل طريقة هي تغيير نوع طريقة عرض الحاوية في رمز UIStoryboard.
  • افتح UIStoryboard كرمز (قائمة سياق الملف → فتح كـ ... → شفرة المصدر) ؛
  • تغيير النوع من containerView view . من الضروري تغيير علامات الفتح والإغلاق .


    بنفس الطريقة ، يمكنك تغيير ، على سبيل المثال ، UIView إلى UIScrollView ، إذا لزم الأمر. والعكس صحيح.




قمنا بتعيين وحدة التحكم إلى الخاصية is initial view controller ، وسوف نسمي UIStoryboard كوحدة التحكم.


نحن تحميل وحدة تحكم من UIStoryboard.

إذا كان اسم وحدة التحكم يتطابق مع اسم UIStoryboard ، عندئذٍ يمكن لف التنزيل من خلال طريقة ستجد الملف نفسه:


 protocol Storyboardable { } extension Storyboardable where Self: UIViewController { static func instantiateInitialFromStoryboard() -> Self { let controller = storyboard().instantiateInitialViewController() return controller! as! Self } static func storyboard(fileName: String? = nil) -> UIStoryboard { let storyboard = UIStoryboard(name: fileName ?? storyboardIdentifier, bundle: nil) return storyboard } static var storyboardIdentifier: String { return String(describing: self) } static var storyboardName: String { return storyboardIdentifier } } 

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


إضافة وحدة تحكم في التعليمات البرمجية


لكي تعمل وحدة التحكم بشكل صحيح ، نحتاج إلى جميع أساليب دورة حياتها: will/did-appear/disappear .


للعرض الصحيح ، تحتاج إلى استدعاء 5 خطوات:


  willMove(toParent parent: UIViewController?) addChild(_ childController: UIViewController) addSubview(_ subivew: UIView) layout didMove(toParent parent: UIViewController?) 

تقترح Apple تقليل الرمز إلى 4 خطوات ، لأن addChild() نفسه سوف يستدعي willMove(toParent) . باختصار:


  addChild(_ childController: UIViewController) addSubview(_ subivew: UIView) layout didMove(toParent parent: UIViewController?) 

للبساطة ، يمكنك التفاف كل شيء في extension . بالنسبة insertSubview() ، نحتاج إلى إصدار مع insertSubview() .


 extension UIViewController { func insertFullframeChildController(_ childController: UIViewController, toView: UIView? = nil, index: Int) { let containerView: UIView = toView ?? view addChild(childController) containerView.insertSubview(childController.view, at: index) containerView.pinToBounds(childController.view) childController.didMove(toParent: self) } } 

للحذف ، تحتاج إلى نفس الخطوات ، فقط بدلاً من وحدة التحكم الأصل ، تحتاج إلى تعيين nil . الآن removeFromParent() يستدعي didMove(toParent: nil) ، والتخطيط غير مطلوب. النسخة المختصرة مختلفة جدا:


  willMove(toParent: nil) view.removeFromSuperview() removeFromParent() 

تخطيط


وضع قيود


لتعيين حجم وحدة التحكم بشكل صحيح ، سوف نستخدم AutoLayout . نحن بحاجة إلى تسمير جميع الأطراف في جميع الجوانب:


 extension UIView { func pinToBounds(_ view: UIView) { view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ view.topAnchor.constraint(equalTo: topAnchor), view.bottomAnchor.constraint(equalTo: bottomAnchor), view.leadingAnchor.constraint(equalTo: leadingAnchor), view.trailingAnchor.constraint(equalTo: trailingAnchor) ]) } } 

إضافة وحدة تحكم تابعة في التعليمات البرمجية


الآن يمكن الجمع بين كل شيء:


 // ModalContainerViewController.swift public func embedController(_ controller: UIViewController) { insertFullframeChildController(controller, index: 0) } 

نظرًا لتكرار الاستخدام ، يمكننا التفاف كل هذا extension :


 // ModalContainerViewController.swift extension UIViewController { func wrapInModalContainer() -> ModalContainerViewController { let modalController = ModalContainerViewController.instantiateInitialFromStoryboard() modalController.embedController(self) return modalController } } 

هناك حاجة أيضًا إلى طريقة مماثلة لوحدة تحكم القالب. prepare(for segue:) استخدام prepare(for segue:) في prepare(for segue:) ، ولكن الآن يمكنك prepare(for segue:) في طريقة تضمين التحكم:


 // OnboardingViewController.swift public func embedController(_ controller: UIViewController, actionsDatasource: OnboardingViewControllerDatasource) { insertFullframeChildController(controller, toView: view().contentContainerView, index: 0) view().supportingViews = actionsDatasource.supportingViews } 

إنشاء وحدة تحكم يشبه هذا:


 // MainViewController.swift @IBAction func showModalControllerDidPress(_ sender: UIButton) { let content = NewYearContentViewController.instantiateInitialFromStoryboard() //     let onboarding = OnboardingViewController.instantiateInitialFromStoryboard() onboarding.embedController(contentController, actionsDatasource: contentController) let modalController = onboarding.wrapInModalContainer() present(modalController, animated: true) } 

إن توصيل شاشة جديدة بالقالب أمر بسيط:


  • إزالة ما لا يتعلق بالمحتوى ؛
  • تحديد أزرار الإجراءات عن طريق تطبيق بروتوكول OnboardingViewControllerDatasource ؛
  • اكتب طريقة تربط قالب ومحتوى.

المزيد عن الحاويات


شريط الحالة


غالبًا ما يكون من الضروري التحكم في رؤية status bar بواسطة وحدة تحكم ذات محتوى وليس حاوية. هناك بعض property لهذا:


 // UIView.swift var childForStatusBarStyle: UIViewController? var childForStatusBarHidden: UIViewController? 

باستخدام هذه property يمكنك إنشاء سلسلة من وحدات التحكم ، وهذه الأخيرة ستكون مسؤولة عن عرض status bar .


منطقة آمنة


إذا كانت أزرار الحاوية تتداخل مع المحتوى ، فيجب عليك زيادة منطقة safeArea . يمكن القيام بذلك في التعليمات البرمجية: set additinalSafeAreaInsets لوحدات التحكم التابعة. يمكنك الاتصال به من embedController() :


 private func addSafeArea(to controller: UIViewController) { if #available(iOS 11.0, *) { let buttonHeight = CGFloat(30) let topInset = UIEdgeInsets(top: buttonHeight, left: 0, bottom: 0, right: 0) controller.additionalSafeAreaInsets = topInset } } 

إذا أضفت 30 نقطة في الأعلى ، فسوف يتوقف الزر عن تداخل المحتوى safeArea المنطقة الخضراء:



هوامش. الحفاظ على هوامش superview


وحدات التحكم لديها margins قياسية. عادة ما تكون مساوية 16 نقطة من كل جانب من جوانب الشاشة وفقط في أحجام Plus هي 20 نقطة.


استنادًا إلى margins يمكنك إنشاء ثوابت ، وستكون المسافة البادئة إلى الحافة مختلفة عن أجهزة iPhone المختلفة:



عندما نضع UIView في مكان آخر ، UIView margins إلى النصف: إلى 8 نقاط. لمنع هذا ، تحتاج إلى تضمين Preserve superview margins . عندئذ تكون margins UIView مساوية margins الأصل. انها مناسبة للحاويات ملء الشاشة.


النهاية


وحدات تحكم الحاوية هي أداة قوية. تعمل على تبسيط التعليمات البرمجية والمهام المنفصلة ويمكن إعادة استخدامها. يمكنك كتابة وحدات التحكم المتداخلة بأي طريقة: في UIStoryboard أو .xib أو ببساطة في التعليمات البرمجية. الأهم من ذلك ، أنها سهلة الإنشاء ومتعة الاستخدام.


مثال على مقال عن جيثب


هل لديك شاشات يمكن من خلالها صنع قالب؟ شارك في التعليقات!

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


All Articles