Mon problème est que je souhaite afficher un écran de chargement pour l'invite de notification Push initiale "L'application souhaite vous envoyer des notifications Push."
Ainsi, si l'utilisateur clique sur yes
, je peux continuer et lancer l'application dans les méthodes de délégation alors invoquées:
- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
[self hideLoadingScreen];
}
- (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error
{
[self hideLoadingScreen];
}
Cependant, si l'utilisateur clique sur no
, aucune de ces méthodes n'est appelée, ce qui est logique. Ma question est la suivante: existe-t-il une méthode de délégation différente qui est congédiée s’il refuse?
Mon problème est que si no
est sélectionné, les écrans de chargement ne disparaissent jamais. J'ai donc besoin de savoir quand l’utilisateur a fini la sélection.
Dans iOS 7, lorsque l'invite de notification Push du système apparaît, l'application devient inactive et UIApplicationWillResignActiveNotification se déclenche. De même, lorsque l'utilisateur répond à l'invite (en appuyant sur Oui ou sur Non), l'application redevient active et UIApplicationDidBecomeActiveNotification se déclenche.
Vous pouvez donc écouter cette notification, puis masquer votre écran de chargement.
Remarque: tant que l'invite est affichée, le bouton Accueil, le Centre de notifications et le Centre de contrôle sont désactivés. Ils ne peuvent donc pas déclencher une UIApplicationDidBecomeActiveNotification faux positif. Cependant, si l'utilisateur appuie sur le bouton Verrouiller, cela déclenchera UIApplicationDidBecomeActiveNotification.
Vous pouvez toujours obtenir les types de notification autorisés actuels à partir de:
UIRemoteNotificationType notificationTypes = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
Gardez à l'esprit que l'utilisateur peut également désactiver la notification dans les paramètres du téléphone.
Si vous vérifiez cela sur didRegisterForRemoteNotificationsWithDeviceToken, vous devriez voir si les types que vous avez demandés sont activés.
Voici comment je l’ai fait dans Swift 3. L’important est de garder l’état interne du cycle de vie de l’application. Lorsque l'invite Push est présentée, l'application démissionne, mais n'entre pas en arrière-plan. Tout cela dans mon AppDelegate.Swift.
Ceci est un très gros bidouillage et n'est pas recommandé en production. Apple pourrait changer la manière dont ces alertes sont présentées et cela pourrait casser à tout moment. Ceci a été testé avec différents iPhones et iPads fonctionnant sous iOS 9 et 10.
/// An internal value used to track application lifecycle state
enum ApplicationLifecycleState {
case willResignActive
case didEnterBackground
case willEnterForeground
case didBecomeActive
case unknown
}
/// This is used purely for tracking the application lifecycle for handling the system Push notification alert
var internalLifecycleState: ApplicationLifecycleState = .unknown {
didSet {
// If we're not in the middle of asking for Push permissions, none of the below applies, just bail out here
if !isAskingForPushPermissions { return }
// WARNING: Application lifecycle trickery ahead
// The normal application lifecycle calls for backgrounding are as follows:
// applicationWillResignActive -> applicationDidEnterBackground -> applicationWillEnterForeground -> applicationDidBecomeActive
// However, when the system Push notification alert is presented, the application resigns active, but does not enter the background:
// applicationWillResignActive -> [user taps on alert] -> applicationDidBecomeActive
// We can use this discrepancy to our advantage to detect if the user did not allow Push permissions
// If applicationDidBecomeActive
// AND the previous state was applicationWillResignActive
// AND the notification types bitmask is 0, we know that the user did not allow Push permissions
// User denied permissions
if internalLifecycleState == .didBecomeActive
&& oldValue == .willResignActive
&& UIApplication.shared.currentUserNotificationSettings?.types.rawValue == 0 {
// We're done
firePushCompletionBlockAndCleanup(registered: false)
} else {
// The state below can only be entered on iOS 10 devices.
// If the user backgrounds the app while the system alert is being shown,
// when the app is foregrounded the alert will dismiss itself without user interaction.
// This is the equivalent of the user denying Push permissions.
// On iOS versions below 10, the user cannot background the app while a system alert is being shown.
if #available(iOS 10, *), internalLifecycleState == .didBecomeActive {
firePushCompletionBlockAndCleanup(registered: false)
}
}
}
}
/// Used internally to track if the system Push notification alert is currently being presented
var isAskingForPushPermissions = false
typealias PushNotificationRegistrationCompletionBlock = ((_ registered: Bool) -> Void)
// ...
func applicationWillResignActive(_ application: UIApplication) {
internalLifecycleState = .willResignActive
}
func applicationDidEnterBackground(_ application: UIApplication) {
internalLifecycleState = .didEnterBackground
}
func applicationWillEnterForeground(_ application: UIApplication) {
internalLifecycleState = .willEnterForeground
}
func applicationDidBecomeActive(_ application: UIApplication) {
internalLifecycleState = .didBecomeActive
}
// ...
func setupPushNotifications(_ application: UIApplication = UIApplication.shared, completion: @escaping PushNotificationRegistrationCompletionBlock) {
isAskingForPushPermissions = true
pushCompletionBlock = completion
let settings = UIUserNotificationSettings(types: [.alert, .sound, .badge], categories: nil)
application.registerUserNotificationSettings(settings)
application.registerForRemoteNotifications()
}
fileprivate func firePushCompletionBlockAndCleanup(registered: Bool) {
pushCompletionBlock?(registered)
pushCompletionBlock = nil
isAskingForPushPermissions = false
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
// application:didRegisterForRemoteNotificationsWithDeviceToken may be called more than once (once for each notification type)
// By checking that the notification types bitmask is greater than 0, we can find the final time this is called (after the user actually tapped "allow")
// If the user denied Push permissions, this function is never called with a positive notification type bitmask value
if UIApplication.shared.currentUserNotificationSettings?.types.rawValue ?? 0 > 0 {
firePushCompletionBlockAndCleanup(registered: true)
}
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("Failed to register for notifications with error: " + error.localizedDescription)
firePushCompletionBlockAndCleanup(registered: false)
}
Usage:
appDelegate.setupPushNotifications(completion: { [weak self] (registered) in
// If registered is false, the user denied permissions
})
Pour Swift 3 et Swift 4.0 Utilisation de NotificationCenter et de la méthode AppDelegate didRegister notificationSettings
. NotificationSettings indique si les utilisateurs ont opté pour les badges, les sons, etc. et constituera un tableau vide s'ils refusent les notifications Push. Il est déclenché spécifiquement lorsque les utilisateurs répondent à l'invite de notifications Push et semble être ce que la plupart des développeurs utilisent, car il est plus spécifique que de vérifier didBecomeActive. Mais Apple pourrait changer cela. Qui sait?
Malheureusement, NotificationCenter n’ayant pas de nom de notification prédéfini, vous devez soit configurer et étendre (voir fin), soit utiliser la valeur brute dans (le responsable de la sécurité dispose de plus d'informations à ce sujet).
Dans AppDelegate:
func application(_ application: UIApplication, didRegister notificationSettings: UIUserNotificationSettings) {
// if not registered users will have an empty set of settings
let accepted: Bool = !notificationSettings.types.isEmpty
NotificationCenter.default.post(name: Notification.Name(rawValue: "didRespondToPrompt"), object: self, userInfo: ["didAccept" : accepted])
}
Observez ensuite chaque fois que vous en avez besoin, par exemple dans un contrôleur de vue:
class MyViewController: UIViewController {
//MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(MyViewController.didRespondToPushPrompt(_:)), name: NSNotification.Name(rawValue: "didRespondToPrompt"), object: nil)
}
@objc func didRespondToPushPrompt(_ notification: Notification) {
if let userInfo: [AnyHashable : Any] = notification.userInfo, let didAccept: Bool = userInfo[NSNotificationKeyNames.didAccept] as? Bool, !didAccept {
//if user doesn't accept, do this...
} else {
//all other situations code goes here
}
}
}
Quelques choses: Premièrement, pour Swift 4.0, j'utilise "@objc" devant une méthode, mais ce n'est pas nécessaire pour Swift 3.
En outre, pour utiliser NotificationCenter, en pratique, je n'ai pas utilisé "rawValue". Au lieu de cela, j'ai fait une extension comme ceci:
import Foundation
extension NSNotification.Name {
static let DidRegisterForPushNotifications = NSNotification.Name("DidRegisterForPushNotifications")
}
Que je pourrais ensuite utiliser comme ceci:
NotificationCenter.default.post(name: Notification.Name.DidRegisterForPushNotifications, object: self, userInfo: ["didAccept" : myBool])
.__ etc., etc.
Ne pourriez-vous pas simplement faire ce qui suit:
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
BOOL pushEnabled = notificationSettings.types & UIUserNotificationTypeAlert;
}
Cette méthode doit être le rappel de cette invite de notifications Push, et à partir de là, vous pouvez vérifier le masque de masque pour voir si les notifications Push ont été activées ou non.
Certaines des réponses ici ne sont plus pertinentes, ou sont plus compliquées qu’elles ne le devraient, puisque le cadre UserNotifications et iOS 10 vous permettent d’obtenir facilement ces données de la manière suivante:
let center = UNUserNotificationCenter.current()
// Request permission to display alerts and play sounds.
center.requestAuthorization(options: [.alert, .sound])
{ (granted, error) in
// Enable or disable features based on authorization.
}
Je suppose que vous pouvez avoir une variable BOOL pour la vérifier dans votre AppDelegate, car il semble n'y avoir aucun autre moyen que d'utiliser des API externes. Voir this .
AppDelegate.m
// declare a BOOL
BOOL allow = NO;
- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
allow = YES;
[self hideLoadingScreen];
}
- (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error
{
allow = YES;
[self hiedLoadingScreen];
}
Maintenant, j'imagine que vous pouvez accéder à cette variable BOOL pour différencier les cas où l'option Ne pas autoriser est activée ou non.
Voici un exemple de code Swift 2 pour vous les gars ... C’est un peu compliqué, mais j’espère que mes commentaires vous aideront à comprendre.
Définir les variables
var appDidBecomeActiveCount = 0
var userDefaults:NSUserDefaults!
AppDelegate - didFinishLaunchingWithOptions
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
userDefaults = NSUserDefaults.standardUserDefaults()
if userDefaults.valueForKey("FirstLaunche") == nil {
userDefaults.setBool(true, forKey: "FirstLaunche")
userDefaults.synchronize()
}
// Register for notification
//iOS 8+
let settings:UIUserNotificationSettings = UIUserNotificationSettings(forTypes: [UIUserNotificationType.Alert , UIUserNotificationType.Badge ,UIUserNotificationType.Sound], categories: nil)
UIApplication.sharedApplication().registerUserNotificationSettings(settings)
UIApplication.sharedApplication().registerForRemoteNotifications()
}
AppDelegate - applicationDidBecomeActive
func applicationDidBecomeActive(application: UIApplication) {
//Delay until alert get dismissed and notification type setted in app
delay(0.5, closure: { () -> () in
self.checkTheDilemma()
})
}
//I love this short method <3_<3
func delay(delay:Double, closure:()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(delay * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(), closure)
}
Action de contrôle
func checkTheDilemma (){
//Checking if this user turned off Push notifications or didn't allow it at all
let notificationType = UIApplication.sharedApplication().currentUserNotificationSettings()?.types
if userDefaults.valueForKey("FirstLaunche") as! Bool == true {
//User now is asked for notification permission because it's app's first launche
// if appDidBecomeActiveCount == 0 --> Pop up message will appeare
// if appDidBecomeActiveCount == 1 --> Pop up message dismissed
// if notificationType?.rawValue == 0 --> Notifications off
// if notificationType?.rawValue > 0 --> Notifications on
if notificationType?.rawValue == 0
&& appDidBecomeActiveCount == 1 { //If user disabled notifications from pop up alert
// ** User just tapped "Don't allow" btn :\
// Do what ever you are here for
//Now set FirstLaunche = false
userDefaults.setBool(false, forKey: "FirstLaunche")
userDefaults.synchronize()
}
} else {
if notificationType?.rawValue == 0
&& appDidBecomeActiveCount == 0 { // This guy is not registered for Push notification
// ** User disabled notifications in past (because this is not his first launch)
}
}
appDidBecomeActiveCount++
}
Vous pouvez détecter si l'utilisateur a annulé l'invite de notification dans la méthode didRegisterUserNotificationSettings
qui se déclenche après l'appel de registerForRemoteNotificationTypes
en vérifiant le notificationSettings.types
.
Si vous avez demandé un certain nombre de paramètres mais que notificationSettings.types == UIUserNotificationTypeNone
signifie, cet utilisateur a annulé l'invite.
Mais n'oubliez pas que la méthode registerForRemoteNotificationTypes
est maintenant obsolète!