Open a ViewController from remote notification - swift

I try to open a particular ViewController when my app catch a remote notification.
Let me show my project's architecture.
Here my storyboard :
When I receive a notification I want open a "SimplePostViewController", so this is my appDelegate :
var window: UIWindow?
var navigationVC: UINavigationController?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let notificationTypes: UIUserNotificationType = [UIUserNotificationType.Alert, UIUserNotificationType.Badge, UIUserNotificationType.Sound]
let pushNotificationSettings = UIUserNotificationSettings(forTypes: notificationTypes, categories: nil)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
self.navigationVC = storyboard.instantiateViewControllerWithIdentifier("LastestPostsNavigationController") as? UINavigationController
application.registerUserNotificationSettings(pushNotificationSettings)
application.registerForRemoteNotifications()
return true
}
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
if let postId = userInfo["postId"] as? String {
print(postId)
let api = EVWordPressAPI(wordpressOauth2Settings: Wordpress.wordpressOauth2Settings, site: Wordpress.siteName)
api.postById(postId) { post in
if (post != nil) {
self.navigationVC!.pushViewController(SimplePostViewController(), animated: true)
} else {
print("An error occurred")
}
}
}
}
I save my UINavigationViewController when the app is launch and simply try to push a new SimplePostViewController when I receive a notification. But nothing happen.
I placed breakpoints and seen that my pushViewController method was reached, but not the ViewWillAppear of my SimplePostViewController.
I also used the "whats new" view add perform my segue but nothing happen too.
Solution :
for child in (self.rootViewController?.childViewControllers)! {
if child.restorationIdentifier == "LastestPostsNavigationController" {
let lastestPostsTableViewController = (child.childViewControllers[0]) as! LastestPostsTableViewController
let simplePostVC = (self.storyboard?.instantiateViewControllerWithIdentifier("PostViewController"))! as! PostViewController
simplePostVC.post = post
lastestPostsTableViewController.navigationController?.pushViewController(simplePostVC, animated: true)
}
}
I use :
child.childViewControllers[0]
because I've only one child in my example.

I created a sample project with a local notification instead of a remote notification for ease of showing the functionality but it should be as simple as setting the root view controller of the window in the app delegate didreceiveremote notification.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Subscribe for notifications - assume the user chose yes for now
application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound], categories: nil))
return true
}
func applicationDidEnterBackground(application: UIApplication) {
//Crete a local notification
let notification = UILocalNotification()
notification.alertBody = "This is a fake notification"
notification.fireDate = NSDate(timeIntervalSinceNow: 2)
UIApplication.sharedApplication().scheduleLocalNotification(notification)
}
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
let sb = UIStoryboard(name: "Main", bundle: nil)
let otherVC = sb.instantiateViewControllerWithIdentifier("otherVC") as! OtherViewController
window?.rootViewController = otherVC;
}
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
//Your code here
}
`
You need to worry about managing your view hierarchy and sending anything to it that you need to send from the notification user data.
In my example, I create a local notification when you close the app that fires after a view seconds. If you then launch the app from the notification, it will open the "other view controller" which would be the "SimplePostViewController" in your case.
Also, be sure that you are registering for remote notifications in the didFinishLaunchWithOptions.
Github very simple sample : https://github.com/spt131/exampleNotificationResponse

Related

"Remember User" in SignIn Screen does not direct to Main Screen in Swift. (window is nil)

After Sign In I want to remember user and should go to the main ViewController but it doesnt. I tried to check that my code works or not it seems work but it does nothing. I know it is working because when I write the string of "with Identifier", it gives error immediately but If I write "with Identifier" string wrong then it gives error. I put print to understand and it says "window is nil" all the time. By the way in app delegate I did:
var window: UIWindow?
Here is my code:
let user : String? = UserDefaults.standard.string(forKey: "username")
if user != nil {
let board : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let myTag = board.instantiateViewController(withIdentifier: "myTags") as! mainBeaconList
print("mainBeaconList: \(myTag)")
if let window = window {
print("window: \(window)")
window.rootViewController = myTag
} else {
print("window is nil")
}
}
You might be missing to set window
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
self.window = UIWindow(frame: UIScreen.main.bounds)
//Rest of Initialization
}

The default Firebase app has not yet been configured but FirebaseApp.configure() added to appdelegate's didFinishLaunchingWithOptions method

I am trying to configure the firebase cloud messaging on my ios App.
As per instruction of Firebase docs, I have added firebase configuration like this (appdelegate.swift)
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
IQKeyboardManager.shared.enable = true
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: { _, _ in }
)
} else {
let settings: UIUserNotificationSettings =
UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
application.registerUserNotificationSettings(settings)
}
application.registerForRemoteNotifications()
Messaging.messaging().isAutoInitEnabled = true
Messaging.messaging().delegate = self
Messaging.messaging().token { token, error in
if let error = error {
print("Error fetching FCM registration token: \(error)")
} else if let token = token {
self.fcmId = token
print("\n My FCM registration token: \(token) \n")
}
}
FirebaseApp.configure()
//For check update
Siren.shared.wail()
if UserDefaults.standard.value(forKey: "login") as? String == "yes" {
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let nextViewController = storyBoard.instantiateViewController(withIdentifier: "MainViewController") as! MainViewController
let navigationController = UINavigationController(rootViewController: nextViewController)
let appdelegate = UIApplication.shared.delegate as! AppDelegate
appdelegate.window!.rootViewController = navigationController
self.window?.makeKeyAndVisible()
}else{
let mainStoryboardIpad : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewControlleripad : UIViewController = mainStoryboardIpad.instantiateViewController(withIdentifier: "goToLogin") as! LoginViewController
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = initialViewControlleripad
self.window?.makeKeyAndVisible()
}
return true
}
when I run the app on iPhone 6, I am getting.
[Firebase/Core][I-COR000003] The default Firebase app has not yet been configured. Add FirebaseApp.configure() to your application initialization. This can be done in in the App Delegate's application(_:didFinishLaunchingWithOptions:)(or the#main` struct's initializer in SwiftUI).
Move FirebaseApp.configure() at the top, as this should be called first before calling any Firebase method.
I know this is an old post, but it looks like there isn't the right solution yet.
Sometimes it doesn't help to just put FirebaseApp.configure() in the top of didFinishLaunchingWithOptions.
Just place your FirebaseApp.configure() in the init() of the AppDelegate.
So you can be sure that Firebase will be configured first.
class AppDelegate: UIResponder, UIApplicationDelegate {
override init() {
FirebaseApp.configure()
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
}
}
I know this is an old post but I'm writing this because I was struggling with this also.
I had an init() in the view that opens as a default view that fetched data from FireBase, and basically, it called that init before calling the FireBase configuration function and it caused that error.

Handle background push notification on iOS side of Flutter's Firebase Messaging

Until the moment of writing the FCM Flutter plugin did not implement a background handling for push notifications on iOS.
I'm trying to implement using native code(Swift) but I'm facing some difficulties.
This is my AppDelegate.swift:
import UIKit
import Flutter
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
}
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let _platformChannel = FlutterMethodChannel(name: "br.uff.uffmobileplus/uffmobile_channel",
binaryMessenger: controller as! FlutterBinaryMessenger)
_platformChannel.setMethodCallHandler({
//omitted code
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
override func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let notificationChannel = FlutterMethodChannel(name: "br.uff.uffmobileplus/notification_channel", binaryMessenger: controller as! FlutterBinaryMessenger)
notificationChannel.invokeMethod("saveToDataBase", arguments: userInfo)
completionHandler(UIBackgroundFetchResult.newData)
}
}
(I omitted some non relevant code)
I saw some native iOS implementations and they did something near this.I'm not a Swift programmer so don't know at all if this is correct.
What is happening is that the
didReceiveRemoteNotification
is not being called when a remote data or notification message arrives.
I'm using the platform_channel to communicate between dart and swift code.
This is the json data message:
"\"data\": {"
"\"body\": \"$body\","
"\"title\": \"$title\","
"\"route\": \"$route\","
"\"sender\": \"$sender\","
"\"click_action\": \"FLUTTER_NOTIFICATION_CLICK\","
"\"mutable_content\": true,"
"\"content_available\": true"
"}, "
"\"priority\": \"high\","
"\"to\": "
"\"/topics/$group\""
Yes it's written weirdly but works because it's triggering the onMessage with correct information.
What I want is to do a background work (save on my local DB) when the data message arrives.

iOS firebase: FIRAuthUIDelegate.authUI not being called

I am trying to launch google login from AppDelegate.swift and then launch my app's main screen upon login success.
I am able to
show the google login button as shown above
the user is sent to google to sign in
the user is sent back to original (step 1)
After step 3. I'd like to send the user to my app's main page.
My code is below. The problem I'm having is that authUI is not being called.
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, FIRAuthUIDelegate {
var window: UIWindow?
var authUI: FIRAuthUI?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
FIRApp.configure()
authUI = FIRAuthUI.defaultAuthUI()
authUI?.delegate = self
let providers: [FIRAuthProviderUI] = [FIRGoogleAuthUI()]
authUI?.providers = providers
// show google login button
let authViewController = authUI?.authViewController()
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window?.rootViewController = authViewController
self.window?.makeKeyAndVisible()
return true
}
func application(application: UIApplication, openURL url: NSURL, options: [String: AnyObject]) -> Bool {
return GIDSignIn.sharedInstance().handleURL(url, sourceApplication: options[UIApplicationOpenURLOptionsSourceApplicationKey] as? String, annotation: options[UIApplicationOpenURLOptionsAnnotationKey])
}
func authUI(authUI: FIRAuthUI, didSignInWithUser user: FIRUser?, error: NSError?) {
// launch main view controller
}
}
EDIT: This appears to be a duplicate of another question. The other question's title is quite general and only gets to the details a few lines deep. In any case, I believe Chris's answer is more thorough than the one there. I think both the question and answers here are clearer, more pointed and more thorough so it would be a mistake to just direct people here to go there as would happen if this was marked as a duplicate.
I think your problem lies here, in the - (void)signInWithProviderUI:(id<FIRAuthProviderUI>)providerUI method.
The delegate method is called in the dismissViewControllerAnimated:completion: completion block.
[self.navigationController dismissViewControllerAnimated:YES completion:^{
[self.authUI invokeResultCallbackWithUser:user error:error];
}];
As you can see from the Apple docs, this method is expected to be called on a modally presented viewController. You are displaying it as a root view controller. Try displaying it with a modal from a UIViewController, and things should work out. To debug this try and set a breakpoint at line 193 to see that it won't get hit. I would be very surprised if this doesn't work when you display the authController modally.
To come up with a possible solution to your problem (I am assuming you want to ensure a user is signed in before using your app). The below is a simplification of what I am using in an app currently.
EDIT: Updated for the new 1.0.0 FirebaseUI syntax.
class MainTabController: UITabBarController, FIRAuthUIDelegate {
let authUI: FUIAuth? = FUIAuth.defaultAuthUI()
override func viewDidLoad() {
super.viewDidLoad()
var authProviders = [FUIFacebookAuth(), FUIGoogleAuth()]
authUI.delegate = self
authUI.providers = authProviders
//I use this method for signing out when I'm developing
//try! FIRAuth.auth()?.signOut()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !isUserSignedIn() {
showLoginView()
}
}
private func isUserSignedIn() -> Bool {
guard FIRAuth.auth()?.currentUser != nil else { return false }
return true
}
private func showLoginView() {
if let authVC = FUIAuth.defaultAuthUI()?.authViewController() {
present(authVC, animated: true, completion: nil)
}
}
func authUI(_ authUI: FUIAuth, didSignInWith user: FIRUser?, error: Error?) {
guard let user = user else {
print(error)
return
}
...
}
It must be a problem of reference.
class AppDelegate: UIResponder, UIApplicationDelegate, FIRAuthUIDelegate {
var window: UIWindow?
let authUI = FIRAuthUI.defaultAuthUI()
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
FIRApp.configure()
authUI.delegate = self
let providers: [FIRAuthProviderUI] = [FIRGoogleAuthUI()]
authUI.providers = providers
// show google login button
let authViewController = authUI.authViewController()
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window?.rootViewController = authViewController
self.window?.makeKeyAndVisible()
return true
}
}
Try this. AppDelegate will hold the reference of authUI and its delegate.

How to read a variable value in the AppDelegate from a ViewController using Swift

I would like to read the value of a variable contained in the AppDelegate from a ViewController. The variable contains the Device Token used to enable iOS Push Notifications and I would like to show it in a UILabel.
This is my code so far:
AppDelegate.swift
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
internal var deviceTokenToPass: String?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let pushNotificationsTypes: UIUserNotificationType = UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound
let pushNotificationsSettings: UIUserNotificationSettings = UIUserNotificationSettings(forTypes: pushNotificationsTypes, categories: nil)
application.registerUserNotificationSettings(pushNotificationsSettings)
application.registerForRemoteNotifications()
return true
}
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
let chararacterSet: NSCharacterSet = NSCharacterSet(charactersInString: "<>")
self.deviceTokenToPass = (deviceToken.description as NSString).stringByTrimmingCharactersInSet(chararacterSet).stringByReplacingOccurrencesOfString(" ", withString: "", options: nil, range: nil) as String
}
}
ViewController.swift
import UIKit
class ViewController: UIViewController {
#IBOutlet var deviceTokenLabel : UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
var deviceToken = appDelegate.deviceTokenToPass
println(deviceToken) /* nil */
if deviceToken != nil {
self.deviceTokenLabel.text = deviceToken
} else {
self.deviceTokenLabel.numberOfLines = 4
self.deviceTokenLabel.text = "Cannot read your device token.\nYou must be using an iOS Simulator or you didn't allowed the application for push notifications"
}
}
}
The problem is that if I place the println(deviceToken) code in the AppDelegate.swift the device token is correctly displayed in the console, if I place it in the ViewController.swift it's value will be nil.
If you ensure that the property that you'd like to access is actually readable, then you can use the following:
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let anAttribute = appDelegate.theAttribute
The didRegisterForRemoteNotificationsWithDeviceToken notification is asynchronous (invoked after the device has been successfully registered), so what I think is happening is that the notification occurs after the view controller is displayed.
To check that, print it from both the app delegate and the view controller, and see which happens first.
As to how fix that, I would implement didFailToRegisterForRemoteNotificationsWithError, so that one of the 2 is invoked, then I would send a notification (via NSNotificationCenter) in both cases (success and failure), and store the outcome in a property.
In the view controller, I would subscribe for the custom notification, and show a progress view until a notification is received. But in viewDidLoad I'd also check for the outcome property set in the app delegate, just in case the registration is too fast and it's already been completed.
in app delegate
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
weak var label: UILabel? = nil
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
let chararacterSet: NSCharacterSet = NSCharacterSet(charactersInString: "<>")
let deviceTokenToPass = (deviceToken.description as NSString).stringByTrimmingCharactersInSet(chararacterSet).stringByReplacingOccurrencesOfString(" ", withString: "", options: nil, range: nil) as String
if let l = label {
l.text = deviceTokenToPass
}
}
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let pushNotificationsTypes: UIUserNotificationType = UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound
let pushNotificationsSettings: UIUserNotificationSettings = UIUserNotificationSettings(forTypes: pushNotificationsTypes, categories: nil)
application.registerUserNotificationSettings(pushNotificationsSettings)
application.registerForRemoteNotifications()
return true
}
}
in ViewController
class ViewController: UIViewController {
#IBOutlet weak var deviceTokenLabel : UILabel!
override func viewDidLoad() {
super.viewDidLoad()
if let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate {
appDelegate.label = deviceTokenLabel
}
}
}