Cenário: O usuário toca em um botão em um controlador de exibição. O controlador de exibição é o mais alto (obviamente) na pilha de navegação. O toque chama um método de classe de utilitário chamado em outra classe. Acontece uma coisa ruim e desejo exibir um alerta antes que o controle retorne ao controlador de exibição.
+ (void)myUtilityMethod {
// do stuff
// something bad happened, display an alert.
}
Isso foi possível com UIAlertView
(mas talvez não seja o bastante).
Nesse caso, como você apresenta um UIAlertController
, ali mesmo myUtilityMethod
?
fonte
Na WWDC, parei em um dos laboratórios e fiz a um engenheiro da Apple a mesma pergunta: "Qual era a melhor prática para exibir um
UIAlertController
?" E ele disse que eles estavam recebendo muito essa pergunta e brincamos que eles deveriam ter tido uma sessão nela. Ele disse que internamente a Apple está criando umUIWindow
com um transparenteUIViewController
e, em seguida, apresentando oUIAlertController
nele. Basicamente, o que está na resposta de Dylan Betterman.Mas eu não queria usar uma subclasse de,
UIAlertController
porque isso exigiria que eu mudasse meu código em todo o aplicativo. Então, com a ajuda de um objeto associado, criei uma categoriaUIAlertController
que fornece umashow
método em Objective-C.Aqui está o código relevante:
Aqui está um exemplo de uso:
O
UIWindow
que é criado será destruído quandoUIAlertController
for desalocado, pois é o único objeto que está retendo oUIWindow
. Mas se você atribuir aUIAlertController
uma propriedade ou aumentar sua contagem de retenção acessando o alerta em um dos blocos de ação,UIWindow
ele permanecerá na tela, bloqueando sua interface do usuário. Consulte o código de uso de amostra acima para evitar no caso de precisar acessarUITextField
.Fiz um repositório do GitHub com um projeto de teste: FFGlobalAlertController
fonte
viewDidDisappear:
em uma categoria parece uma Má Idéia. Em essência, você está competindo com a implementação do frameworkviewDidDisappear:
. Por enquanto, pode estar tudo bem, mas se a Apple decidir implementar esse método no futuro, não há como chamá-lo (ou seja, não há analogiasuper
disso que aponte para a implementação primária de um método a partir de uma implementação de categoria) .prefersStatusBarHidden
epreferredStatusBarStyle
sem uma subclasse extra?Rápido
Objetivo-C
fonte
Você pode fazer o seguinte com o Swift 2.2:
E Swift 3.0:
fonte
Warning: Attempt to present <UIAlertController: 0x145bfa30> on <UINavigationController: 0x1458e450> whose view is not in the window hierarchy!
.visibleViewController
propriedade a qualquer momento para ver de qual controlador apresentar o alerta. Confira os documentosBastante genérico
UIAlertController
extension
para todos os casos deUINavigationController
e / ouUITabBarController
. Também funciona se houver um VC modal na tela no momento.Uso:
Esta é a extensão:
fonte
UI
classe que detém um (fraco!)currentVC
Do tipoUIViewController
.Eu tenhoBaseViewController
que herda deUIViewController
e conjuntoUI.currentVC
paraself
emviewDidAppear
seguida, paranil
onviewWillDisappear
. Todos os meus controladores de exibição no aplicativo são herdadosBaseViewController
. Dessa forma, se você tem algoUI.currentVC
(não énil
...) - ele definitivamente não está no meio de uma animação de apresentação e você pode pedir para apresentar suaUIAlertController
.else { if let presentedViewController = controller.presentedViewController { presentedViewController.presentViewController(self, animated: animated, completion: completion) } else { controller.presentViewController(self, animated: animated, completion: completion) } }
Para melhorar a resposta da agilityvision , você precisará criar uma janela com um controlador de visualização raiz transparente e apresentar a visualização de alerta a partir daí.
No entanto , desde que você tenha uma ação no seu controlador de alerta, não precisará manter uma referência à janela . Como etapa final do bloco manipulador de ação, você só precisa ocultar a janela como parte da tarefa de limpeza. Por ter uma referência à janela no bloco manipulador, isso cria uma referência circular temporária que seria interrompida quando o controlador de alerta fosse descartado.
fonte
A solução a seguir não funcionou, embora parecesse bastante promissora em todas as versões. Esta solução está gerando AVISO .
Aviso: Tente apresentar em cuja exibição não está na hierarquia da janela!
https://stackoverflow.com/a/34487871/2369867 => Isso parece promissor na época. Mas era não no
Swift 3
. Então, eu estou respondendo isso no Swift 3 e este não é um exemplo de modelo.Este é um código totalmente funcional por si só, quando você cola dentro de qualquer função.
Este é um código testado e funcionando no Swift 3.
fonte
UIWindow
contrário a janela será liberada e desaparecerá logo após sair do escopo.Aqui está a resposta do mythicalcoder como uma extensão, testada e funcionando no Swift 4:
Exemplo de uso:
fonte
Isso funciona no Swift para controladores de exibição normais e mesmo se houver um controlador de navegação na tela:
fonte
UIWindow
não responde. Algo a ver com owindowLevel
provável. Como posso torná-lo responsivo?alertWindow
comonil
quando terminar.Adicionando à resposta de Zev (e retornando ao Objective-C), você pode se deparar com uma situação em que seu controlador de exibição raiz está apresentando outro VC por meio de um segue ou qualquer outra coisa. Chamar o PresentationViewController no VC raiz cuidará disso:
Isso solucionou um problema que tive onde o VC raiz seguiu para outro VC e, em vez de apresentar o controlador de alerta, um aviso como os relatados acima foi emitido:
Não testei, mas isso também pode ser necessário se o seu VC raiz for um controlador de navegação.
fonte
UIApplication.sharedApplication().keyWindow?.rootViewController?.presentedViewController?.presentViewController(controller, animated: true, completion: nil)
Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior (<UIAlertController: 0x15cd4afe0>)
rootViewController.presentedViewController
se não for nulo, caso contrário, usandorootViewController
. Para uma solução totalmente genérico, pode ser necessário andar a cadeia depresentedViewController
s para chegar aotopmost
VCA resposta de @ agilityvision traduzida para Swift4 / iOS11. Não usei cadeias localizadas, mas você pode alterar isso facilmente:
fonte
window.backgroundColor = UIColor.clear
consertou isso.viewController.view.backgroundColor = UIColor.clear
não parece ser necessário.UIAlertController
subclasses:The UIAlertController class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified.
developer.apple.com/documentation/uikit/uialertcontrollerCrie uma extensão como na resposta Aviel Gross. Aqui você tem a extensão Objective-C.
Aqui você tem o arquivo de cabeçalho * .h
E implementação: * .m
Você está usando esta extensão em Seu arquivo de implementação, assim:
fonte
Post cruzado minha resposta pois esses dois tópicos não são sinalizados como dupes ...
Agora que
UIViewController
faz parte da cadeia de respostas, você pode fazer algo assim:fonte
A resposta de Zev Eisenberg é simples e direta, mas nem sempre funciona, e pode falhar com esta mensagem de aviso:
Isso ocorre porque o rootViewController do Windows não está no topo das visualizações apresentadas. Para corrigir isso, precisamos percorrer a cadeia de apresentação, conforme mostrado no meu código de extensão UIAlertController escrito em Swift 3:
Atualizações em 15/09/2017:
Testou e confirmou que a lógica acima ainda funciona muito bem na nova semente iOS 11 GM disponível. O método mais votado pela agilityvision, no entanto, não: a exibição de alerta apresentada em um
UIWindow
fica abaixo do teclado e potencialmente impede que o usuário toque em seus botões. Isso ocorre porque no iOS 11 todos os níveis da janela mais altos que os da janela do teclado são reduzidos para um nível abaixo dele.Um artefato de apresentação do
keyWindow
porém é a animação do teclado deslizando para baixo quando o alerta é apresentado e deslizando novamente para cima quando o alerta é descartado. Se você deseja que o teclado permaneça lá durante a apresentação, tente apresentar a partir da janela superior, como mostra o código abaixo:A única parte não tão boa do código acima é que ele verifica o nome da classe
UIRemoteKeyboardWindow
para garantir que também possamos incluí-lo. No entanto, o código acima funciona muito bem nas sementes GM iOS 9, 10 e 11 GM, com a cor certa e sem os artefatos deslizantes do teclado.fonte
Swift 4+
Solução que uso há anos sem problemas. Primeiro, estendo
UIWindow
para descobrir que é visibleViewController. NOTA : se você estiver usando classes de coleção * personalizadas (como o menu lateral), adicione manipulador para este caso na seguinte extensão. Depois de obter o controle da maioria das visualizações, é fácil apresentarUIAlertController
da mesma formaUIAlertView
.fonte
Para o iOS 13, com base nas respostas de mythicalcoder e bobbyrehm :
No iOS 13, se você estiver criando sua própria janela para apresentar o alerta, será necessário manter uma forte referência a essa janela ou o alerta não será exibido porque a janela será imediatamente desalocada quando a referência sair do escopo.
Além disso, você precisará definir a referência como nulo novamente depois que o alerta for descartado para remover a janela e continuar permitindo a interação do usuário na janela principal abaixo dela.
Você pode criar uma
UIViewController
subclasse para encapsular a lógica de gerenciamento de memória da janela:Você pode usá-lo como está ou, se quiser um método de conveniência
UIAlertController
, pode jogá-lo em uma extensão:fonte
dismiss
o WindowAlertPresentationController diretamentealert.presentingViewController?.dismiss(animated: true, completion: nil)
Maneira abreviada de apresentar o alerta no Objective-C:
Onde
alertController
está o seuUIAlertController
objeto?NOTA: Você também precisará garantir que sua classe auxiliar estenda
UIViewController
fonte
Se alguém estiver interessado, criei uma versão Swift 3 da resposta @agilityvision. O código:
fonte
Com isso, você pode facilmente apresentar seu alerta assim
Uma coisa a notar é que, se houver um UIAlertController atualmente sendo exibido,
UIApplication.topMostViewController
retornará aUIAlertController
. Apresentar em cima de umUIAlertController
comportamento estranho e deve ser evitado. Como tal, você deve verificar manualmente isso!(UIApplication.topMostViewController is UIAlertController)
antes de apresentar ou adicionar umelse if
caso para retornar nulo seself is UIAlertController
fonte
Você pode enviar a visualização atual ou o controlador como um parâmetro:
fonte
Kevin Sliech forneceu uma ótima solução.
Agora uso o código abaixo na minha subclasse principal UIViewController.
Uma pequena alteração que fiz foi verificar se o melhor controlador de apresentação não é um UIViewController simples. Caso contrário, deve haver algum VC que apresente um VC simples. Assim, retornamos o VC que está sendo apresentado.
Parece que tudo funcionou até agora nos meus testes.
Obrigado Kevin!
fonte
Além de ótimas respostas dadas ( visão de agilidade , adib , malhal ). Para alcançar o comportamento de enfileiramento, como nos bons e antigos UIAlertViews (evitar a sobreposição de janelas de alerta), use este bloco para observar a disponibilidade no nível da janela:
Exemplo completo:
Isso permitirá que você evite a sobreposição de janelas de alerta. O mesmo método pode ser usado para separar e colocar em controladores de exibição de fila para qualquer número de camadas de janela.
fonte
Eu tentei de tudo mencionado, mas sem sucesso. O método que eu usei para o Swift 3.0:
fonte
Algumas dessas respostas funcionaram apenas parcialmente para mim, combinando-as no método de classe a seguir no AppDelegate foi a solução para mim. Ele funciona no iPad, nas visualizações UITabBarController, no UINavigationController, e na apresentação de modais. Testado no iOS 10 e 13.
Uso:
fonte
Suporte a cena iOS13 (ao usar o UIWindowScene)
fonte
Você pode tentar implementar uma categoria
UIViewController
com mehtod como- (void)presentErrorMessage;
And e dentro desse método, você implementa o UIAlertController e, em seguida, apresenta-oself
. Em seu código de cliente, você terá algo como:[myViewController presentErrorMessage];
Dessa forma, você evitará parâmetros e avisos desnecessários sobre a exibição não estar na hierarquia da janela.
fonte
myViewController
no código onde a coisa ruim acontece. Esse é um método utilitário que não sabe nada sobre o controlador de exibição que o chamou.UIAlertView
me levou a quebrar essa regra em alguns pontos.Existem 2 abordagens que você pode usar:
-Usar
UIAlertView
ou 'UIActionSheet' em vez (não recomendado, porque é preterido no iOS 8 mas funciona agora)De alguma forma, lembre-se do último controlador de exibição que é apresentado. Aqui está um exemplo.
Uso:
fonte
Eu uso esse código com algumas pequenas variações pessoais na minha classe AppDelegate
fonte
Parece funcionar:
fonte
criar a classe auxiliar AlertWindow e depois usá-la como
fonte