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?
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?
Respostas:
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.
A partir da UIResponder
documentaçã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 nextResponder
até 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; \
})
UIResponder
;). Post muito edificante.
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
parentViewController
não pode ser definido public
se a extensão estiver no mesmo arquivo que UIView
você pode configurá-lo fileprivate
, ele será compilado, mas não funcionará! 😐
Apenas para fins de depuração, você pode chamar _viewDelegate
visualizaçõ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._viewControllerForAncestor
percorrerá as supervistas até encontrar o primeiro que pertence a um controlador de exibição.
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
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)
}
Se você não está familiarizado com o código e deseja encontrar o ViewController corespondendo a determinada exibição, tente:
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.
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
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
}
}
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.
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
}
}
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 :)
recursiveDescription
somente imprime a hierarquia de exibição , não os controladores.