Scénario: l'utilisateur appuie sur un bouton d'un contrôleur de vue. Le contrôleur de vue est le plus haut (évidemment) de la pile de navigation. Le tap appelle une méthode de classe utilitaire appelée sur une autre classe. Une mauvaise chose s'y produit et je veux afficher une alerte juste avant que le contrôle ne revienne au contrôleur de vue.
+ (void)myUtilityMethod {
// do stuff
// something bad happened, display an alert.
}
Cela a été possible avec UIAlertView
(mais peut-être pas tout à fait approprié).
Dans ce cas, comment présentez-vous un UIAlertController
, juste là myUtilityMethod
?
la source
À la WWDC, je me suis arrêté dans l'un des laboratoires et j'ai posé la même question à un ingénieur Apple: "Quelle était la meilleure pratique pour afficher un
UIAlertController
?" Et il a dit qu'ils avaient beaucoup posé cette question et nous avons plaisanté en disant qu'ils auraient dû avoir une séance à ce sujet. Il a dit qu'en interne, Apple crée unUIWindow
avec un transparentUIViewController
, puis présente leUIAlertController
le dessus. Fondamentalement, ce qui est dans la réponse de Dylan Betterman.Mais je ne voulais pas utiliser une sous-classe de
UIAlertController
car cela nécessiterait que je change mon code dans toute mon application. Donc, avec l'aide d'un objet associé, j'ai créé une catégorieUIAlertController
qui fournit unshow
méthode dans Objective-C.Voici le code pertinent:
Voici un exemple d'utilisation:
Le
UIWindow
qui est créé sera détruit lors de laUIAlertController
désallocation, car c'est le seul objet qui conserve leUIWindow
. Mais si vous affectez leUIAlertController
à une propriété ou que son nombre de retenues augmente en accédant à l'alerte dans l'un des blocs d'action, leUIWindow
reste à l'écran, verrouillant votre interface utilisateur. Voir l'exemple de code d'utilisation ci-dessus pour éviter en cas de besoin d'accéderUITextField
.J'ai fait un dépôt GitHub avec un projet de test: FFGlobalAlertController
la source
viewDidDisappear:
en œuvre sur une catégorie ressemble à une mauvaise idée. En substance, vous êtes en concurrence avec la mise en œuvre du cadre deviewDidDisappear:
. Pour l'instant, cela peut être correct, mais si Apple décide de mettre en œuvre cette méthode à l'avenir, il n'y a aucun moyen pour vous de l'appeler (c'est-à-dire qu'il n'y a pas d'analogue desuper
cela qui pointe vers la mise en œuvre principale d'une méthode à partir d'une mise en œuvre de catégorie) .prefersStatusBarHidden
etpreferredStatusBarStyle
sans sous-classe supplémentaire?Rapide
Objectif c
la source
Vous pouvez effectuer les opérations suivantes avec Swift 2.2:
Et Swift 3.0:
la source
Warning: Attempt to present <UIAlertController: 0x145bfa30> on <UINavigationController: 0x1458e450> whose view is not in the window hierarchy!
.visibleViewController
propriété à tout moment pour voir de quel contrôleur présenter l'alerte. Découvrez les documentsAssez générique
UIAlertController
extension
pour tous les cas deUINavigationController
et / ouUITabBarController
. Fonctionne également s'il y a un VC modal à l'écran pour le moment.Usage:
Ceci est l'extension:
la source
UI
classe singleton qui contient un (faible!) DecurrentVC
type.UIViewController
J'aiBaseViewController
qui hérite deUIViewController
et définiUI.currentVC
surself
onviewDidAppear
puis surnil
onviewWillDisappear
. Tous mes contrôleurs de vue dans l'application héritentBaseViewController
. De cette façon, si vous avez quelque choseUI.currentVC
(ce n'est pasnil
...) - ce n'est certainement pas au milieu d'une animation de présentation, et vous pouvez lui demander de présenter votreUIAlertController
.else { if let presentedViewController = controller.presentedViewController { presentedViewController.presentViewController(self, animated: animated, completion: completion) } else { controller.presentViewController(self, animated: animated, completion: completion) } }
Pour améliorer la réponse d'agilityvision , vous devrez créer une fenêtre avec un contrôleur de vue racine transparent et présenter la vue d'alerte à partir de là.
Cependant, tant que vous avez une action dans votre contrôleur d'alerte, vous n'avez pas besoin de conserver une référence à la fenêtre . Comme étape finale du bloc du gestionnaire d'actions, il vous suffit de masquer la fenêtre dans le cadre de la tâche de nettoyage. En ayant une référence à la fenêtre dans le bloc de gestionnaire, cela crée une référence circulaire temporaire qui serait rompue une fois le contrôleur d'alerte fermé.
la source
La solution suivante n'a pas fonctionné même si elle semblait assez prometteuse avec toutes les versions. Cette solution génère AVERTISSEMENT .
Attention: Tentez de présenter sur la vue de laquelle ne figure pas dans la hiérarchie des fenêtres!
https://stackoverflow.com/a/34487871/2369867 => Cela semble alors prometteur. Mais il était pas dans
Swift 3
. Je réponds donc à cela dans Swift 3 et ce n'est pas exemple de modèle.Il s'agit d'un code plutôt entièrement fonctionnel en soi une fois que vous avez collé à l'intérieur d'une fonction.
Ceci est testé et fonctionne dans Swift 3.
la source
UIWindow
ou bien la fenêtre sera libérée et disparaîtra peu de temps après être sortie du champ d'application.Voici la réponse de mythicalcoder en tant qu'extension, testée et fonctionnant dans Swift 4:
Exemple d'utilisation:
la source
Cela fonctionne dans Swift pour les contrôleurs de vue normaux et même s'il y a un contrôleur de navigation à l'écran:
la source
UIWindow
ne répond pas. Quelque chose à voir avec lewindowLevel
probablement. Comment puis-je le rendre réactif?alertWindow
ànil
lorsque vous avez terminé avec elle.En ajoutant à la réponse de Zev (et en revenant à Objective-C), vous pourriez rencontrer une situation où votre contrôleur de vue racine présente un autre VC via un enchaînement ou autre chose. L'appel de PresentViewController sur le VC racine se chargera de cela:
Cela a résolu un problème que j'avais lorsque le VC racine avait migré vers un autre VC, et au lieu de présenter le contrôleur d'alerte, un avertissement comme ceux rapportés ci-dessus a été émis:
Je ne l'ai pas testé, mais cela peut également être nécessaire si votre VC racine se trouve être un contrôleur de navigation.
la source
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
si ce n'est pas nul, sinon utilisezrootViewController
. Pour une solution entièrement générique, il peut être nécessaire de parcourir la chaîne depresentedViewController
s pour arriver autopmost
VCLa réponse de @ agilityvision a été traduite en Swift4 / iOS11. Je n'ai pas utilisé de chaînes localisées, mais vous pouvez facilement changer cela:
la source
window.backgroundColor = UIColor.clear
corrigé cela.viewController.view.backgroundColor = UIColor.clear
ne semble pas nécessaire.UIAlertController
sousThe 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.
Créer une extension comme dans la réponse Aviel Gross. Ici, vous avez l'extension Objective-C.
Ici, vous avez un fichier d'en-tête * .h
Et implémentation: * .m
Vous utilisez cette extension dans votre fichier d'implémentation comme ceci:
la source
Cross poster ma réponse car ces deux fils ne sont pas marqués comme dupes ...
Maintenant que cela
UIViewController
fait partie de la chaîne de répondeurs, vous pouvez faire quelque chose comme ceci:la source
La réponse de Zev Eisenberg est simple et directe, mais elle ne fonctionne pas toujours, et elle peut échouer avec ce message d'avertissement:
Cela est dû au fait que le rootViewController Windows n'est pas en haut des vues présentées. Pour corriger cela, nous devons remonter la chaîne de présentation, comme indiqué dans mon code d'extension UIAlertController écrit dans Swift 3:
Mises à jour le 15/09/2017:
Testé et confirmé que la logique ci-dessus fonctionne toujours très bien dans la nouvelle graine iOS 11 GM. Cependant, la méthode la plus votée par agilityvision ne le fait pas: la vue d'alerte présentée dans une nouvelle version
UIWindow
est en dessous du clavier et empêche potentiellement l'utilisateur d'appuyer sur ses boutons. En effet, dans iOS 11, tous les niveaux de fenêtre supérieurs à ceux de la fenêtre du clavier sont abaissés à un niveau inférieur.Un des artefacts de la présentation
keyWindow
est cependant l'animation du clavier glissant vers le bas lorsque l'alerte est présentée et remontant lorsque l'alerte est rejetée. Si vous souhaitez que le clavier y reste pendant la présentation, vous pouvez essayer de présenter à partir de la fenêtre supérieure elle-même, comme indiqué dans le code ci-dessous:La seule partie moins importante du code ci-dessus est qu'il vérifie le nom
UIRemoteKeyboardWindow
de la classe pour s'assurer que nous pouvons également l'inclure. Néanmoins, le code ci-dessus fonctionne très bien dans les semences GM iOS 9, 10 et 11, avec la bonne teinte et sans les artefacts coulissants du clavier.la source
Swift 4+
Solution que j'utilise depuis des années sans aucun problème. Tout d'abord, j'étends
UIWindow
pour trouver qu'il est visibleViewController. REMARQUE : si vous utilisez des classes de collection * personnalisées (telles que le menu latéral), vous devez ajouter un gestionnaire pour ce cas dans l'extension suivante. Après avoir obtenu la plupart des contrôleurs de vue, il est facile de présenterUIAlertController
commeUIAlertView
.la source
Pour iOS 13, en s'appuyant sur les réponses de mythicalcoder et bobbyrehm :
Dans iOS 13, si vous créez votre propre fenêtre pour présenter l'alerte, vous devez conserver une référence forte à cette fenêtre, sinon votre alerte ne s'affichera pas car la fenêtre sera immédiatement désallouée lorsque sa référence quittera la portée.
De plus, vous devrez redéfinir la référence sur zéro après la fermeture de l'alerte afin de supprimer la fenêtre pour continuer à permettre l'interaction de l'utilisateur sur la fenêtre principale en dessous.
Vous pouvez créer une
UIViewController
sous - classe pour encapsuler la logique de gestion de la mémoire de fenêtre:Vous pouvez l'utiliser tel
UIAlertController
quel , ou si vous voulez une méthode pratique sur votre , vous pouvez la jeter dans une extension:la source
dismiss
directement le WindowAlertPresentationControlleralert.presentingViewController?.dismiss(animated: true, completion: nil)
Manière abrégée de présenter l'alerte dans Objective-C:
Où
alertController
est tonUIAlertController
objet.REMARQUE: vous devrez également vous assurer que votre classe d'assistance s'étend
UIViewController
la source
Si quelqu'un est intéressé, j'ai créé une version Swift 3 de @agilityvision answer. Le code:
la source
Avec cela, vous pouvez facilement présenter votre alerte comme ça
Une chose à noter est que s'il y a un UIAlertController actuellement affiché,
UIApplication.topMostViewController
retournera unUIAlertController
. Présenter au-dessus d'unUIAlertController
a un comportement étrange et doit être évité. En tant que tel, vous devez soit vérifier manuellement cela!(UIApplication.topMostViewController is UIAlertController)
avant de présenter, soit ajouter unelse if
cas pour retourner nil siself is UIAlertController
la source
Vous pouvez envoyer la vue ou le contrôleur actuel en tant que paramètre:
la source
Kevin Sliech a fourni une excellente solution.
J'utilise maintenant le code ci-dessous dans ma sous-classe principale UIViewController.
Une petite modification que j'ai faite était de vérifier si le meilleur contrôleur de présentation n'est pas un simple UIViewController. Sinon, ce doit être un VC qui présente un VC simple. Ainsi, nous renvoyons le VC qui est présenté à la place.
Semble tout fonctionner jusqu'à présent dans mes tests.
Merci Kevin!
la source
En plus de bonnes réponses données ( agilityvision , adib , malhal ). Pour atteindre un comportement de mise en file d'attente comme dans les bons vieux UIAlertViews (éviter le chevauchement des fenêtres d'alerte), utilisez ce bloc pour observer la disponibilité au niveau de la fenêtre:
Exemple complet:
Cela vous permettra d'éviter le chevauchement des fenêtres d'alerte. La même méthode peut être utilisée pour séparer et mettre en file d'attente des contrôleurs de vue pour un nombre quelconque de couches de fenêtre.
la source
J'ai essayé tout ce qui a été mentionné, mais sans succès. La méthode que j'ai utilisée pour Swift 3.0:
la source
Certaines de ces réponses n'ont fonctionné qu'en partie pour moi, les combiner dans la méthode de classe suivante dans AppDelegate était la solution pour moi. Il fonctionne sur iPad, dans les vues UITabBarController, dans UINavigationController, en lors de la présentation des modaux. Testé sur iOS 10 et 13.
Usage:
la source
Prise en charge des scènes iOS13 (lors de l'utilisation de UIWindowScene)
la source
Vous pouvez essayer d'implémenter une catégorie
UIViewController
avec un mehtod comme- (void)presentErrorMessage;
Et et à l'intérieur de cette méthode, vous implémentez UIAlertController, puis la présentezself
. Que dans votre code client, vous aurez quelque chose comme:[myViewController presentErrorMessage];
De cette façon, vous éviterez les paramètres inutiles et les avertissements indiquant que la vue n'est pas dans la hiérarchie des fenêtres.
la source
myViewController
dans le code où la mauvaise chose arrive. C'est dans une méthode utilitaire qui ne sait rien du contrôleur de vue qui l'a appelé.UIAlertView
m'a amené à enfreindre cette règle à quelques endroits.Il existe 2 approches que vous pouvez utiliser:
-Utilisation
UIAlertView
ou 'UIActionSheet' à la place (non recommandé, car il est obsolète dans iOS 8 mais cela fonctionne maintenant)- Souvenez-vous du dernier contrôleur de vue qui est présenté. Voici un exemple.
Usage:
la source
J'utilise ce code avec quelques petites variations personnelles dans ma classe AppDelegate
la source
Semble fonctionner:
la source
créer la classe d'assistance AlertWindow et l'utiliser comme
la source