Acessar o Container View Controller no iOS pai

203

no iOS6, notei a nova exibição de contêiner, mas não sei ao certo como acessar seu controlador a partir da exibição que o contém.

Cenário:

exemplo

Desejo acessar os rótulos no controlador de exibição de alerta no controlador de exibição que abriga a exibição de contêiner.

Há uma sequência entre eles, posso usar isso?

Adam Waite
fonte
totalmente explicado aqui, para visualizações modernas de contêineres: stackoverflow.com/a/23403979/294884
Fattie

Respostas:

362

Sim, você pode usar as instruções para acessar o controlador de exibição filho (e suas visualizações e subvisões). Atribua um identificador a segue (como alertview_embed), usando o inspetor de Atributos no Storyboard. Em seguida, faça com que o controlador de visualização pai (aquele que abriga a visualização de contêiner) implemente um 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
fonte
1
nós não estamos seguindo? estou faltando alguma coisa aqui ...?
Adam Waite
25
Sim, existe uma incorporação que ocorre quando o segundo controlador de exibição é filho do primeiro controlador de exibição. prepareForSegue: é chamado logo antes que isso aconteça. você pode usar esta oportunidade para passar dados para a criança ou para armazenar uma referência à criança para uso posterior. veja também developer.apple.com/library/ios/#documentation/uikit/reference/…
Peter E
1
Ah, certo, o 'segundo controlador de exibição é feito filho do primeiro controlador de exibição' quando a exibição é carregada? Isso está fazendo mais sentido agora, obrigado. Eu não estou com meu projeto agora, mas irá testar mais tarde
Adam Waite
1
exatamente, é chamado antes de viewDidLoad. Quando viewDidLoad é alcançado, o pai e o filho foram conectados e [self childViewControllers] no pai retornará uma matriz de todos os controladores filhos (consulte a resposta de rdelmar abaixo).
Peter E
2
Gostaria de acrescentar uma ressalva à solução proposta: tenha muito cuidado ao acessar a propriedade de exibição do controlador de exibição de destino (filho): em algumas circunstâncias, isso fará com que seu viewDidLoad seja chamado lá e então. para que o viewDidLoad possa disparar com segurança.
AlwaysLearning 12/12/12
56

Você pode fazer isso simplesmente com self.childViewControllers.lastObject(supondo que você tenha apenas um filho, caso contrário, use objectAtIndex:).

rdelmar
fonte
1
@RaphaelOliveira, não necessariamente. Se você tiver vários childControllers em uma única exibição, esta seria a abordagem preferida. Permite coordenar vários contêineres de uma só vez. prepareForSegue tem apenas referência à instância do controlador filho único em que está atuando.
Fydo
2
@Fydo, e qual é o problema de lidar com todos os vários contêineres no 'preparar para segue'?
Lay González
1
E se (! Horrores) você decidir mudar de storyboard ou não usar seques, etc. Então você tem que cavar código fazer alterações, etc.
Tom Andersen
2
Esta é a minha abordagem usual, mas agora ela trava para mim, já que estou acessando o childViewControllers"muito cedo" #
Mazyod 4/16
25

para programação rápida

você pode escrever assim

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
fonte
Qual é a utilidade do ponto de interrogação após segueName na instrução if? "se segueName?"
o reverendo
19

A prepareForSegueabordagem funciona, mas depende da sequência mágica do identificador segue. Talvez haja uma maneira melhor.

Se você conhece a classe do VC que procura, pode fazer isso muito bem com uma propriedade computada:

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

Isso depende childViewControllers. Embora eu concorde que poderia ser frágil confiar na primeira, nomear a classe que você procura faz com que isso pareça bastante sólido.

SimplGy
fonte
3
return childViewControllers.filter { $0 is CamperVanViewController }.firstem um único forro
Adam Waite
1
Eu já fiz o childViewControllers.flatMap({ $0 as? CamperVanViewController }).firstque acho um pouco melhor, pois lança e se livra de nada.
SimplGy
Esta é uma solução muito boa se você quiser acesso que controlador de vista mais de uma vez
Gabriel Goncalves
isso é inútil - não há motivo específico para você ter apenas uma dessa classe em particular. é exatamente por isso que existem identificadores. basta seguir a fórmula padrão ... stackoverflow.com/a/23403979/294884
Fattie
não filtre apenas para pegar o primeiro elemento. apenas use first(where:). childViewControllers.first(where: { $0 is CamperVanViewController })
Alexander - Reinstate Monica
9

Uma resposta atualizada para o Swift 3, usando uma propriedade computada:

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

Isso apenas itera a lista de filhos até atingir a primeira correspondência.

Robin Daugherty
fonte
8

self.childViewControllers é mais relevante quando você precisa de controle dos pais. Por exemplo, se o controlador filho for uma exibição de tabela e você desejar recarregá-lo com força ou alterar uma propriedade por meio de um toque no botão ou qualquer outro evento no Parent View Controller, você poderá fazê-lo acessando a instância do ChildViewController e não através do prepareForSegue. Ambos têm suas aplicações de maneiras diferentes.

Gautam Jain
fonte
2

Existe outra maneira de usar a instrução switch de Swift no tipo de controlador de exibição:

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
fonte
1

Eu uso o código como:

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

Caso alguém esteja procurando pelo Swift 3.0 ,

viewController1 , viewController2 e assim por diante estarão acessíveis.

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
fonte
1

Com genérico você pode fazer algumas coisas doces. Aqui está uma extensão do Array:

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

Você pode fazer isso no seu viewController:

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

você pode escrever assim

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