Accéder à Container View Controller à partir du parent iOS

203

dans iOS6, j'ai remarqué la nouvelle vue conteneur, mais je ne sais pas trop comment accéder à son contrôleur à partir de la vue conteneur.

Scénario:

exemple

Je souhaite accéder aux étiquettes du contrôleur de vue Alert depuis le contrôleur de vue qui héberge la vue conteneur.

Il y a une transition entre eux, puis-je l'utiliser?

Adam Waite
la source
entièrement expliqué ici, pour les vues de conteneurs modernes: stackoverflow.com/a/23403979/294884
Fattie

Réponses:

362

Oui, vous pouvez utiliser la séquence pour accéder au contrôleur de vue enfant (et à sa vue et ses sous-vues). Donnez à la séquence un identifiant (tel que alertview_embed), à l'aide de l'inspecteur Attributs dans Storyboard. Demandez ensuite au contrôleur de vue parent (celui qui héberge la vue conteneur) d'implémenter une méthode comme celle-ci:

- (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
la source
1
on ne segue pas? Est-ce que j'ai râté quelque chose...?
Adam Waite
25
oui, il y a une séquence d'intégration qui se produit lorsque le deuxième contrôleur de vue devient un enfant du premier contrôleur de vue. prepareForSegue: est appelé juste avant que cela ne se produise. vous pouvez utiliser cette opportunité pour transmettre des données à l'enfant ou pour stocker une référence à l'enfant pour une utilisation ultérieure. voir aussi developer.apple.com/library/ios/#documentation/uikit/reference/…
Peter E
1
Ah oui, est-ce que «le deuxième contrôleur de vue est devenu un enfant du premier contrôleur de vue» lorsque la vue se charge? Cela a plus de sens maintenant, merci. Je ne suis pas avec mon projet maintenant mais je testerai plus tard
Adam Waite
1
exactement, il est appelé avant viewDidLoad. Au moment où viewDidLoad est atteint, le parent et l'enfant ont été connectés et [self childViewControllers] dans le parent renverra un tableau de tous les contrôleurs enfants (voir la réponse de rdelmar ci-dessous).
Peter E
2
J'ajouterais une mise en garde à la solution proposée: soyez très prudent lorsque vous accédez à la propriété de vue du contrôleur de vue de destination (enfant): dans certaines circonstances, cela entraînera son appel à viewDidLoad, puis je recommanderais de configurer au préalable toutes les données de séquence nécessaires. afin que le viewDidLoad puisse se déclencher en toute sécurité.
AlwaysLearning
56

Vous pouvez le faire simplement avec self.childViewControllers.lastObject(en supposant que vous n'avez qu'un seul enfant, sinon utilisez objectAtIndex:).

rdelmar
la source
1
@RaphaelOliveira, pas nécessairement. Si vous avez plusieurs childControllers dans une seule vue, CECI serait l'approche préférée. Il vous permet de coordonner plusieurs conteneurs à la fois. prepareForSegue ne fait référence qu'à l'instance de contrôleur enfant unique sur laquelle il agit.
Fydo
2
@Fydo, et quel est le problème avec la gestion de tous les conteneurs multiples sur le "se préparer pour segue"?
Lay González
1
Que se passe-t-il si (horreurs!) Vous décidez de passer du storyboard ou de ne pas utiliser de seques, etc. Ensuite, vous devez rechercher le code, apporter des modifications, etc.
Tom Andersen
2
C'est mon approche habituelle, mais elle se bloque maintenant pour moi car childViewControllers
j'accède
25

pour la programmation rapide

tu peux écrire comme ça

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
la source
À quoi sert le point d'interrogation après segueName sur l'instruction if? "si segueName?"
le révérend
19

L' prepareForSegueapproche fonctionne, mais elle repose sur la chaîne magique de l'identificateur de séquence. Il y a peut-être une meilleure façon.

Si vous connaissez la classe du VC que vous recherchez, vous pouvez le faire très soigneusement avec une propriété calculée:

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

Cela dépend childViewControllers. Bien que je convienne qu'il pourrait être fragile de s'appuyer sur le premier, nommer la classe que vous recherchez rend cela assez solide.

SimplGy
la source
3
return childViewControllers.filter { $0 is CamperVanViewController }.firstdans une seule ligne
Adam Waite
1
J'ai depuis fait childViewControllers.flatMap({ $0 as? CamperVanViewController }).firstce que je pense être un peu mieux, car il jette et élimine tous les nils.
SimplGy
C'est une très bonne solution si vous souhaitez accéder à ce contrôleur de vue plusieurs fois
Gabriel Goncalves
c'est sans espoir - il n'y a aucune raison particulière pour laquelle vous ne pouvez avoir qu'un seul de cette classe particulière. c'est exactement pourquoi les identifiants existent. il suffit de suivre la formule standard ... stackoverflow.com/a/23403979/294884
Fattie
ne filtrez pas uniquement pour prendre le premier élément. il suffit d'utiliser first(where:). childViewControllers.first(where: { $0 is CamperVanViewController })
Alexander - Reinstate Monica
9

Une réponse mise à jour pour Swift 3, en utilisant une propriété calculée:

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

Cela ne fait qu'itérer la liste des enfants jusqu'à ce qu'elle atteigne la première correspondance.

Robin Daugherty
la source
8

self.childViewControllers est plus pertinent lorsque vous avez besoin du contrôle du parent. Par exemple, si le contrôleur enfant est une vue de table et que vous souhaitez le recharger de force ou modifier une propriété via un appui sur un bouton ou tout autre événement sur le contrôleur de vue parent, vous pouvez le faire en accédant à l'instance de ChildViewController et non via prepareForSegue. Les deux ont leurs applications de différentes manières.

Gautam Jain
la source
2

Il existe une autre façon d'utiliser l'instruction switch de Swift sur le type du contrôleur de vue:

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
la source
1

J'utilise du code comme:

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

Si quelqu'un cherche Swift 3.0 ,

viewController1 , viewController2 et ainsi de suite seront alors accessibles.

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
la source
1

Avec générique, vous pouvez faire des choses douces. Voici une extension d'Array:

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

Vous pouvez ensuite le faire dans votre viewController:

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

tu peux écrire comme ça

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