O que addChildViewController realmente faz?

102

Estou começando a mergulhar no desenvolvimento do iOS e uma das primeiras coisas que tive que fazer foi implementar um controlador de visualização de contêiner personalizado - vamos chamá-lo SideBarViewController- que troca qual dos vários controladores de visualização filho possíveis mostra, quase exatamente como um Tab Bar Controller padrão . (É basicamente um Controlador de barra de guias, mas com um menu lateral que pode ser ocultado em vez de uma barra de guias.)

De acordo com as instruções na documentação da Apple, eu chamo addChildViewControllersempre que adiciono um ViewController filho ao meu contêiner. Meu código para trocar o controlador de visualização filho atual mostrado pelo SideBarViewControllerparece com este:

- (void)showViewController:(UIViewController *)newViewController {
    UIViewController* oldViewController = [self.childViewControllers 
                                           objectAtIndex:0];
    
    [oldViewController removeFromParentViewController];
    [oldViewController.view removeFromSuperview];
    
    newViewController.view.frame = CGRectMake(
        0, 0, self.view.frame.size.width, self.view.frame.size.height
    );
    [self addChildViewController: newViewController];
    [self.view addSubview: newViewController.view];
}

Então comecei a tentar descobrir o que addChildViewControllersignifica aqui, e percebi que não tenho ideia. Além de colar o novo ViewControllerno .childViewControllersarray, parece não ter efeito em nada. Ações e saídas da visão do controlador filho para o controlador filho que eu configurei no storyboard ainda funcionam bem, mesmo se eu nunca ligar addChildViewController, e não posso imaginar o que mais isso poderia afetar.

Na verdade, se eu reescrever meu código para não chamar addChildViewControllere ficar assim ...

- (void)showViewController:(UIViewController *)newViewController {

    // Get the current child from a member variable of `SideBarViewController`
    UIViewController* oldViewController = currentChildViewController;

    [oldViewController.view removeFromSuperview];

    newViewController.view.frame = CGRectMake(
        0, 0, self.view.frame.size.width, self.view.frame.size.height
    );
    [self.view addSubview: newViewController.view];

    currentChildViewController = newViewController;
}

... então meu aplicativo ainda funciona perfeitamente, pelo que posso dizer!

A documentação da Apple não esclarece muito o que addChildViewControllersignifica, ou por que devemos chamá-lo. Toda a extensão da descrição relevante do que o método faz ou por que ele deve ser usado em sua seção na UIViewControllerReferência da Classe é, no momento:

Adiciona o controlador de visualização fornecido como filho. ... Este método destina-se apenas a ser chamado por uma implementação de um controlador de visualização de contêiner personalizado. Se você substituir esse método, deverá chamar super em sua implementação.

Há também este parágrafo anterior na mesma página:

Seu controlador de visualização de contêiner deve associar um controlador de visualização filho a si mesmo antes de adicionar a visualização raiz do filho à hierarquia de visualização. Isso permite que o iOS roteie eventos de maneira adequada para os controladores de visualização filho e as visualizações que esses controladores gerenciam. Da mesma forma, depois de remover a visão raiz de um filho de sua hierarquia de visão, ele deve desconectar esse controlador de visão filho de si mesmo. Para fazer ou quebrar essas associações, seu contêiner chama métodos específicos definidos pela classe base. Esses métodos não devem ser chamados por clientes de sua classe de contêiner; eles devem ser usados ​​apenas pela implementação do seu contêiner para fornecer o comportamento de contenção esperado.

Aqui estão os métodos essenciais que você pode precisar chamar:

addChildViewController:
removeFromParentViewController
willMoveToParentViewController:
didMoveToParentViewController:

mas não oferece nenhuma pista sobre quais são os 'eventos' ou 'comportamento de contenção esperado' de que está falando, ou por que (ou mesmo quando) chamar esses métodos é 'essencial'.

Todos os exemplos de controladores de visualização de contêiner personalizados na seção "Controladores de visualização de contêiner personalizados" da documentação da Apple chamam esse método, então presumo que ele atenda a algum propósito importante além de apenas colocar o ViewController filho em um array, mas não consigo imaginar descobrir qual é esse propósito. O que esse método faz e por que devo chamá-lo?

Mark Amery
fonte
3
A página de vídeos WWDC da Apple em 2011 tem uma ótima sessão ("Implementing UIViewController Containment") sobre esse tópico.
Alladinian

Respostas:

94

Eu também estava me perguntando sobre essa questão. Eu assisti Sessão 102 dos WWDC 2011 vídeos e Mr. View Controller, Bruce D. Nilo , disse o seguinte:

viewWillAppear:, viewDidAppear:, Etc não têm nada a ver com addChildViewController:. Tudo o que isso addChildViewController:faz é dizer "Este controlador de visualização é filho daquele" e não tem nada a ver com a aparência da visualização. Quando eles são chamados, está associado a quando as visualizações entram e saem da hierarquia da janela.

Portanto, parece que a chamada para addChildViewController:faz muito pouco. Os efeitos colaterais da ligação são a parte importante. Eles vêm de relacionamentos parentViewControllere childViewControllers. Aqui estão alguns dos efeitos colaterais que conheço:

  • Encaminhando métodos de aparência para controladores de visualização filho
  • Métodos de rotação de encaminhamento
  • (Possivelmente) encaminhando avisos de memória
  • Evitando hierarquias de VC inconsistentes, especialmente transitionFromViewController:toViewController:…onde ambos os VCs precisam ter o mesmo pai
  • Permitir que controladores de visualização de contêineres personalizados participem da preservação e restauração do estado
  • Participar da rede de respostas
  • Ligar as navigationController, tabBarController, etc propriedades
Nevan King
fonte
É a sessão 102, não 101
SeanChense
+1 para a cadeia de resposta. addChildViewController é necessário se você deseja receber eventos de toque em uma subvisualização pertencente a um UIViewController filho
charlieb
108

Acho que um exemplo vale mais que mil palavras.

Eu estava trabalhando em um aplicativo de biblioteca e queria mostrar uma bela visualização do bloco de notas que aparece quando o usuário deseja adicionar uma nota.

insira a descrição da imagem aqui

Depois de tentar algumas soluções, acabei inventando minha própria solução customizada para mostrar o bloco de notas. Portanto, quando desejo mostrar o bloco de notas, crio uma nova instância de NotepadViewControllere adiciono sua visualização raiz como uma subvisualização à visualização principal. Por enquanto, tudo bem.

Então percebi que a imagem do bloco de notas está parcialmente oculta sob o teclado no modo paisagem.

insira a descrição da imagem aqui

Então, eu queria mudar a imagem do bloco de notas e deslocá-la para cima. E para isso, escrevi o código adequado no willAnimateRotationToInterfaceOrientation:duration:método, mas quando executei o aplicativo nada aconteceu! E após a depuração, percebi que nenhum dos UIViewControllermétodos de rotação de é realmente chamado NotepadViewController. Apenas esses métodos no controlador de visualização principal estão sendo chamados.

Para resolver isso, precisei chamar todos os métodos NotepadViewControllermanualmente quando eles são chamados no controlador de visualização principal. Isso logo complicará as coisas e criará uma dependência extra entre componentes não relacionados no aplicativo.

Isso foi no passado, antes de o conceito de controladores de visualizações filho ser introduzido. Mas agora, você só precisa addChildViewControlleracessar o controlador de visualização principal e tudo funcionará como esperado, sem nenhum trabalho manual.

Editar: Existem duas categorias de eventos que são encaminhados para controladores de visualização filho:

1- Métodos de aparência:

- viewWillAppear:
- viewDidAppear:
- viewWillDisappear:
- viewDidDisappear:

2- Métodos de rotação:

- willRotateToInterfaceOrientation:duration:
- willAnimateRotationToInterfaceOrientation:duration:
- didRotateFromInterfaceOrientation:

Você também pode controlar quais categorias de eventos deseja que sejam encaminhadas automaticamente, substituindo shouldAutomaticallyForwardRotationMethodse shouldAutomaticallyForwardAppearanceMethods.

Hejazi
fonte
Pela documentação e depois de fazer um teste rápido acho que não há nenhum outro evento que só seja encaminhado se for addChildViewControllerpara o controlador pai.
Hejazi
desejo que seja encaminhado automaticamente viewWillLayoutSubviews
MobileMon
10

-[UIViewController addChildViewController:]apenas adiciona o controlador de visualização passado em uma matriz de viewControllers que um viewController (o pai) deseja manter a referência. Na verdade, você deve adicionar as visualizações do viewController na tela, adicionando-as como subvisualizações de outra visualização (por exemplo, a visualização do parentViewController). Também existe um objeto de conveniência no Interface Builder para usar childrenViewControllers em Storyboards.

Anteriormente, para manter a referência de outros viewControllers dos quais você usava as visualizações, era necessário manter a referência manual deles em @properties. Ter uma propriedade integrada como childViewControllerse conseqüentemente parentViewControlleré uma maneira conveniente de gerenciar tais interações e construir viewControllers compostos como o UISplitViewController que você encontra em aplicativos para iPad.

Além disso, childrenViewControllers também recebem automaticamente todos os eventos do sistema que o pai recebe: -viewWillAppear, -viewWillDisappear, etc. Anteriormente, você deveria ter chamado esses métodos manualmente em seus "childrenViewControllers".

É isso aí.

Gianluca Tranchedone
fonte
Qual é a sua base para pensar que só isso faz? Além disso, você pode fornecer uma lista dos 'eventos do sistema' recebidos pela criança? Uma busca no Google por iOS "system events"não vomita muito; não parece ser um termo que a Apple usa?
Mark Amery
É basicamente um método de conveniência que permite adicionar a visão do View Controller B como uma subvisão do View Controller A, mas ainda ter o View Controller B gerenciando sua visão. Para que isso funcione corretamente, você precisa garantir que o View Controller B esteja obtendo os eventos do sistema (leia callbacks UIViewControllerDelegate). 'addChildViewController' conecta isso para você, para economizar o esforço de passar tudo manualmente.
Sam Clewlow