【问题标题】:Controlling which view controller loads after receiving a push notification in SWIFT在 SWIFT 中接收推送通知后控制加载哪个视图控制器
【发布时间】:2015-10-15 03:32:57
【问题描述】:

一旦我收到推送通知并滑动打开它,它只会打开我的应用程序,而不是我想要的 VC。

所以我的问题是如何加载我想要的 VC?我知道如果应用程序打开了,我会将 VC 移到 didReceiveRemoteNotification 内的另一个位置,但是如果应用程序未打开,我该怎么办?或者如果它处于后台模式?

我还有两个不同的推送通知,因此我需要它来移动两个不同 VC 中的一个。如何区分不同的推送通知?

谢谢。

【问题讨论】:

    标签: push-notification apple-push-notifications appdelegate


    【解决方案1】:

    当你运行你的应用程序时,你正在调用 applicationDidLaunchWithOptions:

    UIApplication.sharedApplication().registerUserNotificationSettings ( UIUserNotificationSettings(forTypes: (UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound), categories: nil))
    
    
    
    if( launchOptions != nil){
        var notificationDict: AnyObject? = launchOptions?[UIApplicationLaunchOptionsRemoteNotificationKey]
        if(notificationDict != nil){
            handleNotification(notificationDict! as! [NSObject : AnyObject])
        }
    
    }
    

    在这里,你有 handleNotification 这基本上是我的自定义函数,我从通知中提取数据并使用该信息显示相应的控制器。

    这是一个例子:

    let notificationType = userInfo["aps"]!["alert"]!!["some-key-I-Need"]! as! String
    var storyboard = UIStoryboard(name: "Main", bundle: nil)
    let mainViewController = storyboard.instantiateInitialViewController() as! MyViewController
    self.window?.rootViewController  = mainViewController
    

    【讨论】:

    • 谢谢!虽然我还是有点迷茫,我是怎么提取数据的??
    • 好吧,您应该考虑通知,因为它是 json。正如您在代码的第二块中看到的那样,在第一行中我提取了“some-key-I-need”。如果您不确定通知中的内容,并相应地访问您的数据。你在这里有关于格式的一切:developer.apple.com/library/ios/documentation/…
    • 抱歉,我仍然迷路了?第一段代码是用来做什么的?第二个?有没有办法考虑我收到哪种类型的推送通知,然后从那里移动到正确的视图控制器....
    • 第一块代码是检查应用程序是否通过推送通知启动的部分 - 这是选项参数。在内部,我们检查这些选项是否实际上是推送通知或其他东西。如果我们确定我们有 PN,那么我调用 handleNotification 方法。在第二部分中,我只是向您展示如何从 PN 中提取一个键以及如何显示特定的视图控制器(您可以放置​​任何您想要的 VC)
    • 那么handleNotification函数里面有什么?
    【解决方案2】:

    除了@NickCatib 的回答,要查明您是否在应用运行时收到通知,如果是,在前台或后台,您需要在 AppDelegate 中使用此方法:

    func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
    
    
    // You can determine your application state by
    if UIApplication.sharedApplication().applicationState == UIApplicationState.Active {
    
    // Do something you want when the app is active
    
    } else {
    
    // Do something else when your app is in the background
    
    
    }
    }
    

    【讨论】:

    • 好点,这个 if-else 和我的回答涵盖了所有可能的操作 :) 这是一个 +1 :)
    【解决方案3】:

    为 Swift 4.2 更新

    如前所述,您想在 applicationDidLaunchWithOptions 中注册远程通知:

     func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
         let pushSettings = UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
        UIApplication.shared.registerUserNotificationSettings(pushSettings)
         UIApplication.shared.registerForRemoteNotifications()
    }
    

    当您从 lockScreen/Background 回来时,无法知道您将在哪个 viewController 中。我所做的是从 appDelegate 发送通知。当收到remoteNotification 时,会调用appDelegate 中的didReceiveRemoteNotification。

     func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
        let notif = JSON(userInfo) // SwiftyJSON required 
    

    根据您的通知负载,您应该首先确保它不为 nil,然后调用一个 NSNotification,该 NSNotification 将被应该捕获此通知的 viewControllers 捕获。这也是您可以根据收到的有效负载发布不同类型的通知的地方。可能是这个样子,举个例子吧:

    if notif["callback"]["type"] != nil {
      NotificationCenter.default.post(name: Notification.Name(rawValue: "myNotif"), object: nil)
      // This is where you read your JSON to know what kind of notification you received  
    
    }
    

    如果您收到消息通知并且由于令牌已过期而不再登录,则该通知将永远不会在视图控制器中捕获,因为它永远不会被监视。

    现在是在视图控制器中捕获通知的部分。在视图中会出现:

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        NotificationCenter.default.addObserver(self, selector: #selector(self.catchIt), name: NSNotification.Name(rawValue: "myNotif"), object: nil)
    }
    

    现在你添加了这个观察者,每次在这个控制器中调用一个通知,函数catchIt也会被调用。您必须在要实现特定操作的每个视图控制器中实现它。

    func catchIt(_ userInfo: Notification){
    
        let prefs: UserDefaults = UserDefaults.standard
        prefs.removeObject(forKey: "startUpNotif")
    
        if userInfo.userInfo?["userInfo"] != nil{
            let storyboard = UIStoryboard(name: "Main", bundle: nil)
            let vc: RedirectAppInactiveVC = storyboard.instantiateViewController(withIdentifier: "RedirectAppInactiveVC") as! RedirectAppInactiveVC
            self.navigationController?.pushViewController(vc, animated: true)
        } else {
            let storyboard = UIStoryboard(name: "Main", bundle: nil)
            let vc: RedirectAppActiveVC = storyboard.instantiateViewController(withIdentifier: "RedirectAppActiveVC") as! RedirectAppActiveVC
            self.navigationController?.pushViewController(vc, animated: true)
        }
    }
    

    离开视图控制器时不要忘记取消订阅通知,否则视图控制器,如果仍在堆栈中,将捕获通知并执行它(你可能想要这样做,但知道你的内容更安全正在进入)。所以我建议在viewWillDisappear中退订:

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillAppear(animated)
        NotificationCenter.default.removeObserver(self)
    }
    

    这样做,您将加载所需的 viewController。现在我们还没有处理所有的病例。如果您还没有打开您的应用程序怎么办。显然,没有加载任何 UIViewController,它们都无法捕捉到通知。您想知道您是否在 appDelegate 中的 didFinishLaunchingWithOptions: 中收到通知。我要做的是:

    let prefs: UserDefaults = UserDefaults.standard
    if let remoteNotification = launchOptions?[UIApplicationLaunchOptionsKey.remoteNotification] as? NSDictionary {
        prefs.set(remoteNotification as! [AnyHashable: Any], forKey: "startUpNotif")
        prefs.synchronize()
    }
    

    现在,您已设置首选项,说明应用程序是使用远程通知启动的。在应首先在应用程序中加载的控制器中,我建议在 viewDidAppear 中执行以下操作:

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        let prefs: UserDefaults = UserDefaults.standard
        if prefs.value(forKey: "startUpNotif") != nil {
            let userInfo: [AnyHashable: Any] = ["inactive": "inactive"]
            NotificationCenter.default.post(name: Notification.Name(rawValue: "myNotif"), object: nil, userInfo: userInfo as [AnyHashable: Any])
        }
    }
    

    希望对您有所帮助。我还制作了一个 GitHub 存储库来说明本地通知:Local Notifications Observer Pattern(类似于远程通知)。类似的逻辑可以使用根视图控制器Local Notifications Root Pattern来实现,我个人认为这取决于你要实现什么。

    这些示例用于说明如何简单地实现它。对于更大的项目,您最终会得到更复杂的架构,例如内部使用类似机制的协调器。

    【讨论】:

    • 这篇非常好的帖子做得很好,但我会避免使用 NSUserDefaults()。您可以在应用程序委托中设置 rootViewController 并在那里设置值。哦,请修复 prefs.removeObjectForKey("startUpNotification") 到 prefs.removeObjectForKey("startUpNotif") 因为这是您使用的密钥
    • 我认为你是对的,具体取决于上下文。如果您正在设置根视图控制器,您将永远无法访问应该在该视图之前的特定视图。例如,如果一个对话通知以 root 身份进行对话,然后您想弹出导航控制器返回,您将被卡住。我不确定我刚才说的,如果你直接这样改变根目录,你是否能够弹出?
    • 谢谢,虽然只是几个问题,但我仍然不确定您如何准确区分不同的推送通知?作为一个通知,我想移动到一个 VC 和一个通知到另一个?同样正如@NickCatib 所说,肯定只是 rootViewController 会比 NSUserDefaults 简单得多?
    • 并非如此,如果您需要显示特定的控制器,您可以根据需要将任意数量的控制器压入堆栈。您甚至可以使用实际需要的视图控制器设置导航堆栈。 @Henry Brown:你从那个 JSON 中提取数据,然后根据内容做不同的事情......
    • 好吧,这还有更多的研究,我想我快到了,谢谢你的帮助!
    【解决方案4】:

    我发现以上所有答案都非常有帮助。然而,当应用程序被停用时,投票最多的人对我不起作用。后来尝试实现@NickCatib 和@thefredelement 组合的答案,并且在执行storyboard.instantiateInitialViewController() 时产生错误-“无法转换'UINavigationController' 类型的值”。我发现这是因为我有一个带有 NavController 作为 rootViewController 的故事板文件。为了解决这个问题,我创建了一个新的导航控制器,它解决了一个问题:我丢失了应用程序的正确导航,并且视图甚至没有显示后退按钮。我的问题的答案是使用@NickCatib 和@thefredelement 答案,但是使用标识符实例化视图并推送它,使用rootViewController 作为UINavigationController,如下所示。

    let rootViewController = self.window?.rootViewController as! UINavigationController
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    let mvc = storyboard.instantiateViewControllerWithIdentifier("MyViewController") as! 
                 MyViewController
    rootViewController.pushViewController(mvc, animated: true)
    

    这对我来说很好用,而且我没有丢失应用的正确导航属性。

    【讨论】:

      【解决方案5】:

      前面答案的补充信息。

      根据你的状态,你可以改变你的逻辑。 在 didReceiveRemoteNotification 方法中:

      func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {}

      你可以这样切换

      let state = UIApplication.sharedApplication().applicationState switch state { case UIApplicationState.Active: case UIApplicationState.Inactive: case UIApplicationState.Background: }

      根据应用的当前状态执行您想要的操作。

      【讨论】:

        【解决方案6】:

        Swift Rabbit 的答案是最好的。

        我要补充一点,当应用程序从关闭状态变为活动状态时仍然缺少。

        您可以在 AppDelegate 中的 didFinishedLaunchingWithOptions 中添加:

        if let notification = launchOptions?[.remoteNotification] as? [AnyHashable : Any] {
        
                    notificationsUserInfo = notification as [AnyHashable : Any]
                    serveNotifications = true
        
                }
        

        您可以使用通知的值创建一个全局变量或用户默认值,并使用标志让应用程序的其余部分有通知。

        一旦 mainViewController 可见,您就可以执行操作来处理通知。

        override func viewDidAppear(_ animated: Bool) {
                if serveNotifications {
              notificationManager.sharedInstance.processNotification(userInfo: notificationsUserInfo)
        
                    serveNotifications = false
        
                }
        
            }
        

        保重。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-01-12
          • 1970-01-01
          • 2017-08-13
          • 1970-01-01
          相关资源
          最近更新 更多