Dada uma visão, como obtenho seu viewController?

141

Eu tenho um ponteiro para a UIView. Como faço para acessar os seus UIViewController? [self superview]é outro UIView, mas não o UIViewController, certo?

mahboudz
fonte
Eu acho que este tópico tem as respostas: Obter o UIViewController do UIView no iPhone?
Ushox 03/09/09
Basicamente, estou tentando chamar o viewWillAppear do meu viewController, pois meu modo de exibição está sendo descartado. A exibição está sendo descartada pela própria exibição, que está detectando um toque e chamando [self removeFromSuperview]; O viewController não está chamando o viewWillAppear / WillDisappear / DidAppear / DidDisappear.
mahboudz 03/09/09
Eu quis dizer que estou tentando chamar viewWillDisappear, pois minha exibição está sendo descartada.
mahboudz
1
Possível duplicata do Get to UIViewController a partir do UIView?
Efren

Respostas:

42

Sim, superviewé a visualização que contém sua visualização. Sua visão não deve saber exatamente qual é seu controlador de visão, porque isso violaria os princípios do MVC.

O controlador, por outro lado, sabe por qual visão é responsável por ( self.view = myView) e, geralmente, essa visão delega métodos / eventos para manipulação no controlador.

Normalmente, em vez de um ponteiro para sua visão, você deve ter um ponteiro para seu controlador, que por sua vez pode executar alguma lógica de controle ou passar algo para sua visão.

Dimitar Dimitrov
fonte
24
Não tenho certeza se isso violaria os princípios do MVC. A qualquer momento, uma visualização possui apenas um controlador de visualização. Ser capaz de acessá-lo para transmitir uma mensagem de volta a ele deve ser um recurso automático, não aquele em que você deve trabalhar para alcançar (adicionando uma propriedade para acompanhar). Pode-se dizer o mesmo sobre os pontos de vista: por que você precisa saber quem é seu filho? Ou se há outras visualizações de irmãos. No entanto, existem maneiras de obter esses objetos.
mahboudz
Você está certo sobre a visualização, sabendo sobre seu pai, não é uma decisão de projeto super clara, mas já está estabelecido para executar algumas ações, usando diretamente uma variável de membro de superview (verifique o tipo de pai, remova do pai, etc.). Tendo trabalhado com o PureMVC recentemente, eu me tornei um pouco mais exigente quanto à abstração do design :) Eu faria paralelo entre as classes UIView e UIViewController do iPhone e as classes View e Mediador do PureMVC - na maioria das vezes, a classe View não precisa conheça seu manipulador / interface MVC (UIViewController / Mediador).
Dimitar Dimitrov
9
Palavra-chave: "mais".
Glenn Maynard
278

A partir da UIResponderdocumentação para nextResponder:

A classe UIResponder não armazena ou define o próximo respondedor automaticamente, retornando nulo por padrão. As subclasses devem substituir esse método para definir o próximo respondedor. O UIView implementa esse método retornando o objeto UIViewController que o gerencia (se houver) ou sua superview (se não houver) ; UIViewController implementa o método retornando a superview de sua view; UIWindow retorna o objeto de aplicativo e UIApplication retorna nulo.

Portanto, se você recursar uma visualização nextResponderaté que seja do tipo UIViewController, terá o viewController pai de qualquer visualização.

Observe que ele ainda pode não ter um controlador de exibição pai. Mas somente se a exibição não fizer parte da hierarquia de exibição de um viewController.

Extensão Swift 3 e Swift 4.1 :

extension UIView {
    var parentViewController: UIViewController? {
        var parentResponder: UIResponder? = self
        while parentResponder != nil {
            parentResponder = parentResponder?.next
            if let viewController = parentResponder as? UIViewController {
                return viewController
            }
        }
        return nil
    }
}

Extensão Swift 2:

extension UIView {
    var parentViewController: UIViewController? {
        var parentResponder: UIResponder? = self
        while parentResponder != nil {
            parentResponder = parentResponder!.nextResponder()
            if let viewController = parentResponder as? UIViewController {
                return viewController
            }
        }
        return nil
    }
}

Categoria Objective-C:

@interface UIView (mxcl)
- (UIViewController *)parentViewController;
@end

@implementation UIView (mxcl)
- (UIViewController *)parentViewController {
    UIResponder *responder = self;
    while ([responder isKindOfClass:[UIView class]])
        responder = [responder nextResponder];
    return (UIViewController *)responder;
}
@end

Essa macro evita a categoria de poluição:

#define UIViewParentController(__view) ({ \
    UIResponder *__responder = __view; \
    while ([__responder isKindOfClass:[UIView class]]) \
        __responder = [__responder nextResponder]; \
    (UIViewController *)__responder; \
})
mxcl
fonte
Apenas uma coisa: se você se preocupa com a poluição por categoria, apenas defina-a como uma função estática e não como uma macro. Além disso, a conversão brutal é perigosa e, além disso, a macro pode não estar correta, mas não tenho certeza.
Mjuba
A macro funciona, eu só uso a versão macro pessoalmente. Começo muitos projetos e tenho um cabeçalho que simplesmente apareço em todos os lugares com várias dessas funções de utilitário de macro. Economiza tempo. Se você não gosta de macros, pode adaptá-lo a uma função, mas as funções estáticas parecem entediantes, pois você deve colocar uma em cada arquivo que deseja usá-la. Parece que você desejaria uma função não estática declarada em um cabeçalho e definida em um .m em algum lugar?
Mxcl
É bom evitar alguns delegados ou notificações. Obrigado!
Ferran Maylinch
Você deve torná-lo uma extensão de UIResponder;). Post muito edificante.
ScottyBlades
32

resposta @andrey em uma linha (testada no Swift 4.1 ):

extension UIResponder {
    public var parentViewController: UIViewController? {
        return next as? UIViewController ?? next?.parentViewController
    }
}

uso:

 let vc: UIViewController = view.parentViewController
Federico Zanetello
fonte
parentViewControllernão pode ser definido publicse a extensão estiver no mesmo arquivo que UIViewvocê pode configurá-lo fileprivate, ele será compilado, mas não funcionará! 😐
Está funcionando bem. Eu usei isso para a ação do botão voltar dentro do arquivo .xib do cabeçalho comum.
McDonal_11
23

Apenas para fins de depuração, você pode chamar _viewDelegatevisualizações para obter seus controladores de visualização. Esta é uma API privada, portanto não é segura para a App Store, mas é útil para depuração.

Outros métodos úteis:

  • _viewControllerForAncestor- obtenha o primeiro controlador que gerencia uma exibição na cadeia de superview. (obrigado n00neimp0rtant)
  • _rootAncestorViewController - obtém o controlador ancestral cuja hierarquia de visualizações está definida na janela atualmente.
Leo Natan
fonte
Foi exatamente por isso que cheguei a essa pergunta. Aparentemente, 'nextResponder' faz a mesma coisa, mas eu aprecio o insight que essa resposta fornece. Eu entendo e gosto do MVC, mas a depuração é um animal diferente!
mbm29414
4
Parece que isso funciona apenas na visualização principal do controlador de exibição, e não em nenhuma subvisualização dele. _viewControllerForAncestorpercorrerá as supervistas até encontrar o primeiro que pertence a um controlador de exibição.
precisa saber é o seguinte
Obrigado @ n00neimp0rtant! Estou votando esta resposta para que as pessoas vejam seu comentário.
eyuelt
Atualize a resposta com mais métodos.
Leo Natan
1
Isso é útil ao depurar.
585 ev evinin
10

Para obter referência ao UIViewController com o UIView, você pode fazer a extensão do UIResponder (que é uma super classe para o UIView e o UIViewController), que permite percorrer a cadeia de respostas e, assim, alcançar o UIViewController (caso contrário, não retornará nada).

extension UIResponder {
    func getParentViewController() -> UIViewController? {
        if self.nextResponder() is UIViewController {
            return self.nextResponder() as? UIViewController
        } else {
            if self.nextResponder() != nil {
                return (self.nextResponder()!).getParentViewController()
            }
            else {return nil}
        }
    }
}

//Swift 3
extension UIResponder {
    func getParentViewController() -> UIViewController? {
        if self.next is UIViewController {
            return self.next as? UIViewController
        } else {
            if self.next != nil {
                return (self.next!).getParentViewController()
            }
            else {return nil}
        }
    }
}

let vc = UIViewController()
let view = UIView()
vc.view.addSubview(view)
view.getParentViewController() //provide reference to vc
Andrey
fonte
4

A maneira rápida e genérica no Swift 3:

extension UIResponder {
    func parentController<T: UIViewController>(of type: T.Type) -> T? {
        guard let next = self.next else {
            return nil
        }
        return (next as? T) ?? next.parentController(of: T.self)
    }
}

//Use:
class MyView: UIView {
    ...
    let parentController = self.parentController(of: MyViewController.self)
}
iTSangar
fonte
2

Se você não está familiarizado com o código e deseja encontrar o ViewController corespondendo a determinada exibição, tente:

  1. Executar aplicativo em depuração
  2. Navegue para a tela
  3. Inspetor Start View
  4. Pegue a Visualização que deseja encontrar (ou uma visão infantil ainda melhor)
  5. No painel direito, obtenha o endereço (por exemplo, 0x7fe523bd3000)
  6. No console de depuração, comece a escrever comandos:
    po (UIView *) 0x7fe523bd3000
    po [(UIView *) 0x7fe523bd3000 nextResponder]
    po [[(UIView *) 0x7fe523bd3000 nextResponder] nextResponder]
    po [[[(UIView *) 0x7fe523bd3000 nextResponder] nextResponder] nextResponder]
    ...

Na maioria dos casos, você obterá o UIView, mas, de tempos em tempos, haverá uma classe baseada no UIViewController.

m4js7er
fonte
1

Eu acho que você pode propagar a torneira para o controlador de exibição e deixá-lo lidar com isso. Essa é uma abordagem mais aceitável. Quanto ao acesso a um controlador de exibição a partir de sua exibição, você deve manter uma referência a um controlador de exibição, pois não há outra maneira. Consulte este tópico, pode ajudar: Acessando o controlador de exibição de uma visualização

Nava Carmon
fonte
Se você tiver várias visualizações e uma fechar todas as visualizações, e você precisar chamar viewWillDisappear, não seria mais fácil para essa visualização detectar a torneira do que entregá-la ao controlador de visualização e fazer com que o controlador de visualização seja verificado com todas as visualizações para ver em qual delas foi tocada?
mahboudz
0

Mais código seguro de tipo para Swift 3.0

extension UIResponder {
    func owningViewController() -> UIViewController? {
        var nextResponser = self
        while let next = nextResponser.next {
            nextResponser = next
            if let vc = nextResponser as? UIViewController {
                return vc
            }
        }
        return nil
    }
}
Denis Krivitski
fonte
0

Infelizmente, isso é impossível, a menos que você subclasse a view e forneça a ela uma propriedade de instância ou semelhante que armazene a referência do controlador de view dentro dela assim que a view for adicionada à cena ...

Na maioria dos casos - é muito fácil contornar o problema original desta postagem, pois a maioria dos controladores de exibição são entidades bem conhecidas do programador responsável por adicionar quaisquer subvisões ao ViewController's View ;-) É por isso que acho que a Apple nunca se preocupou em adicionar essa propriedade.

Morten Carlsen
fonte
0

Um pouco tarde, mas aqui está uma extensão que permite encontrar um respondedor de qualquer tipo, incluindo o ViewController.

extension NSObject{
func findNext(type: AnyClass) -> Any{
    var resp = self as! UIResponder

    while !resp.isKind(of: type.self) && resp.next != nil
    {
        resp = resp.next!
    }

    return resp
  }                       
}
Hackbarth
fonte
-1

Se você definir um ponto de interrupção, poderá colá-lo no depurador para imprimir a hierarquia de exibição:

po [[UIWindow keyWindow] recursiveDescription]

Você deve encontrar o pai da sua visualização em algum lugar dessa bagunça :)

Scott McCoy
fonte
recursiveDescriptionsomente imprime a hierarquia de exibição , não os controladores.
Alan Zeino
1
a partir disso, você pode identificar o viewcontroller @AlanZeino #
Akshay