Existe uma maneira embutida de ir de um UIView
para o seu UIViewController
? Eu sei que você pode ir de UIViewController
sua UIView
via, [self view]
mas eu queria saber se existe uma referência reversa?
Existe uma maneira embutida de ir de um UIView
para o seu UIViewController
? Eu sei que você pode ir de UIViewController
sua UIView
via, [self view]
mas eu queria saber se existe uma referência reversa?
Respostas:
Como essa é a resposta aceita há muito tempo, sinto que preciso corrigi-la com uma resposta melhor.
Alguns comentários sobre a necessidade:
Um exemplo de como implementá-lo é o seguinte:
@protocol MyViewDelegate < NSObject >
- (void)viewActionHappened;
@end
@interface MyView : UIView
@property (nonatomic, assign) MyViewDelegate delegate;
@end
@interface MyViewController < MyViewDelegate >
@end
A visualização faz interface com seu representante (como UITableView
faz, por exemplo) e não se importa se está implementada no controlador de visualização ou em qualquer outra classe que você acaba usando.
Minha resposta original é a seguinte: Eu não recomendo isso, nem o restante das respostas em que é alcançado o acesso direto ao controlador de exibição
Não há uma maneira integrada de fazer isso. Enquanto você pode obter em torno dele, adicionando uma IBOutlet
no UIView
e ligando estes no Interface Builder, isso não é recomendado. A visualização não deve saber sobre o controlador de visualização. Em vez disso, você deve fazer o que o @Phil M sugere e criar um protocolo para ser usado como delegado.
Usando o exemplo postado por Brock, modifiquei-o para que seja uma categoria de UIView, em vez de UIViewController, e o tornei recursivo para que qualquer subvisão possa (espero) encontrar o UIViewController pai.
@interface UIView (FindUIViewController)
- (UIViewController *) firstAvailableUIViewController;
- (id) traverseResponderChainForUIViewController;
@end
@implementation UIView (FindUIViewController)
- (UIViewController *) firstAvailableUIViewController {
// convenience function for casting and to "mask" the recursive function
return (UIViewController *)[self traverseResponderChainForUIViewController];
}
- (id) traverseResponderChainForUIViewController {
id nextResponder = [self nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]]) {
return nextResponder;
} else if ([nextResponder isKindOfClass:[UIView class]]) {
return [nextResponder traverseResponderChainForUIViewController];
} else {
return nil;
}
}
@end
Para usar esse código, adicione-o a um novo arquivo de classe (nomeiei meu "UIKitCategories") e remova os dados da classe ... copie a interface @ no cabeçalho e a implementação @ no arquivo .m. Em seu projeto, #import "UIKitCategories.h" e use no código UIView:
// from a UIView subclass... returns nil if UIViewController not available
UIViewController * myController = [self firstAvailableUIViewController];
UIView
é uma subclasse de UIResponder
. UIResponder
estabelece o método -nextResponder
com uma implementação que retorna nil
. UIView
substitui esse método, conforme documentado em UIResponder
(por algum motivo, em vez de em UIView
) da seguinte maneira: se a visualização tiver um controlador de visualização, ele será retornado por -nextResponder
. Se não houver um controlador de exibição, o método retornará a superview.
Adicione isso ao seu projeto e você estará pronto para começar.
@interface UIView (APIFix)
- (UIViewController *)viewController;
@end
@implementation UIView (APIFix)
- (UIViewController *)viewController {
if ([self.nextResponder isKindOfClass:UIViewController.class])
return (UIViewController *)self.nextResponder;
else
return nil;
}
@end
Agora UIView
tem um método de trabalho para retornar o controlador de exibição.
UIView
e o UIViewController
. A resposta de Phil M com recursão é o caminho a percorrer.
Eu sugeriria uma abordagem mais leve para percorrer toda a cadeia de respostas sem precisar adicionar uma categoria no UIView:
@implementation MyUIViewSubclass
- (UIViewController *)viewController {
UIResponder *responder = self;
while (![responder isKindOfClass:[UIViewController class]]) {
responder = [responder nextResponder];
if (nil == responder) {
break;
}
}
return (UIViewController *)responder;
}
@end
Combinando várias respostas já fornecidas, eu também as envio com a minha implementação:
@implementation UIView (AppNameAdditions)
- (UIViewController *)appName_viewController {
/// Finds the view's view controller.
// Take the view controller class object here and avoid sending the same message iteratively unnecessarily.
Class vcc = [UIViewController class];
// Traverse responder chain. Return first found view controller, which will be the view's view controller.
UIResponder *responder = self;
while ((responder = [responder nextResponder]))
if ([responder isKindOfClass: vcc])
return (UIViewController *)responder;
// If the view controller isn't found, return nil.
return nil;
}
@end
A categoria faz parte da minha biblioteca estática habilitada para ARC que eu envio em todos os aplicativos que eu criar. Foi testado várias vezes e não encontrei nenhum problema ou vazamento.
PS: Você não precisa usar uma categoria como eu fiz se a exibição em questão for uma subclasse sua. Neste último caso, basta colocar o método na sua subclasse e você estará pronto.
Mesmo que isso possa ser tecnicamente resolvido como recomenda a pgb , IMHO, essa é uma falha de design. A visualização não precisa estar ciente do controlador.
I modificado de resposta para que eu possa passar qualquer ponto de vista, botão, etiqueta etc. para obtê-lo de pai UIViewController
. Aqui está o meu código.
+(UIViewController *)viewController:(id)view {
UIResponder *responder = view;
while (![responder isKindOfClass:[UIViewController class]]) {
responder = [responder nextResponder];
if (nil == responder) {
break;
}
}
return (UIViewController *)responder;
}
Editar versão do Swift 3
class func viewController(_ view: UIView) -> UIViewController {
var responder: UIResponder? = view
while !(responder is UIViewController) {
responder = responder?.next
if nil == responder {
break
}
}
return (responder as? UIViewController)!
}
Edição 2: - Extensão rápida
extension UIView
{
//Get Parent View Controller from any view
func parentViewController() -> UIViewController {
var responder: UIResponder? = self
while !(responder is UIViewController) {
responder = responder?.next
if nil == responder {
break
}
}
return (responder as? UIViewController)!
}
}
Não esqueça que você pode obter acesso ao controlador de visualização raiz da janela da qual a visualização é uma subvisão. A partir daí, se você estiver, por exemplo, usando um controlador de visualização de navegação e desejar inserir uma nova visualização nele:
[[[[self window] rootViewController] navigationController] pushViewController:newController animated:YES];
Você precisará configurar corretamente a propriedade rootViewController da janela primeiro. Faça isso quando criar o controlador, por exemplo, no delegado do aplicativo:
-(void) applicationDidFinishLaunching:(UIApplication *)application {
window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
RootViewController *controller = [[YourRootViewController] alloc] init];
[window setRootViewController: controller];
navigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
[controller release];
[window addSubview:[[self navigationController] view]];
[window makeKeyAndVisible];
}
[[self navigationController] view]
é a (sub) visualização "principal" da janela, a rootViewController
propriedade da janela deve ser configurada para navigationController
controlar a visualização "principal" imediatamente.
Embora essas respostas sejam tecnicamente corretas, incluindo a Ushox, acho que a maneira aprovada é implementar um novo protocolo ou reutilizar um já existente. Um protocolo isola o observador do observado, como colocar um slot de correio entre eles. Com efeito, é isso que Gabriel faz através da invocação do método pushViewController; a view "sabe" que é um protocolo adequado solicitar educadamente ao seu navigationController que forneça uma visualização, pois o viewController está em conformidade com o protocolo navigationController. Embora você possa criar seu próprio protocolo, basta usar o exemplo de Gabriel e reutilizar o protocolo UINavigationController.
Eu me deparei com uma situação em que tenho um pequeno componente que quero reutilizar e adicionei algum código em uma exibição reutilizável (na verdade, não é muito mais do que um botão que abre a PopoverController
).
Enquanto isso funciona bem no iPad (o que se UIPopoverController
apresenta, portanto, não precisa de referência a UIViewController
), fazer com que o mesmo código funcione significa fazer uma referência repentina presentViewController
à sua UIViewController
. Meio inconsistente, certo?
Como mencionado anteriormente, não é a melhor abordagem para ter lógica no seu UIView. Mas parecia realmente inútil agrupar as poucas linhas de código necessárias em um controlador separado.
De qualquer maneira, aqui está uma solução rápida, que adiciona uma nova propriedade a qualquer UIView:
extension UIView {
var viewController: UIViewController? {
var responder: UIResponder? = self
while responder != nil {
if let responder = responder as? UIViewController {
return responder
}
responder = responder?.nextResponder()
}
return nil
}
}
Não acho que seja "péssima" descobrir quem é o controlador de exibição em alguns casos. O que poderia ser uma má idéia é salvar a referência a este controlador, pois ele pode mudar da mesma forma que as superviews. No meu caso, tenho um getter que atravessa a cadeia de respostas.
//.h
@property (nonatomic, readonly) UIViewController * viewController;
//.m
- (UIViewController *)viewController
{
for (UIResponder * nextResponder = self.nextResponder;
nextResponder;
nextResponder = nextResponder.nextResponder)
{
if ([nextResponder isKindOfClass:[UIViewController class]])
return (UIViewController *)nextResponder;
}
// Not found
NSLog(@"%@ doesn't seem to have a viewController". self);
return nil;
}
O loop do while mais simples para encontrar o viewController.
-(UIViewController*)viewController
{
UIResponder *nextResponder = self;
do
{
nextResponder = [nextResponder nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]])
return (UIViewController*)nextResponder;
} while (nextResponder != nil);
return nil;
}
(mais conciso que as outras respostas)
fileprivate extension UIView {
var firstViewController: UIViewController? {
let firstViewController = sequence(first: self, next: { $0.next }).first(where: { $0 is UIViewController })
return firstViewController as? UIViewController
}
}
Meu caso de uso para que eu preciso para acessar a exibição primeiro UIViewController
: Eu tenho um objeto que envolve em torno de AVPlayer
/ AVPlayerViewController
e eu quero fornecer um simples show(in view: UIView)
método que irá incorporar AVPlayerViewController
em view
. Para isso, eu preciso de acesso view
é UIViewController
.
Isso não responde diretamente à pergunta, mas faz uma suposição sobre a intenção da pergunta.
Se você tem uma visão e, nessa visão, precisa chamar um método em outro objeto, como o controlador de visão, é possível usar o NSNotificationCenter.
Primeiro, crie sua sequência de notificações em um arquivo de cabeçalho
#define SLCopyStringNotification @"ShaoloCopyStringNotification"
Na sua opinião, ligue para postNotificationName:
- (IBAction) copyString:(id)sender
{
[[NSNotificationCenter defaultCenter] postNotificationName:SLCopyStringNotification object:nil];
}
Então, no seu controlador de exibição, você adiciona um observador. Eu faço isso em viewDidLoad
- (void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(copyString:)
name:SLCopyStringNotification
object:nil];
}
Agora (também no mesmo controlador de exibição) implemente seu método copyString: como representado no @selector acima.
- (IBAction) copyString:(id)sender
{
CalculatorResult* result = (CalculatorResult*)[[PercentCalculator sharedInstance].arrayTableDS objectAtIndex:([self.viewTableResults indexPathForSelectedRow].row)];
UIPasteboard *gpBoard = [UIPasteboard generalPasteboard];
[gpBoard setString:result.stringResult];
}
Não estou dizendo que essa é a maneira certa de fazer isso, apenas parece mais limpo do que executar a cadeia de resposta inicial. Usei esse código para implementar um UIMenuController em um UITableView e passar o evento de volta ao UIViewController para que eu possa fazer algo com os dados.
Certamente é uma má idéia e um design errado, mas tenho certeza de que todos podemos desfrutar de uma solução Swift da melhor resposta proposta por @Phil_M:
static func firstAvailableUIViewController(fromResponder responder: UIResponder) -> UIViewController? {
func traverseResponderChainForUIViewController(responder: UIResponder) -> UIViewController? {
if let nextResponder = responder.nextResponder() {
if let nextResp = nextResponder as? UIViewController {
return nextResp
} else {
return traverseResponderChainForUIViewController(nextResponder)
}
}
return nil
}
return traverseResponderChainForUIViewController(responder)
}
Se sua intenção é fazer coisas simples, como mostrar um diálogo modal ou dados de rastreamento, isso não justifica o uso de um protocolo. Eu pessoalmente guardo essa função em um objeto utilitário, você pode usá-la de qualquer coisa que implemente o protocolo UIResponder como:
if let viewController = MyUtilityClass.firstAvailableUIViewController(self) {}
Todo o crédito a @Phil_M
Talvez eu esteja atrasado aqui. Mas nesta situação eu não gosto de categoria (poluição). Eu amo assim:
#define UIViewParentController(__view) ({ \
UIResponder *__responder = __view; \
while ([__responder isKindOfClass:[UIView class]]) \
__responder = [__responder nextResponder]; \
(UIViewController *)__responder; \
})
Solução mais rápida
extension UIView {
var parentViewController: UIViewController? {
for responder in sequence(first: self, next: { $0.next }) {
if let viewController = responder as? UIViewController {
return viewController
}
}
return nil
}
}
sequence
bit). Então, se "swifty" significa "mais funcional", acho que é mais swifty.
Versão atualizada para o swift 4: Obrigado por @Phil_M e @ paul-slm
static func firstAvailableUIViewController(fromResponder responder: UIResponder) -> UIViewController? {
func traverseResponderChainForUIViewController(responder: UIResponder) -> UIViewController? {
if let nextResponder = responder.next {
if let nextResp = nextResponder as? UIViewController {
return nextResp
} else {
return traverseResponderChainForUIViewController(responder: nextResponder)
}
}
return nil
}
return traverseResponderChainForUIViewController(responder: responder)
}
Versão Swift 4
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
}
Exemplo de uso
if let parent = self.view.parentViewController{
}
return
palavra-chave agora 🤓Solução 1:
extension UIView {
var parentViewController: UIViewController? {
sequence(first: self) { $0.next }
.first(where: { $0 is UIViewController })
.flatMap { $0 as? UIViewController }
}
}
Solução 2:
extension UIView {
var parentViewController: UIViewController? {
sequence(first: self) { $0.next }
.compactMap{ $0 as? UIViewController }
.first
}
}
Minha solução provavelmente seria considerada meio falsa, mas eu tive uma situação semelhante a mayoneez (eu queria trocar de vista em resposta a um gesto em um EAGLView) e obtive o controlador de exibição do EAGL da seguinte maneira:
EAGLViewController *vc = ((EAGLAppDelegate*)[[UIApplication sharedApplication] delegate]).viewController;
EAGLViewController *vc = [(EAGLAppDelegate *)[UIApplication sharedApplication].delegate viewController];
.
ClassName *object
- com um asterisco.
Eu acho que há um caso em que o observado precisa informar o observador.
Vejo um problema semelhante em que o UIView em um UIViewController está respondendo a uma situação e ele precisa primeiro informar ao controlador de exibição pai para ocultar o botão voltar e, após a conclusão, informar ao controlador de exibição pai que ele precisa sair da pilha.
Eu tenho tentado isso com delegados sem sucesso.
Eu não entendo por que isso deve ser uma má idéia?
Outra maneira fácil é ter sua própria classe de exibição e adicionar uma propriedade do controlador de exibição na classe de exibição. Geralmente, o controlador de visualização cria a visualização e é aí que o controlador pode se definir para a propriedade. Basicamente, é em vez de procurar ao redor (com um pouco de hacking) pelo controlador, fazendo com que o controlador se ajuste à visualização - isso é simples, mas faz sentido, porque é o controlador que "controla" a visualização.
Se você não fizer o upload para a App Store, também poderá usar um método privado do UIView.
@interface UIView(Private)
- (UIViewController *)_viewControllerForAncestor;
@end
// Later in the code
UIViewController *vc = [myView _viewControllerForAncestor];
var parentViewController: UIViewController? {
let s = sequence(first: self) { $0.next }
return s.compactMap { $0 as? UIViewController }.first
}
Se o seu rootViewController for UINavigationViewController, que foi configurado na classe AppDelegate,
+ (UIViewController *) getNearestViewController:(Class) c {
NSArray *arrVc = [[[[UIApplication sharedApplication] keyWindow] rootViewController] childViewControllers];
for (UIViewController *v in arrVc)
{
if ([v isKindOfClass:c])
{
return v;
}
}
return nil;}
Onde c é necessário, veja a classe de controladores.
USO:
RequiredViewController* rvc = [Utilities getNearestViewController:[RequiredViewController class]];
Não tem jeito.
O que faço é passar o ponteiro UIViewController para o UIView (ou uma herança apropriada). Me desculpe, eu não posso ajudar com a abordagem da IB para o problema, porque eu não acredito em IB.
Para responder ao primeiro comentarista: às vezes você precisa saber quem ligou para você, porque determina o que você pode fazer. Por exemplo, com um banco de dados, você pode ter somente acesso de leitura ou leitura / gravação ...