प्याज नियंत्रक। हम स्क्रीन को भागों में तोड़ते हैं

परमाणु डिजाइन और सिस्टम डिज़ाइन डिज़ाइन में लोकप्रिय हैं: यह वह है जब सब कुछ घटकों से होता है, नियंत्रण से लेकर स्क्रीन तक। एक प्रोग्रामर के लिए अलग-अलग नियंत्रण लिखना मुश्किल नहीं है, लेकिन पूरे स्क्रीन के साथ क्या करना है?


आइए नए साल के उदाहरण पर एक नज़र डालें:


  • चलो सब कुछ एक साथ रहना;
  • नियंत्रकों में विभाजित: नेविगेशन, टेम्पलेट और सामग्री का चयन करें;
  • अन्य स्क्रीन के लिए पुन: उपयोग कोड।


सभी एक झुंड में


इस नए साल की स्क्रीन पिज़्ज़ेरिया के विशेष शुरुआती घंटों के बारे में बात करती है। यह काफी सरल है, इसलिए इसे एक नियंत्रक बनाना अपराध नहीं होगा:



लेकिन। अगली बार, जब हमें एक समान स्क्रीन की आवश्यकता होगी, तो हमें इसे फिर से दोहराना होगा, और फिर सभी स्क्रीन पर समान परिवर्तन करना होगा। खैर, यह संपादन के बिना नहीं होता है।


इसलिए, इसे भागों में विभाजित करना और अन्य स्क्रीन के लिए इसका उपयोग करना अधिक उचित है। मैंने तीन पर प्रकाश डाला:


  • नेविगेशन,
  • सामग्री के लिए एक क्षेत्र के साथ एक टेम्पलेट और स्क्रीन के नीचे कार्रवाई के लिए एक जगह,
  • केंद्र में अद्वितीय सामग्री।

प्रत्येक भाग को अपने स्वयं के UIViewController


कंटेनर नेविगेशन


नेविगेशन कंटेनर के सबसे हड़ताली उदाहरण UINavigationController और UITabBarController । प्रत्येक व्यक्ति अपने स्वयं के नियंत्रण के तहत स्क्रीन पर एक पट्टी रखता है, और शेष स्थान को एक और UIViewController लिए छोड़ देता है।


हमारे मामले में, केवल एक करीबी बटन के साथ सभी मोडल स्क्रीन के लिए एक कंटेनर होगा।


क्या बात है?

यदि हम बटन को दाईं ओर ले जाना चाहते हैं, तो हमें केवल एक नियंत्रक में इसे बदलना होगा।


या, अगर हम एक विशेष एनीमेशन के साथ सभी मोडल विंडो को दिखाने का फैसला करते हैं, और एक कड़ी चोट के साथ इंटरएक्टिव रूप से बंद कर देते हैं, जैसा कि ऐपस्ट्रीम स्टोरी कार्ड में है। फिर UIViewControllerTransitioningDelegate को केवल इस कंट्रोलर के लिए सेट करना होगा।



आप container view को अलग करने के लिए एक container view का उपयोग कर सकते हैं: यह माता-पिता में एक UIView बनाएगा और उसमें बच्चे नियंत्रक UIView सम्मिलित करेगा।



स्क्रीन के किनारे पर container viewSafe area स्वचालित रूप से बाल नियंत्रक पर लागू होगा:



स्क्रीन पैटर्न


सामग्री स्क्रीन पर स्पष्ट है: चित्र, शीर्षक, पाठ। बटन इसका हिस्सा लगता है, लेकिन विभिन्न आईफ़ोन पर सामग्री गतिशील है, और बटन तय हो गया है। विभिन्न कार्यों वाले दो सिस्टम दिखाई देते हैं: एक सामग्री प्रदर्शित करता है, और दूसरा इसे एम्बेड और संरेखित करता है। उन्हें दो नियंत्रकों में विभाजित किया जाना चाहिए।



पहला स्क्रीन के लेआउट के लिए ज़िम्मेदार है: सामग्री को केंद्रित किया जाना चाहिए, और स्क्रीन के नीचे बटन दबाया गया। दूसरा कंटेंट ड्रा करेगा।



एक टेम्पलेट के बिना, सभी नियंत्रक समान हैं, लेकिन तत्व नृत्य करते हैं।

अंतिम स्क्रीन के बटन अलग हैं - यह सामग्री पर निर्भर करता है। प्रतिनिधिमंडल समस्या को हल करने में मदद करेगा: नियंत्रक-टेम्पलेट सामग्री से नियंत्रण के लिए पूछेगा और उन्हें अपने UIStackView में प्रदर्शित करेगा।


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

क्यों देखें ()?

UIViewController मेरे पिछले लेख नियंत्रक में UIViewController साथ UIView को कैसे माहिर करें, इसके बारे में पढ़ सकते हैं , इसे आसान लें! हम UIView में कोड निकालते हैं।


बटन को संबंधित वस्तुओं के माध्यम से नियंत्रक से जोड़ा जा सकता है। उनके IBOutlet और IBAction सामग्री नियंत्रक में संग्रहीत हैं, बस तत्वों को पदानुक्रम में नहीं जोड़ा गया है।



आप सामग्री से तत्व प्राप्त कर सकते हैं और उन्हें UIStoryboardSegue की तैयारी के चरण में टेम्पलेट में जोड़ सकते हैं:


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

सेटर में, हम UIStackView नियंत्रण UIStackView :


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

नतीजतन, हमारे नियंत्रक को तीन भागों में विभाजित किया गया था: नेविगेशन, टेम्पलेट और सामग्री। तस्वीर में, सभी container view ग्रे में दिखाए गए हैं:



गतिशील नियंत्रक आकार


सामग्री नियंत्रक का अपना अधिकतम आकार है, यह आंतरिक constraints द्वारा सीमित है।


Container view Autoresizing mask आधार पर कॉन्स्टोर्स जोड़ता है, और वे सामग्री के आंतरिक आयामों के साथ संघर्ष करते हैं। समस्या कोड में हल की गई है: सामग्री नियंत्रक में, आपको यह इंगित करने की आवश्यकता है कि यह Autoresizing mask से नक्षत्रों से प्रभावित नहीं है:


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


इंटरफ़ेस बिल्डर के लिए दो और चरण हैं:


चरण 1. UIView लिए Intrinsic size निर्दिष्ट करें। लॉन्च के बाद वास्तविक मूल्य दिखाई देंगे, लेकिन अब हम किसी भी उपयुक्त व्यक्ति को डालेंगे।



चरण 2। सामग्री नियंत्रक के लिए, निर्दिष्ट Simulated Size निर्दिष्ट करें। यह पिछले आकार से मेल नहीं खा सकता है।


लेआउट त्रुटियां थीं, मुझे क्या करना चाहिए?

त्रुटियाँ तब होती हैं जब AutoLayout यह पता नहीं लगा सकता है कि वर्तमान आकार में तत्वों को कैसे विघटित किया जाए।


अधिकतर, निरंतरता की प्राथमिकताओं को बदलने के बाद समस्या दूर हो जाती है। आपको उन्हें नीचे रखने की आवश्यकता है ताकि UIView से एक दूसरों की तुलना में अधिक विस्तार / अनुबंध कर सके।


हम भागों में विभाजित करते हैं और कोड में लिखते हैं


हमने नियंत्रक को कई भागों में विभाजित किया है, लेकिन अभी तक हम उन्हें पुन: उपयोग नहीं कर सकते हैं, 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 से कंट्रोलर लोड करते हैं।

यदि नियंत्रक का नाम 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 उपयोग UIStoryborad , स्क्रीन को भागों में तोड़ना आसान है।


कोड में एक नियंत्रक जोड़ें


नियंत्रक को ठीक से काम करने के लिए, हमें इसके जीवन चक्र के सभी तरीकों की आवश्यकता है: will/did-appear/disappear


सही प्रदर्शन के लिए, आपको 5 चरणों को कॉल करने की आवश्यकता है:


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

Apple कोड को 4 चरणों में कम करने का सुझाव देता है, क्योंकि willMove(toParent) addChild() खुद को willMove(toParent) । संक्षेप में:


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

सादगी के लिए, आप इसे extension लपेट सकते हैं। हमारे मामले के लिए, हमें 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:) , लेकिन अब आप इसे कंट्रोलर एम्बेड विधि में बांध सकते हैं:


 // 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 कंट्रोल करने के लिए status bar की दृश्यता के लिए यह आवश्यक होता है कि कन्टेनर नहीं। इसके लिए property का एक जोड़ा है:


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

इन property का उपयोग करके property आप नियंत्रकों की एक श्रृंखला बना सकते हैं, बाद वाली status bar प्रदर्शित करने के लिए जिम्मेदार होगी।


सुरक्षित क्षेत्र


यदि कंटेनर बटन सामग्री को ओवरलैप करते हैं, तो आपको safeArea क्षेत्र को बढ़ाना चाहिए। यह कोड में किया जा सकता है: बाल नियंत्रकों के लिए additinalSafeAreaInsets सेट करें। आप इसे embedController() से 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 हरे क्षेत्र पर कब्जा कर safeArea :



हाशिये। सुपरवाइज मार्जिन को संरक्षित करें


नियंत्रकों में मानक margins । आमतौर पर वे स्क्रीन के प्रत्येक पक्ष से 16 अंक के बराबर होते हैं और केवल प्लस-आकार पर वे 20 अंक होते हैं।


margins आधार पर margins आप स्थिरांक बना सकते हैं, अलग-अलग iPhones के लिए किनारे पर इंडेंटेशन अलग होगा:



जब हम एक UIView को दूसरे में रखते हैं, तो margins आधा कर दिया जाता है: 8 अंक। इसे रोकने के लिए, आपको Preserve superview margins को शामिल करने की आवश्यकता है। फिर बच्चे का margins UIView माता-पिता के margins बराबर होगा। यह पूर्ण स्क्रीन कंटेनरों के लिए उपयुक्त है।


अंत


कंटेनर नियंत्रक एक शक्तिशाली उपकरण हैं। वे कोड को सरल करते हैं, अलग कार्य करते हैं, और उनका पुन: उपयोग किया जा सकता है। आप किसी भी तरह से नेस्टेड कंट्रोलर लिख सकते हैं: UIStoryboard , .xib या कोड में। सबसे महत्वपूर्ण बात, वे बनाने में आसान और उपयोग करने में मज़ेदार हैं।


GitHub पर एक लेख से एक उदाहरण


क्या आपके पास ऐसी स्क्रीन हैं जिनसे यह एक टेम्प्लेट बनाने लायक होगा? टिप्पणियों में साझा करें!

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


All Articles