143 votes

Comment ajouter une vue de conteneur de manière programmatique ?

Une vue de conteneur peut être facilement ajoutée à un storyboard via l'éditeur d'interface. Une fois ajoutée, une vue conteneur se compose d'une vue de remplacement, d'une séquence d'intégration et d'un contrôleur de vue (enfant).

Cependant, je n'arrive pas à trouver un moyen d'ajouter une vue de conteneur de manière programmatique. En fait, je ne suis même pas capable de trouver une classe nommée UIContainerView ou à peu près.

Un nom pour la classe de la vue conteneur est certainement un bon début. Un guide complet, incluant la transition, serait très apprécié.

Je connais le guide de programmation des contrôleurs de vues, mais je ne le considère pas de la même manière que Interface Builder pour le visualiseur de conteneurs. Par exemple, lorsque les contraintes sont correctement définies, la vue (enfant) s'adapte aux changements de taille de la vue conteneur.

1 votes

Que voulez-vous dire lorsque vous dites "lorsque les contraintes sont correctement définies, la vue (enfant) s'adaptera aux changements de taille de la vue conteneur" (ce qui implique que ce n'est pas vrai lorsque vous faites du confinement de contrôleur de vue) ? Les contraintes fonctionnent de la même manière, que vous utilisiez la vue du conteneur dans IB ou le confinement du contrôleur de vue de manière programmatique.

1 votes

La chose la plus importante est l'intégration ViewController Le cycle de vie de l'entreprise. L'embarqué ViewController Le cycle de vie d'une personne ajoutée par Interface Builder est normal, mais celui d'une personne ajoutée de manière programmatique a été modifié. viewDidAppear ni l'un ni l'autre viewWillAppear(_:) ni viewWillDisappear .

2 votes

@DawnSong - Si vous effectuez correctement les appels au contenu de la vue, la fonction viewWillAppear y viewWillDisappear sont appelés sur le contrôleur de vue enfant, tout va bien. Si vous avez un exemple où ils ne le sont pas, vous devriez clarifier, ou poster votre propre question demandant pourquoi ils ne le sont pas.

276voto

Rob Points 70987

La "vue conteneur" d'un storyboard est juste un standard UIView objet. Il n'existe pas de type spécial de "vue conteneur". En fait, si vous regardez la hiérarchie des vues, vous pouvez voir que la "vue conteneur" est un objet standard de type UIView :

container view

Pour y parvenir de manière programmatique, vous utilisez le "confinement du contrôleur de vue" :

  • Instanciez le contrôleur de vue enfant en appelant instantiateViewController(withIdentifier:) sur l'objet storyboard.
  • Appelez addChildViewController dans votre contrôleur de vue parent.
  • Ajoutez le contrôleur de vue view à votre hiérarchie de vues avec addSubview (et définir également le frame ou des contraintes, selon le cas).
  • Appelez le didMove(toParentViewController:) sur le contrôleur de vue enfant, en passant la référence au contrôleur de vue parent.

Voir Mise en œuvre d'un contrôleur de vue de conteneur dans le Guide de programmation du contrôleur de vue et la section "Implémentation d'un contrôleur de vue conteneur" du document _Référence de classe UIViewController ._


Par exemple, en Swift 3, cela pourrait ressembler à ceci :

override func viewDidLoad() {
    super.viewDidLoad()

    let controller = storyboard!.instantiateViewController(withIdentifier: "Second")
    addChildViewController(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(toParentViewController: self)
}

Notez que l'opération ci-dessus n'ajoute pas réellement une "vue conteneur" à la hiérarchie. Si vous voulez le faire, vous devez faire quelque chose comme.. :

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")
    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)
}

Ce dernier modèle est extrêmement utile si vous passez d'un contrôleur de vue enfant à un autre et que vous voulez simplement vous assurer que la vue d'un enfant se trouve au même endroit que la vue de l'enfant précédent (c'est-à-dire que toutes les contraintes uniques pour le placement sont dictées par la vue conteneur, plutôt que de devoir reconstruire ces contraintes à chaque fois). Mais si vous n'effectuez qu'un simple confinement de vue, la nécessité de cette vue conteneur séparée est moins impérieuse.


Pour les rendus de Swift 2, voir révision précédente de cette réponse .

1 votes

Je ne pense pas que votre réponse soit complète. La chose la plus importante est l'intégration ViewController Le cycle de vie de l'entreprise. L'embarqué ViewController Le cycle de vie d'une personne ajoutée par Interface Builder est normal, mais celui d'une personne ajoutée de manière programmatique a été modifié. viewDidAppear ni l'un ni l'autre viewWillAppear(_:) ni viewWillDisappear .

0 votes

Une autre chose étrange est que l'embarqué ViewController 's viewDidAppear est appelé dans la section viewDidLoad au lieu de le faire pendant la période où son parent viewDidAppear

0 votes

@DawnSong - "mais celui qui est ajouté de manière programmatique a viewDidAppear [mais] ni l'un ni l'autre viewWillAppear(_:) ni viewWillDisappear ". Le site will Les méthodes d'apparition sont appelées correctement dans les deux scénarios. L'un doit appeler didMove(toParentViewController:_) lorsque vous le faites par programme, sinon ils ne le feront pas. En ce qui concerne la synchronisation des méthodes d'apparition, elles sont appelées dans la même séquence dans les deux cas. Ce qui diffère, cependant, c'est le moment où la méthode viewDidLoad car avec l'embed, il est chargé avant parent.viewDidLoad mais avec le programmatique, comme on peut s'y attendre, cela se produit pendant parent.viewLoadLoad .

29voto

Bright Future Points 2507

La réponse de @Rob dans Swift 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)

19voto

Jeffrey Chen Points 529

Voici mon code en swift 3, fonctionne aussi en swift 4.

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

        if let previous = previous {
            removeFromParent(vc: previous)
        }
        child.willMove(toParentViewController: parent)
        parent.addChildViewController(child)
        container.addSubview(child.view)
        child.didMove(toParentViewController: 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(toParentViewController: nil)
        vc.view.removeFromSuperview()
        vc.removeFromParentViewController()
    }

    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.childViewControllers.first
        )
        completion?(vc)
    }
}

Utilisation

@IBOutlet weak var container:UIView!

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

Utilisez l'autre fonction d'intégration avec le contrôleur de vue non-storyboard.

2 votes

Superbe classe, mais je me retrouve à devoir intégrer 2 viewControllers dans le même contrôleur de vue principal, ce que votre removeFromParent l'appel empêche, comment modifieriez-vous votre classe pour le permettre ?

0 votes

Brillant :) Merci.

0 votes

C'est un bel exemple, mais comment puis-je y ajouter des animations de transition (emboîtement, remplacement des contrôleurs de vue enfants) ?

13voto

Vasily Bodnarchuk Points 8047

Détails

xCode : 9.1, Swift 4

Solution

class EmbedController {

    public private(set) weak var rootViewController: UIViewController?

    public private(set) var controllers = [UIViewController]()

    init (rootViewController: UIViewController) {
        self.rootViewController = rootViewController
    }

    func append(viewController: UIViewController) {
        if let rootViewController = self.rootViewController {
            controllers.append(viewController)
            rootViewController.addChildViewController(viewController)
            rootViewController.view.addSubview(viewController.view)
        }
    }

    deinit {
        if self.rootViewController != nil {
            for controller in controllers {
                controller.view.removeFromSuperview()
                controller.removeFromParentViewController()
            }
            controllers.removeAll()
            self.rootViewController = nil
        }
    }
}

Utilisation

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)
    }
}

Echantillon complet

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

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)")
    }
}

Résultats

enter image description here enter image description here enter image description here

1 votes

J'ai utilisé ce code pour ajouter tableViewController dans un viewController mais ne peut pas fixer le titre du premier. Je ne sais pas s'il est possible de le faire. J'ai posté cette question . C'est gentil de votre part d'y jeter un coup d'œil.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X