Une vue conteneur peut être facilement ajoutée à un storyboard via Interface Editor. Une fois ajoutée, une vue conteneur contient une vue d'espace réservé, une séquence incorporée et un contrôleur de vue (enfant).
Cependant, je ne peux pas trouver un moyen d'ajouter une vue de conteneur par programme. En fait, je ne suis même pas capable de trouver une classe nommée UIContainerView
ou plus.
Un nom pour la classe de vue de conteneur est sûrement un bon début. Un guide complet incluant le segue sera très apprécié.
Je connais le Guide de programmation de View Controller, mais je ne le considère pas comme le même qu'Interface Builder pour Container Viewer. Par exemple, lorsque les contraintes sont correctement définies, la vue (enfant) s'adapte aux changements de taille dans la vue conteneur.
Une "vue de conteneur" de storyboard est simplement un objet UIView
standard. Il n'y a pas de type spécial "vue conteneur". En fait, si vous regardez la hiérarchie des vues, vous pouvez voir que la "vue du conteneur" est un UIView
:
Pour ce faire par programme, vous utilisez "voir le confinement du contrôleur":
instantiateViewController(withIdentifier:)
sur l'objet Storyboard.addChild
dans votre contrôleur de vue parent.view
du contrôleur de vues à votre hiérarchie de vues avec addSubview
(et définissez également le paramètre frame
ou les contraintes appropriées).didMove(toParent:)
sur le contrôleur de vue enfant en transmettant la référence au contrôleur de vue parent.Voir Implémentation d'un contrôleur de vue par conteneur dans le Guide de programmation de View Controller et la section "Implémentation d'un contrôleur de vue par conteneur" du Référence de la classe UIViewController .
Par exemple, dans Swift 4.2, cela pourrait ressembler à:
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)
}
Notez que ce qui précède n’ajoute pas réellement une "vue conteneur" à la hiérarchie. Si vous voulez faire cela, vous feriez 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")
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)
}
Ce dernier modèle est extrêmement utile en cas de transition entre différents contrôleurs de vue enfant et vous voulez simplement vous assurer que la vue d’un enfant se trouve au même emplacement et 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 ne faites que confiner une vue simple, la nécessité de cette vue conteneur séparée est moins convaincante.
Dans les exemples ci-dessus, je règle translatesAutosizingMaskIntoConstraints
sur false
en définissant moi-même les contraintes. Vous pouvez évidemment laisser translatesAutosizingMaskIntoConstraints
en tant que true
et définir à la fois les options frame
et autosizingMask
pour les vues que vous ajoutez, si vous préférez.
Voir les révisions précédentes de cette réponse pour Swift et Swift 2 rendus.
@ 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)
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)")
}
}
Voici mon code dans Swift 3, fonctionne également dans 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)
}
}
@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.