Controlador de vista de contenedor de acceso desde iOS principal

203

en iOS6 noté la nueva Vista de contenedor, pero no estoy muy seguro de cómo acceder a su controlador desde la vista de contenedor.

Guión:

ejemplo

Quiero acceder a las etiquetas en el controlador de vista de alerta desde el controlador de vista que aloja la vista de contenedor.

Hay una separación entre ellos, ¿puedo usar eso?

Adam Waite
fuente
completamente explicado aquí, para vistas modernas de contenedores: stackoverflow.com/a/23403979/294884
Fattie

Respuestas:

362

Sí, puede usar el segue para obtener acceso al controlador de vista secundario (y su vista y subvistas). Dele al segue un identificador (como alertview_embed), utilizando el inspector de atributos en Storyboard. Luego haga que el controlador de vista principal (el que aloja la vista de contenedor) implemente un método como este:

- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
   NSString * segueName = segue.identifier;
   if ([segueName isEqualToString: @"alertview_embed"]) {
       AlertViewController * childViewController = (AlertViewController *) [segue destinationViewController];
       AlertView * alertView = childViewController.view;
       // do something with the AlertView's subviews here...
   }
}
Peter E
fuente
1
no estamos segueing? Me estoy perdiendo de algo...?
Adam Waite el
25
sí, hay una secuencia de inserción que ocurre cuando el segundo controlador de vista se convierte en hijo del primer controlador de vista. prepareForSegue: se llama justo antes de que esto suceda. puede usar esta oportunidad para pasar datos al niño o para almacenar una referencia al niño para su uso posterior. ver también developer.apple.com/library/ios/#documentation/uikit/reference/…
Peter E
1
Ah cierto, ¿el 'segundo controlador de vista se convierte en hijo del primer controlador de vista' cuando se carga la vista? Esto tiene más sentido ahora, gracias. No estoy con mi proyecto ahora, pero lo probaré más tarde
Adam Waite
1
exactamente, se llama antes de viewDidLoad. Cuando se alcanza viewDidLoad, el padre y el hijo se han conectado y [self childViewControllers] en el padre devolverá una matriz de todos los controladores hijos (consulte la respuesta de rdelmar a continuación).
Peter E
2
Agregaría una advertencia a la solución propuesta: tenga mucho cuidado al acceder a la propiedad de vista del controlador de vista de destino (secundario): en algunas circunstancias, esto hará que se llame a viewDidLoad allí y luego. Recomendaría configurar de antemano los datos de segue necesarios para que viewDidLoad pueda dispararse de forma segura.
Siempre aprendiendo el
56

Puede hacerlo simplemente con self.childViewControllers.lastObject(suponiendo que solo tenga un hijo, de lo contrario use objectAtIndex:).

rdelmar
fuente
1
@RaphaelOliveira, no necesariamente. Si tiene varios childControllers en una sola vista, ESTE sería el enfoque preferido. Le permite coordinar múltiples contenedores a la vez. prepareForSegue solo tiene referencia a la instancia del controlador secundario único en el que está actuando.
Fydo
2
@Fydo, ¿y cuál es el problema con el manejo de todos los contenedores múltiples en el 'prepare for segue'?
Lay González
1
¿Qué pasa si (horrores) decide cambiar del guión gráfico o no usar secuestros, etc. Luego tiene que desenterrar el código para hacer cambios, etc.
Tom Andersen
2
Este es mi enfoque habitual, pero ahora se bloquea porque estoy accediendo al childViewControllers"demasiado pronto"
Mazyod
25

para programación rápida

puedes escribir asi

var containerViewController: ExampleViewController?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    // you can set this name in 'segue.embed' in storyboard
    if segue.identifier == "checkinPopupIdentifierInStoryBoard" {
        let connectContainerViewController = segue.destinationViewController as ExampleViewController
        containerViewController = connectContainerViewController
    }
}
Sruit A.Suk
fuente
¿De qué sirve el signo de interrogación después de segueName en la instrucción if? "if segueName?"
El Reverendo
19

El prepareForSegueenfoque funciona, pero se basa en la cadena mágica del identificador de segue. Tal vez hay una mejor manera.

Si conoce la clase de VC que busca, puede hacerlo de manera muy clara con una propiedad calculada:

var camperVan: CamperVanViewController? {
  return childViewControllers.flatMap({ $0 as? CamperVanViewController }).first
  // This works because `flatMap` removes nils
}

Esto se basa en childViewControllers. Si bien estoy de acuerdo en que podría ser frágil confiar en el primero, nombrar la clase que busca hace que esto parezca bastante sólido.

SimplGy
fuente
3
return childViewControllers.filter { $0 is CamperVanViewController }.firsten una sola línea
Adam Waite
1
Desde entonces he hecho childViewControllers.flatMap({ $0 as? CamperVanViewController }).firstlo que creo que es un poco mejor, ya que arroja y elimina cualquier nulo.
SimplGy
Esta es una muy buena solución si desea acceder a ese controlador de vista más de una vez
Gabriel Goncalves
esto no tiene remedio: no hay una razón particular por la que pueda tener solo una de esa clase en particular. eso es exactamente por qué existen los identificadores. simplemente siga la fórmula estándar ... stackoverflow.com/a/23403979/294884
Fattie
no filtre solo para tomar el primer elemento. solo usa first(where:). childViewControllers.first(where: { $0 is CamperVanViewController })
Alexander - Restablece a Mónica el
9

Una respuesta actualizada para Swift 3, usando una propiedad calculada:

var jobSummaryViewController: JobSummaryViewController {
    get {
        let ctrl = childViewControllers.first(where: { $0 is JobSummaryViewController })
        return ctrl as! JobSummaryViewController
    }
}

Esto solo itera la lista de hijos hasta que alcanza la primera coincidencia.

Robin Daugherty
fuente
8

self.childViewControllers es más relevante cuando necesita control del padre. Por ejemplo, si el controlador secundario es una vista de tabla y desea volver a cargarlo con fuerza o cambiar una propiedad mediante un toque de botón o cualquier otro evento en el Controlador de vista principal, puede hacerlo accediendo a la instancia de ChildViewController y no a través de prepareForSegue. Ambos tienen sus aplicaciones de diferentes maneras.

Gautam Jain
fuente
2

Hay otra forma de usar la declaración de cambio de Swift en el tipo de controlador de vista:

override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
  switch segue.destination
  {
    case let aViewController as AViewController:
      self.aViewController = aViewController
    case let bViewController as BViewController:
      self.bViewController = bViewController
    default:
      return
  }
}
Joanna Carter
fuente
1

Yo uso el código como:

- (IBAction)showCartItems:(id)sender{ 
  ListOfCartItemsViewController *listOfItemsVC=[self.storyboard instantiateViewControllerWithIdentifier:@"ListOfCartItemsViewController"];
  [self addChildViewController:listOfItemsVC];
 }
Mannam Brahmam
fuente
1

En caso de que alguien esté buscando Swift 3.0 ,

viewController1 , viewController2 y así sucesivamente serán accesibles.

let viewController1 : OneViewController!
let viewController2 : TwoViewController!

// Safety handling of optional String
if let identifier: String = segue.identifier {

    switch identifier {

    case "segueName1":
        viewController1 = segue.destination as! OneViewController
        break

    case "segueName2":
        viewController2 = segue.destination as! TwoViewController
        break

    // ... More cases can be inserted here ...

    default:
        // A new segue is added in the storyboard but not yet including in this switch
        print("A case missing for segue identifier: \(identifier)")
        break
    }

} else {
    // Either the segue or the identifier is inaccessible 
    print("WARNING: identifier in segue is not accessible")
}
Marco Leong
fuente
1

Con genérico puedes hacer algunas cosas dulces. Aquí hay una extensión de Array:

extension Array {
    func firstMatchingType<Type>() -> Type? {
        return first(where: { $0 is Type }) as? Type
    }
}

Luego puede hacer esto en su viewController:

var viewControllerInContainer: YourViewControllerClass? {
    return childViewControllers.firstMatchingType()!
}
Sunkas
fuente
0

puedes escribir asi

- (IBAction)showDetail:(UIButton *)sender {  
            DetailViewController *detailVc = [self.childViewControllers firstObject];  
        detailVc.lable.text = sender.titleLabel.text;  
    }  
}
Khurshid
fuente