Accedi a Container View Controller da Parent iOS


203

in iOS6 ho notato la nuova vista contenitore, ma non sono sicuro di come accedere al suo controller dalla vista contenitore.

Scenario:

esempio

Voglio accedere alle etichette nel controller della vista di avviso dal controller della vista che ospita la vista del contenitore.

C'è un seguito tra loro, posso usarlo?


completamente spiegato qui, per le moderne vedute container: stackoverflow.com/a/23403979/294884
Fattie

Risposte:


362

Sì, è possibile utilizzare il comando seguente per accedere al controller di visualizzazione figlio (e alla sua vista e visualizzazioni secondarie). Assegna al segui un identificatore (comealertview_embed ), usando l'ispettore Attributi in Storyboard. Quindi chiedi al controller della vista padre (quello che ospita la vista contenitore) di implementare un metodo come questo:

- (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...
   }
}

1
non stiamo seguendo? mi sto perdendo qualcosa qui ...?
Adam Waite,

25
sì, c'è un embed segue che si verifica quando il controller della seconda vista diventa figlio del controller della prima vista. preparForSegue: viene chiamato poco prima che ciò accada. puoi sfruttare questa opportunità per trasmettere dati al bambino o per memorizzare un riferimento al bambino per un uso successivo. vedi anche developer.apple.com/library/ios/#documentation/uikit/reference/…
Peter E

1
Ah giusto, il "controller della seconda vista è diventato figlio del controller della prima vista" quando la vista si carica? Questo ha più senso ora, grazie. Non sto con il mio progetto ora, ma testerò più tardi
Adam Waite,

1
esattamente, viene chiamato prima di viewDidLoad. Quando viene raggiunto viewDidLoad, il genitore e il figlio sono stati connessi e [self childViewControllers] nel genitore restituirà un array di tutti i controllori figlio (vedere la risposta di rdelmar di seguito).
Peter E

2
Vorrei aggiungere un avvertimento alla soluzione proposta: fare molta attenzione quando si accede alla proprietà della vista del controller della vista di destinazione (figlio): in alcune circostanze ciò causerà la sua visualizzazione in quel punto di viewDidLoad e quindi. Raccomanderei di impostare in anticipo tutti i dati necessari in modo che viewDidLoad possa sparare in sicurezza.
Impara sempre il

56

Puoi farlo semplicemente con self.childViewControllers.lastObject(supponendo che tu abbia solo un figlio, altrimenti usa objectAtIndex:).


1
@RaphaelOliveira, non necessariamente. Se hai più childController in una singola vista, QUESTO sarebbe l'approccio preferito. Ti consente di coordinare più contenitori contemporaneamente. preparazioneForSegue ha solo riferimento all'istanza del controller figlio singolo su cui agisce.
Fydo,

2
@Fydo, e qual è il problema con la gestione di tutti i contenitori multipli nel 'preparare per segue'?
Lay González,

1
E se (orrori!) Decidessi di passare dallo storyboard o di non usare sequenze, ecc. Quindi devi scavare il codice per apportare modifiche, ecc.
Tom Andersen,

2
Questo è il mio solito approccio, ma ora si blocca da quando childViewControllers
accedo

25

per la programmazione rapida

puoi scrivere così

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

A che serve il punto interrogativo dopo segueName sull'istruzione if? "se segueName?"
il reverendo

19

Il prepareForSegue approccio funziona, ma si basa sulla stringa magica dell'identificatore seguente. Forse c'è un modo migliore.

Se conosci la classe del VC che stai cercando, puoi farlo molto bene con una proprietà calcolata:

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

Questo si basa childViewControllers. Mentre sono d'accordo, potrebbe essere fragile fare affidamento sul primo, nominare la classe che cerchi fa sembrare abbastanza solido.


3
return childViewControllers.filter { $0 is CamperVanViewController }.firstin a one liner
Adam Waite,

1
Da allora ho fatto childViewControllers.flatMap({ $0 as? CamperVanViewController }).firstche penso sia un po 'meglio, dal momento che si lancia e si sbarazza di tutti gli zero.
Semplicamente

Questa è davvero una buona soluzione se si desidera accedere a quel controller di visualizzazione più di una volta
Gabriel Goncalves,

questo è senza speranza - non c'è un motivo particolare per cui potresti avere solo una di quella particolare classe. questo è esattamente il motivo per cui esistono identificatori. basta seguire la formula standard ... stackoverflow.com/a/23403979/294884
Fattie

non filtrare solo per prendere il primo elemento. basta usare first(where:). childViewControllers.first(where: { $0 is CamperVanViewController })
Alexander - Ripristina Monica il

9

Una risposta aggiornata per Swift 3, utilizzando una proprietà calcolata:

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

Questo scorre solo l'elenco dei bambini fino a quando non raggiunge la prima corrispondenza.


8

self.childViewControllers è più rilevante quando è necessario il controllo da parte del genitore. Ad esempio, se il controller figlio è una vista tabella e si desidera ricaricarlo forzatamente o modificare una proprietà tramite un tocco di pulsante o qualsiasi altro evento su Controller vista padre, è possibile farlo accedendo all'istanza di ChildViewController e non tramite preparForSegue. Entrambi hanno le loro applicazioni in diversi modi.


2

Esiste un altro modo che utilizza l'istruzione switch di Swift sul tipo di controller di visualizzazione:

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

1

Uso il codice come:

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

1

Nel caso qualcuno stia cercando Swift 3.0 ,

viewController1 , viewController2 e così via saranno quindi accessibili.

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

1

Con il generico puoi fare alcune cose dolci. Ecco un'estensione di Array:

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

È quindi possibile farlo nel viewController:

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

0

puoi scrivere così

- (IBAction)showDetail:(UIButton *)sender {  
            DetailViewController *detailVc = [self.childViewControllers firstObject];  
        detailVc.lable.text = sender.titleLabel.text;  
    }  
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.