Uzyskaj dostęp do kontrolera widoku kontenera z nadrzędnego systemu iOS

203

w iOS6 zauważyłem nowy widok kontenera, ale nie jestem pewien, jak uzyskać dostęp do jego kontrolera z widoku zawierającego.

Scenariusz:

przykład

Chcę uzyskać dostęp do etykiet w kontrolerze widoku Alert z kontrolera widoku, w którym znajduje się widok kontenera.

Jest między nimi podział, czy mogę tego użyć?

Adam Waite
źródło
w pełni wyjaśnione tutaj, dla nowoczesnych widoków kontenerów: stackoverflow.com/a/23403979/294884
Fattie

Odpowiedzi:

362

Tak, możesz użyć segue, aby uzyskać dostęp do kontrolera widoku potomnego (oraz jego widoku i widoków podrzędnych). Nadaj segue identyfikator (np. alertview_embed), Używając Inspektora atrybutów w Storyboard. Następnie poproś kontroler widoku nadrzędnego (ten zawierający widok kontenera) zaimplementuj następującą metodę:

- (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.
źródło
1
nie segregujemy się? Czy coś mi umyka...?
Adam Waite,
25
tak, istnieje segment osadzania, który występuje, gdy drugi kontroler widoku staje się dzieckiem pierwszego kontrolera widoku. preparForSegue: jest wywoływany tuż przed tym. możesz skorzystać z tej okazji, aby przekazać dane dziecku lub zapisać odniesienie do dziecka do późniejszego wykorzystania. patrz także developer.apple.com/library/ios/#documentation/uikit/reference/…
Peter E
1
Ach tak, czy „drugi kontroler widoku staje się dzieckiem pierwszego kontrolera widoku”, gdy ładuje się widok? Dzięki temu ma to większy sens. Nie jestem teraz z moim projektem, ale przetestuję później
Adam Waite
1
dokładnie nazywa się to przed viewDidLoad. Do czasu osiągnięcia viewDidLoad rodzic i dziecko zostały połączone, a [self childViewControllers] w rodzicu zwróci tablicę wszystkich kontrolerów potomnych (patrz odpowiedź rdelmar poniżej).
Peter E,
2
Dodałbym jedno zastrzeżenie do proponowanego rozwiązania: bądź bardzo ostrożny podczas uzyskiwania dostępu do właściwości widoku kontrolera widoku docelowego (potomnego): w niektórych okolicznościach spowoduje to wywołanie jego viewDidLoad tam i wtedy. Zalecam wcześniejsze skonfigurowanie wszelkich potrzebnych danych segregowanych dzięki czemu viewDidLoad może bezpiecznie strzelać.
AlwaysLearning
56

Możesz to zrobić po prostu self.childViewControllers.lastObject(zakładając, że masz tylko jedno dziecko, w przeciwnym razie użyj objectAtIndex:).

rdelmar
źródło
1
@RaphaelOliveira, niekoniecznie. Jeśli masz wiele kontrolerów podrzędnych w jednym widoku, będzie to preferowane podejście. Pozwala koordynować wiele pojemników jednocześnie. PreparForSegue ma odniesienie tylko do pojedynczej instancji kontrolera podrzędnego, na której działa.
Fydo
2
@Fydo i na czym polega problem z obsługą wszystkich wielu pojemników w polu „Przygotuj się na segue”?
Lay González
1
Co jeśli (okropności!) Zdecydujesz się przełączyć z storyboardu lub nie będziesz używać sekwetów itp. Następnie musisz wykopać kod i wprowadzić zmiany itp.
Tom Andersen
2
To jest moje zwykłe podejście, ale teraz zawiesza mnie, ponieważ childViewControllers
uzyskuję
24

do szybkiego programowania

możesz tak pisać

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
źródło
Jakie jest zastosowanie znaku zapytania po segueName w instrukcji if? „jeśli segueName?”
Wielebny
18

prepareForSeguePodejście działa, ale opiera się na magicznej ciąg identyfikatora segue. Może jest lepszy sposób.

Jeśli znasz klasę VC, której szukasz, możesz to zrobić bardzo starannie za pomocą obliczonej właściwości:

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

To zależy od childViewControllers. Chociaż zgadzam się, że oparcie się na pierwszym może być kruche, nazwanie poszukiwanej klasy sprawia, że ​​wydaje się to dość solidne.

Prostota
źródło
3
return childViewControllers.filter { $0 is CamperVanViewController }.firstw jednej linijce
Adam Waite
1
Od tego czasu zrobiłem to, childViewControllers.flatMap({ $0 as? CamperVanViewController }).firstco moim zdaniem jest trochę lepsze, ponieważ rzuca i pozbywa się zera.
SimplGy
To naprawdę dobre rozwiązanie, jeśli chcesz uzyskać dostęp do tego kontrolera więcej niż raz
Gabriel Goncalves
to jest beznadziejne - nie ma konkretnego powodu, dla którego możesz mieć tylko jedną z tych konkretnych klas. właśnie dlatego istnieją identyfikatory. postępuj zgodnie ze standardową formułą ... stackoverflow.com/a/23403979/294884
Fattie
nie filtruj tylko po to, by wziąć pierwszy element. po prostu użyj first(where:). childViewControllers.first(where: { $0 is CamperVanViewController })
Alexander - Przywróć Monikę
9

Zaktualizowana odpowiedź dla Swift 3, wykorzystująca obliczoną właściwość:

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

Spowoduje to tylko iterację listy dzieci, aż do osiągnięcia pierwszego dopasowania.

Robin Daugherty
źródło
8

self.childViewControllers jest bardziej odpowiedni, gdy potrzebujesz kontroli od rodzica. Na przykład, jeśli kontroler podrzędny jest widokiem tabeli i chcesz go przeładować w sposób wymuszony lub zmienić właściwość za pomocą przycisku przycisku lub dowolnego innego zdarzenia w kontrolerze nadrzędnym, możesz to zrobić, uzyskując dostęp do instancji ChildViewController, a nie za pośrednictwem preparForSegue. Oba mają swoje zastosowania na różne sposoby.

Gautam Jain
źródło
2

Istnieje inny sposób użycia instrukcji switch Swift na typ kontrolera widoku:

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
źródło
1

Używam Code jak:

- (IBAction)showCartItems:(id)sender{ 
  ListOfCartItemsViewController *listOfItemsVC=[self.storyboard instantiateViewControllerWithIdentifier:@"ListOfCartItemsViewController"];
  [self addChildViewController:listOfItemsVC];
 }
Mannam Brahmam
źródło
1

Jeśli ktoś szuka Swift 3.0 ,

viewController1 , viewController2 itd. będą wówczas dostępne.

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
źródło
1

Za pomocą generic możesz robić słodkie rzeczy. Oto rozszerzenie Array:

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

Następnie możesz to zrobić w swoim viewController:

var viewControllerInContainer: YourViewControllerClass? {
    return childViewControllers.firstMatchingType()!
}
Sunkas
źródło
0

możesz tak pisać

- (IBAction)showDetail:(UIButton *)sender {  
            DetailViewController *detailVc = [self.childViewControllers firstObject];  
        detailVc.lable.text = sender.titleLabel.text;  
    }  
}
Khurshid
źródło