ios - tesis - Cómo agregar una vista de contenedor mediante programación
tesis sobre youtube pdf (4)
Detalles
- Xcode 10.2 (10E125), Swift 5
Solución
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()
}
}
Uso
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)
}
}
Muestra completa
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)")
}
}
Resultados
Una vista de contenedor se puede agregar fácilmente a un guión gráfico a través del editor de interfaz. Cuando se agrega, una vista de contenedor es una vista de marcador de posición, un segmento de inserción y un controlador de vista (secundario).
Sin embargo, no puedo encontrar una manera de agregar una vista de contenedor mediante programación.
En realidad, ni siquiera puedo encontrar una clase llamada
UIContainerView
o menos.
Un nombre para la clase de Vista de contenedor es seguramente un buen comienzo. Una guía completa que incluye el segue será muy apreciada.
Conozco la Guía de programación de View Controller, pero no la considero igual que Interface Builder para Container Viewer. Por ejemplo, cuando las restricciones se establecen correctamente, la vista (secundaria) se adapta a los cambios de tamaño en la Vista de contenedor.
Aquí está mi código en swift 3, también funciona 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)
}
}
Uso
@IBOutlet weak var container:UIView!
ViewEmbedder.embed(
withIdentifier: "MyVC", // Storyboard ID
parent: self,
container: self.container){ vc in
// do things when embed complete
}
Use la otra función de incrustación con un controlador de vista sin guión gráfico.
La respuesta de @ Rob en 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)
Una "vista de contenedor" del guión gráfico es solo un objeto estándar de
UIView
.
No hay un tipo especial de "vista de contenedor".
De hecho, si observa la jerarquía de vistas, puede ver que la "vista de contenedor" es una vista de
UIView
estándar:
Para lograr esto mediante programación, usted emplea "contención del controlador de vista":
-
Cree una instancia del controlador de vista secundario llamando a
instantiateViewController(withIdentifier:)
en el objeto del guión gráfico. -
Llame a
addChild
en su controlador de vista principal. -
Agregue la vista del controlador de
view
a su jerarquía de vistas conaddSubview
(y también configure elframe
o las restricciones según corresponda). -
Llame al
didMove(toParent:)
en el controlador de vista secundario, pasando la referencia al controlador de vista primario.
Consulte Implementación de un controlador de vista de contenedor en la Guía de programación del controlador de vista y la sección "Implementación de un controlador de vista de contenedor" de la Referencia de clase UIViewController .
Por ejemplo, en Swift 4.2 podría verse así:
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)
}
Tenga en cuenta que lo anterior en realidad no agrega una "vista de contenedor" a la jerarquía. Si quieres hacer eso, harías algo como:
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)
}
Este último patrón es extremadamente útil si alguna vez hace la transición entre diferentes controladores de vista secundarios y solo desea asegurarse de que la vista de un niño esté en la misma ubicación y la vista del niño anterior (es decir, todas las restricciones únicas para la ubicación están dictadas por la vista del contenedor, en lugar de necesitar reconstruir estas restricciones cada vez). Pero si solo realiza una contención de vista simple, la necesidad de esta vista de contenedor separada es menos convincente.
En los ejemplos anteriores, estoy configurando
translatesAutosizingMaskIntoConstraints
a
false
definiendo las restricciones yo mismo.
Obviamente, puede dejar las
translatesAutosizingMaskIntoConstraints
como
true
y establecer tanto el
frame
como la
autosizingMask
para las vistas que agregue, si lo prefiere.
Consulte las revisiones anteriores de esta respuesta para las versiones de Swift 3 y Swift 2 .