कैसे एक कंटेनर देखें प्रोग्रामेटिक रूप से जोड़ने के लिए


107

एक कंटेनर व्यू को इंटरफ़ेस संपादक के माध्यम से आसानी से स्टोरीबोर्ड में जोड़ा जा सकता है। जब जोड़ा जाता है, एक कंटेनर दृश्य एक प्लेसहोल्डर दृश्य, एक एम्बेड सेगमेंट और एक (बच्चा) व्यू कंट्रोलर का होता है।

हालाँकि, मैं प्रोग्रामर रूप से कंटेनर व्यू को जोड़ने का एक तरीका नहीं खोज पा रहा हूँ। वास्तव में, मैं एक वर्ग का नाम भी नहीं पा रहा हूं UIContainerView

कंटेनर व्यू के वर्ग का एक नाम निश्चित रूप से एक अच्छी शुरुआत है। सेग्यू सहित एक पूरी गाइड की बहुत सराहना की जाएगी।

मुझे व्यू कंट्रोलर प्रोग्रामिंग गाइड के बारे में पता है, लेकिन मैं इसे वैसा नहीं मानता, जिस तरह से कंटेनर व्यूअर के लिए इंटरफेस बिल्डर करता है। उदाहरण के लिए, जब बाधाओं को ठीक से सेट किया जाता है, तो (बच्चा) दृश्य कंटेनर व्यू में आकार में बदलाव के लिए अनुकूल होगा।


1
जब आप कहते हैं कि "क्या मतलब है" जब बाधाओं को ठीक से सेट किया गया है, तो (बच्चा) दृश्य कंटेनर व्यू में आकार में बदलाव को स्वीकार कर लेगा ("इसका अर्थ है कि जब आप नियंत्रक नियंत्रण को देखते हैं तो यह सच नहीं है)? बाधाएं उसी तरह से काम करती हैं, जैसा आपने आईबी में कंटेनर दृश्य के माध्यम से किया था या प्रोग्राम कंट्रोलर प्रोग्राम में देखें।
रोब

1
सबसे महत्वपूर्ण बात एम्बेडेड ViewControllerजीवन चक्र है। एम्बेडेड ViewControllerके इंटरफ़ेस बिल्डर द्वारा जीवन चक्र सामान्य है, लेकिन प्रोग्राम के रूप में जोड़ा एक है viewDidAppear, न तो viewWillAppear(_:)है और न ही viewWillDisappear
डॉनसॉन्ग

2
@DawnSong - यदि आप व्यू कॉन्टेंट कॉल को सही तरीके से करते हैं, viewWillAppearऔर viewWillDisappearचाइल्ड व्यू कंट्रोलर को कॉल किया जाता है, तो ठीक है। यदि आपके पास एक उदाहरण है जहां वे नहीं हैं, तो आपको स्पष्ट करना चाहिए या अपने स्वयं के प्रश्न को पोस्ट करना चाहिए कि वे क्यों नहीं हैं।
रोब

जवाबों:


228

एक स्टोरीबोर्ड "कंटेनर दृश्य" सिर्फ एक मानक UIViewवस्तु है। कोई विशेष "कंटेनर दृश्य" प्रकार नहीं है। वास्तव में, यदि आप पदानुक्रम को देखते हैं, तो आप देख सकते हैं कि "कंटेनर दृश्य" एक मानक है UIView:

कंटेनर दृश्य

इस प्रोग्राम को प्राप्त करने के लिए, आप "व्यू कंट्रोलर कंटेंट" को नियोजित करते हैं:

  • instantiateViewController(withIdentifier:)स्टोरीबोर्ड ऑब्जेक्ट पर कॉल करके चाइल्ड व्यू कंट्रोलर को इंस्टेंट करें ।
  • addChildअपने पैरेंट व्यू कंट्रोलर में कॉल करें।
  • viewअपने दृश्य पदानुक्रम के साथ दृश्य नियंत्रक जोड़ें addSubview(और frameउपयुक्त के रूप में या बाधाओं को भी सेट करें )।
  • didMove(toParent:)चाइल्ड व्यू कंट्रोलर पर विधि को कॉल करें , पेरेंट व्यू कंट्रोलर के संदर्भ को पास करना।

देखें एक कंटेनर देखें नियंत्रक को लागू करने में देखें नियंत्रक गाइड प्रोग्रामिंग और की धारा "एक कंटेनर देखें नियंत्रक को लागू करने" UIViewController कक्षा संदर्भ


उदाहरण के लिए, स्विफ्ट 4.2 में ऐसा लग सकता है:

override func viewDidLoad() {
    super.viewDidLoad()

    let controller = storyboard!.instantiateViewController(withIdentifier: "Second")
    addChild(controller)
    controller.view.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(controller.view)

    NSLayoutConstraint.activate([
        controller.view.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
        controller.view.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
        controller.view.topAnchor.constraint(equalTo: view.topAnchor, constant: 10),
        controller.view.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10)
    ])

    controller.didMove(toParent: self)
}

ध्यान दें, ऊपर वास्तव में पदानुक्रम में "कंटेनर दृश्य" नहीं जोड़ा गया है। यदि आप ऐसा करना चाहते हैं, तो आप कुछ ऐसा करेंगे:

override func viewDidLoad() {
    super.viewDidLoad()

    // add container

    let containerView = UIView()
    containerView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(containerView)
    NSLayoutConstraint.activate([
        containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
        containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
        containerView.topAnchor.constraint(equalTo: view.topAnchor, constant: 10),
        containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10),
    ])

    // add child view controller view to container

    let controller = storyboard!.instantiateViewController(withIdentifier: "Second")
    addChild(controller)
    controller.view.translatesAutoresizingMaskIntoConstraints = false
    containerView.addSubview(controller.view)

    NSLayoutConstraint.activate([
        controller.view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
        controller.view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
        controller.view.topAnchor.constraint(equalTo: containerView.topAnchor),
        controller.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
    ])

    controller.didMove(toParent: self)
}

यह बाद का पैटर्न बेहद उपयोगी है अगर कभी अलग-अलग चाइल्ड व्यू कंट्रोलर्स के बीच संक्रमण हो और आप सिर्फ यह सुनिश्चित करना चाहते हैं कि एक बच्चे का दृश्य उसी स्थान पर हो और पिछले बच्चे का दृश्य (यानी प्लेसमेंट के लिए सभी अद्वितीय अवरोध कंटेनर दृश्य द्वारा तय किए गए हों,) हर बार इन बाधाओं के पुनर्निर्माण की आवश्यकता के बजाय)। लेकिन अगर केवल साधारण दृश्य नियंत्रण प्रदर्शन करते हैं, तो इस अलग कंटेनर दृश्य की आवश्यकता कम सम्मोहक है।


उपरोक्त उदाहरणों में, मैं खुद को बाधाओं translatesAutosizingMaskIntoConstraintsको falseपरिभाषित करने के लिए स्थापित कर रहा हूं। आप स्पष्ट रूप से छोड़ सकते हैं translatesAutosizingMaskIntoConstraintsके रूप में trueहै और दोनों सेट frameऔर autosizingMaskविचारों आप जोड़ने के लिए, यदि आप चाहें तो के लिए।


स्विफ्ट 3 और स्विफ्ट 2 प्रस्तुतिकरण के लिए इस उत्तर के पिछले संशोधन देखें ।


मुझे नहीं लगता कि आपका उत्तर पूर्ण है। सबसे महत्वपूर्ण बात एम्बेडेड ViewControllerजीवन चक्र है। एम्बेडेड ViewControllerके इंटरफ़ेस बिल्डर द्वारा जीवन चक्र सामान्य है, लेकिन प्रोग्राम के रूप में जोड़ा एक है viewDidAppear, न तो viewWillAppear(_:)है और न ही viewWillDisappear
डॉनसॉन्ग

एक और अजीब बात यह है कि एम्बेडेड है ViewController's viewDidAppearअपने माता-पिता के में कहा जाता है viewDidLoad, के बजाय के दौरान अपने माता पिता कीviewDidAppear
DawnSong

@DawnSong - "लेकिन प्रोग्राम में एक जोड़ा गया है viewDidAppear, [लेकिन] न तो viewWillAppear(_:)और न ही viewWillDisappear"। willदिखाई तरीकों दोनों स्थितियों में सही ढंग से कहा जाता है। किसी को didMove(toParentViewController:_)प्रोग्राम करते समय, फोन करना चाहिए , वरना वे नहीं करेंगे। उपस्थिति के समय के बारे में। विधियाँ, उन्हें एक ही क्रम में दोनों तरीकों से बुलाया जाता है। अलग क्या है, थोम, का समय है viewDidLoad, क्योंकि एम्बेड के साथ, यह पहले लोड किया गया है parent.viewDidLoad, लेकिन प्रोग्रामेटिक के साथ, जैसा कि हम उम्मीद करेंगे, यह दौरान होता है parent.viewLoadLoad
रोब

2
मैं काम न करने की अड़चनों पर अड़ा हुआ था; पता चला मैं गायब था translatesAutoresizingMaskIntoConstraints = false। मुझे नहीं पता कि इसकी आवश्यकता क्यों है या यह काम क्यों करता है, लेकिन आपके उत्तर में इसे शामिल करने के लिए धन्यवाद।
hasen

1
@Rob at developer.apple.com/library/archive/featuredarticles/… 5-1 की सूची में, ऑब्जेक्टिव-सी कोड की एक पंक्ति है जो कहती है, "content.view.frame = [स्व फ्रेमफ़ोरकंट्रोलर कंट्रोलर];" उस कोड में "FrameForContentController" क्या है? क्या कंटेनर दृश्य का फ्रेम है?
डैनियल ब्राउनर

24

@ स्विफ्ट 3 में रोब का जवाब:

    // add container

    let containerView = UIView()
    containerView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(containerView)
    NSLayoutConstraint.activate([
        containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
        containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
        containerView.topAnchor.constraint(equalTo: view.topAnchor, constant: 10),
        containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10),
        ])

    // add child view controller view to container

    let controller = storyboard!.instantiateViewController(withIdentifier: "Second")
    addChildViewController(controller)
    controller.view.translatesAutoresizingMaskIntoConstraints = false
    containerView.addSubview(controller.view)

    NSLayoutConstraint.activate([
        controller.view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
        controller.view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
        controller.view.topAnchor.constraint(equalTo: containerView.topAnchor),
        controller.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
        ])

    controller.didMove(toParentViewController: self)

13

विवरण

  • Xcode 10.2 (10E125), स्विफ्ट 5

उपाय

import UIKit

class WeakObject {
    weak var object: AnyObject?
    init(object: AnyObject) { self.object = object}
}

class EmbedController {

    private weak var rootViewController: UIViewController?
    private var controllers = [WeakObject]()
    init (rootViewController: UIViewController) { self.rootViewController = rootViewController }

    func append(viewController: UIViewController) {
        guard let rootViewController = rootViewController else { return }
        controllers.append(WeakObject(object: viewController))
        rootViewController.addChild(viewController)
        rootViewController.view.addSubview(viewController.view)
    }

    deinit {
        if rootViewController == nil || controllers.isEmpty { return }
        for controller in controllers {
            if let controller = controller.object {
                controller.view.removeFromSuperview()
                controller.removeFromParent()
            }
        }
        controllers.removeAll()
    }
}

प्रयोग

class SampleViewController: UIViewController {
    private var embedController: EmbedController?

    override func viewDidLoad() {
        super.viewDidLoad()
        embedController = EmbedController(rootViewController: self)

        let newViewController = ViewControllerWithButton()
        newViewController.view.frame = CGRect(origin: CGPoint(x: 50, y: 150), size: CGSize(width: 200, height: 80))
        newViewController.view.backgroundColor = .lightGray
        embedController?.append(viewController: newViewController)
    }
}

पूरा नमूना

ViewController

import UIKit

class ViewController: UIViewController {

    private var embedController: EmbedController?
    private var button: UIButton?
    private let addEmbedButtonTitle = "Add embed"

    override func viewDidLoad() {
        super.viewDidLoad()

        button = UIButton(frame: CGRect(x: 50, y: 50, width: 150, height: 20))
        button?.setTitle(addEmbedButtonTitle, for: .normal)
        button?.setTitleColor(.black, for: .normal)
        button?.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
        view.addSubview(button!)

        print("viewDidLoad")
        printChildViewControllesInfo()
    }

    func addChildViewControllers() {

        var newViewController = ViewControllerWithButton()
        newViewController.view.frame = CGRect(origin: CGPoint(x: 50, y: 150), size: CGSize(width: 200, height: 80))
        newViewController.view.backgroundColor = .lightGray
        embedController?.append(viewController: newViewController)

        newViewController = ViewControllerWithButton()
        newViewController.view.frame = CGRect(origin: CGPoint(x: 50, y: 250), size: CGSize(width: 200, height: 80))
        newViewController.view.backgroundColor = .blue
        embedController?.append(viewController: newViewController)

        print("\nChildViewControllers added")
        printChildViewControllesInfo()
    }

    @objc func buttonTapped() {

        if embedController == nil {
            embedController = EmbedController(rootViewController: self)
            button?.setTitle("Remove embed", for: .normal)
            addChildViewControllers()
        } else {
            embedController = nil
            print("\nChildViewControllers removed")
            printChildViewControllesInfo()
            button?.setTitle(addEmbedButtonTitle, for: .normal)
        }
    }

    func printChildViewControllesInfo() {
        print("view.subviews.count: \(view.subviews.count)")
        print("childViewControllers.count: \(childViewControllers.count)")
    }
}

ViewControllerWithButton

import UIKit

class ViewControllerWithButton:UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    private func addButon() {
        let buttonWidth: CGFloat = 150
        let buttonHeight: CGFloat = 20
        let frame = CGRect(x: (view.frame.width-buttonWidth)/2, y: (view.frame.height-buttonHeight)/2, width: buttonWidth, height: buttonHeight)
        let button = UIButton(frame: frame)
        button.setTitle("Button", for: .normal)
        button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
        view.addSubview(button)
    }

    override func viewWillLayoutSubviews() {
        addButon()
    }

    @objc func buttonTapped() {
        print("Button tapped in \(self)")
    }
}

परिणाम

यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें


1
मैंने इस कोड का उपयोग tableViewControllerएक जोड़ने के लिए किया है viewControllerलेकिन पूर्व का शीर्षक सेट नहीं कर सकता। मुझे नहीं पता कि क्या ऐसा करना संभव है। मैंने यह प्रश्न पोस्ट किया है । यदि आप इस पर एक नज़र रखते हैं तो यह आपके लिए अच्छा है।
महावन

12

यहाँ स्विफ्ट 5 में मेरा कोड है।

class ViewEmbedder {
class func embed(
    parent:UIViewController,
    container:UIView,
    child:UIViewController,
    previous:UIViewController?){

    if let previous = previous {
        removeFromParent(vc: previous)
    }
    child.willMove(toParent: parent)
    parent.addChild(child)
    container.addSubview(child.view)
    child.didMove(toParent: parent)
    let w = container.frame.size.width;
    let h = container.frame.size.height;
    child.view.frame = CGRect(x: 0, y: 0, width: w, height: h)
}

class func removeFromParent(vc:UIViewController){
    vc.willMove(toParent: nil)
    vc.view.removeFromSuperview()
    vc.removeFromParent()
}

class func embed(withIdentifier id:String, parent:UIViewController, container:UIView, completion:((UIViewController)->Void)? = nil){
    let vc = parent.storyboard!.instantiateViewController(withIdentifier: id)
    embed(
        parent: parent,
        container: container,
        child: vc,
        previous: parent.children.first
    )
    completion?(vc)
}

}

प्रयोग

@IBOutlet weak var container:UIView!

ViewEmbedder.embed(
    withIdentifier: "MyVC", // Storyboard ID
    parent: self,
    container: self.container){ vc in
    // do things when embed complete
}

गैर-स्टोरीबोर्ड दृश्य नियंत्रक के साथ अन्य एम्बेड फ़ंक्शन का उपयोग करें।


2
महान वर्ग, हालाँकि मुझे खुद को एक ही मास्टर व्यू कंट्रोलर के भीतर 2 व्यू कॉन्ट्रोलर्स को एम्बेड करने की आवश्यकता है, जिसे आपका removeFromParentकॉल रोकता है, आप इसे अनुमति देने के लिए अपनी कक्षा में संशोधन कैसे करेंगे?
गैरीसाबो

शानदार :) धन्यवाद
Rebeloper

यह अच्छा उदाहरण है, लेकिन मैं इसमें कुछ परिवर्तन एनिमेशन कैसे जोड़ सकता हूं (एम्बेड करना, चाइल्ड व्यू कंट्रोलर्स की जगह)?
मिचेल जिब्रो
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.