I am having an issue where I am unable to access a function in my viewcontroller.swft file when lunching from a killed app in didFinishLaunchingWithOptions.
I am trying to access the function with viewController?.loadRequestnotificationwaiting(for: url as! String) from AppDelegate which I pass data to the viewcontroller where I can do some stuff. But when I place an alert in the function in the viewcontroller loadRequestnotificationwaiting. The data is not being passed.
Now I use this same method in other areas to pass data to the viewcontroller from the appdelegate and they work fine. It seems to not work when using it in didFinishLaunchingWithOptions
Is the viewcontroller not available yet when trying to access it from didFinishLaunchingWithOptions?
AppDelegate.swift
class AppDelegate : UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
weak var viewController : ViewController?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UNUserNotificationCenter.current().delegate = self
ConnectionManager.sharedInstance.observeReachability()
// Override point for customization after application launch.
FirebaseApp.configure()
registerForPushNotifications()
// opened from a push notification when the app is closed
if let userInfo = launchOptions?[.remoteNotification] as? [String : AnyObject] {
if let object = userInfo["aps"] {
let url = object["url"]
viewController?.loadRequestnotificationwaiting(for: url as! String)
}
}
return true
}
}
ViewController.swift
class ViewController: UIViewController, WKNavigationDelegate {
func loadRequestnotificationwaiting(for notification_url_wait : String) {
notification_url_final_wait = notification_url_wait
let url = URL(string: notification_url_final_wait!)
let URLrequest = URLRequest(url: url!)
self.webView.load(URLrequest)
}
}
You are relying on your AppDelegate's viewController property, but you are setting this property in your viewDidLoad method of your view controller; This is fine when a notification is received when your app is already running, since the viewController property is already set.
When a notification causes your application to be launched, the viewController property is not set and the function isn't called.
Assuming that the view controller you need is the initial view controller from your storyboard, you can get the root view controller from your app delegate's window property;
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UNUserNotificationCenter.current().delegate = self
ConnectionManager.sharedInstance.observeReachability()
// Override point for customization after application launch.
FirebaseApp.configure()
registerForPushNotifications()
// opened from a push notification when the app is closed
if let userInfo = launchOptions?[.remoteNotification] as? [String : AnyObject] {
if let object = userInfo["aps"],
let url = object["url"] as? String,
let viewController = self.window?.rootViewController as? ViewController {
viewController.loadRequestnotificationwaiting(for: url)
}
}
return true
}
if your view controller is root, you can summon it by call UIApplication.shared.keyWindow!.rootViewController! or overwrite it with your viewController
you can raise a notification using -[NotificationCenter postNotificationName:object:userInfo:] and observe this notification name in View Controller
Related
This question already has answers here:
loading url from scheme not processing first time - appdelegate vs viewcontroller
(2 answers)
Closed 3 years ago.
I would like to know how to call a function from another function within AppDelegate. It would be better to call this function from ViewController but could not get it to work.
I have in my AppDelegate.m the following code:
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
let geturl = url.host?.removingPercentEncoding;
UserDefaults.standard.set(geturl, forKey: "DeepLinkUrl")
return true
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
//I WANT CALL the upper function to set the URL IN HERE
return true
}
Since I don't know how to call the open url function from the ViewController.m I did this calling didFinishLaunchingWithOptions func from AppDelegate.m
My ViewController.m looks like:
#objc func appWillEnterForeground() {
print("app on foreground")
let appDelegate: AppDelegate? = UIApplication.shared.delegate as? AppDelegate
appDelegate?.application(UIApplication.shared, didFinishLaunchingWithOptions: nil)
//ACTUALLY I WANT TO CALL THE SET URL FUNCTION IN STEAD OF didFinishLaunchingWithOption BUT DON'T KNOW HOW. SO I FOUND THIS WHICH IS BEING CALLED
let user = UserDefaults.standard
if user.url(forKey: "DeepLinkUrl") != nil{
let str = user.value(forKey: "DeepLinkUrl") as! String
print(str)
}
}
Any ideas?
You don't call this method at all. It is called by the operating system when your application is launched. You are absolutely not supposed to ever call it yourself.
Same with the other method, which will be called by the operating system when your application is asked to open a URL. Which might be the URL of a file, or a URL with a scheme that you registered for.
Set a breakpoint on appDidFinishLaunching, then on your viewDidLoad method, start debugging to get some idea what is going on. You might also consider reading Apple's documentation.
I'm implementing 3D touch quick actions in my app and I have the following problem:
When the app is already running and so the quick action is initiated through perform Action For Shortcut Item, it works perfectly fine. However, when the app is killed and then launched through a quick action (so didFinishLaunchingWithOptions) it does not take me to the desired view controller, but rather to the home screen of the app.
Here is my code:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
//... other stuff I'm doing
if let shortcutItem = launchOptions?[UIApplication.LaunchOptionsKey.shortcutItem] as? UIApplicationShortcutItem {
shortcutItemToProcess = shortcutItem
}
return true
NOTE: I've read previous SO answers where they said that I need to return false in didFinishLaunchingWithOptions when the app was launched through a quick action, so that performAction won't get called. I need to always return true however in my didFinishLaunching method because of the other things I'm handling there. I tried however to return false just to see if that causes the problem and the app still behaved the same way.
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: #escaping (Bool) -> Void) {
shortcutItemToProcess = shortcutItem
}
Here is how I present the view controller:
func applicationDidBecomeActive(_ application: UIApplication) {
if let shortcutItem = shortcutItemToProcess {
if shortcutItem.type == "com.myName.MyApp.myQuickAction" {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let myViewController = storyboard.instantiateViewController(withIdentifier: "myViewController") as! MyViewController
if let navVC = window?.rootViewController as! UINavigationController? {
navVC.pushViewController(myViewController, animated: true)
}
}
So this works fine when app is already running, but it lands me on the home page of my app when the app is killed. What am I doing wrong and how can I solve this?
I am a self-taught newcomer to iOS programming and have worked through one beginner textbook. I'm trying to publish my first mobile application so I am going back through and cleaning up my code. The textbook I used stressed 'Dependency Injection' but I've struggled adapting their simple example to a more complex application.
The app operates as a shell and retrieves/parses txt files to populate. I successfully connected my model, which retrieves/parses the data, and the TableViewController that needs populated using the following code:
MyTableViewController {
var data: Data!
}
AppDelegate {
var window: UIWindow?
let data = Data()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let navCon = window!.rootViewController as! MyNavigationController
let tableVC = navCon.topViewController as! MyTableViewController
tableVC.data = data
return true
}
I then embedded that NavigationController within a TabBarController because the app will have other tabs. I tried the same process of setting the rootViewController and then drilling down until I could set my data variable, but I can't find the correct way to layer the ViewControllers and keep getting the error;
'fatal error: unexpectedly found nil while unwrapping an Optional value'
I tried two different approaches:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let tabBarCon = window!.rootViewController as! MyTabBarController
let navCon = tabBarCon.presentedViewController as! MyNavigationController
let tableVC = navCon.topViewController as! MyTableViewController
tableVC.data = data
return true
}
and
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let tabBarCon = window!.rootViewController as! MyTabBarController
let navCon = MyNavigationController()
tabBarCon.viewControllers = [navCon]
let tableVC = navCon.topViewController as! MyTableViewController
tableVC.data = data
return true
}
Is there a solution to correct this error or am I going about this process wrong? Again, I have a file that pulls in a txt file and then creates a dictionary. I need a separate TableViewController to be able to access that dictionary to populate itself, but I want to achieve this in the most efficient and apple promoted manner, not all in the same file as I did in my first design.
Thanks for the help!
For Dependency Injection I do recommend you use Typhoon. From mine experience it's one of the best tool. This will help you to achive app asembly like:
/*
* This is the definition for our AppDelegate. Typhoon will inject the specified properties
* at application startup.
*/
public dynamic func appDelegate() -> AnyObject {
return TyphoonDefinition.withClass(AppDelegate.self) {
(definition) in
definition.injectProperty("cityDao", with: self.coreComponents.cityDao())
definition.injectProperty("rootViewController", with: self.rootViewController())
}
}
I found the solution through this thread;
Aassigning a value to a view controller from AppDelegate.swift
The result can be achieved by reconfiguring the second solution I attempted;
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let tabBarCon = window!.rootViewController as! MyTabBarController
let tabBarRootVCs: Array = tabBarCon.viewControllers!
let navCon = tabBarRootVCs[0] as! MyNavigationController
let tableVC = navCon.topViewController as! MyTableViewController
tableVC.data = data
return true
}
I have an UITabBarController which presents in one tab a table of chats. If the user clicks on one row, a view controller will be displayed that presents the latest messages.
Now I am introducing Push notifications. The desired behavior would be to automatically go to the chat room if the user opens up the Push notification. Although there's plenty information available on how to handle these use cases, I do not succeed in implementing it.
Here's my code so far:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
if (!isUserLoggedIn()) {
// show login vc
} else {
let protectedController = mainStoryboard.instantiateViewController(withIdentifier: "TabBarController") as! UITabBarController
if let notificationPayload = launchOptions?[UIApplicationLaunchOptionsKey.remoteNotification] as? NSDictionary {
if let chatId = notificationPayload["chatId"] as? String {
print(chatId)
// show chat VC
}
}
window!.rootViewController = protectedController
window!.makeKeyAndVisible()
}
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .sound]) { (granted, error) in
// Enable or disable features based on authorization.
}
application.registerForRemoteNotifications()
return true
}
The print statement will never be called but that may be due to the fact that the application is completely closed before opening the notification.
didFinishLaunchingWithOptions is not necessarily called, when you click on a notification, only if your app is not in the memory anymore
In your willFinishLaunchingWithOptions function of your appDelegate, set up a delegate for the currentNotificationCenter
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
UNUserNotificationCenter.current().delegate = self
}
Make sure your AppDelegate implements UNUserNotificationCenterDelegate relevant for you could be this
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
if let chatID = userInfo["chatID"] as? String {
// here you can instantiate / select the viewController and present it
}
completionHandler()
}
Update to answer your follow-up question: How to add a UIViewController to a UINavigationController within your UITabbarController
In order to instantiate your ViewController and add it to your UITabbarController:
let myViewController = MyViewController()
guard let tabbarController = self.window.rootViewController as? UITabbarController else {
// your rootViewController is no UITabbarController
return
}
guard let selectedNavigationController = tabbarController.selectedViewController as? UINavigationController else {
// the selected viewController in your tabbarController is no navigationController!
return
}
selectedNavigationController.pushViewController(myViewController, animated: true)
When the user taps on a notification, callback method of the app delegate is:
application:didReceiveRemoteNotification:fetchCompletionHandler:
More information about notifications from Apple.
You should put your code inside this method.
Also you can create Router class (subclass of NSObject), which will show chat view controller and will be responsible for navigation between view controllers in application.
It's a good manner incapsulate this logic into separate class, not keeping it in the AppDelegate class.
I have scenario where my app receives an application:openURL:sourceApplication call from an extension and I want to open a tab in my tabBar and once its fully loaded then sendNext to a signal that the tabBar will be listening to once its loaded.
so sequence of events would be something like:
AppDelegate receives an OpenURL call
AppDelegate requests tabBar to load/open a tab called RemindersViewController
RemindersViewController RACSubject called RemindersLoadedSignal which i send to it a next value of true once the view has complete loading and binding
AppDelegate listens to the RemindersLoadedSignal and when it receives next from it it will emit its next signal to openURLSubject.
RemindersViewController listening to openURLSubject will perform some action since now its loaded and the signal was emitted.
class AppDelegate: UIResponder, UIApplicationDelegate {
let openURLSubject = RACSubject()
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
RemindersLoadedSignal.subscribeNextAs({ (loaded:NSBool) -> () in
println("This subscription works for some reason")
})
return true
}
func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject?) -> Bool {
myTabBar.selectTabWithClass(RemindersViewController.self)
//SendNext to openURLSubject once the tab is loaded
RemindersLoadedSignal.subscribeNextAs({ (loaded:NSBool) -> () in
println("This subscription Never gets called")
self.openURLSubject.sendNext(reminderURLRequest)
})
return true
}
}
let RemindersLoadedSignal = RACSubject()
class RemindersViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
override func viewDidLoad() {
super.viewDidLoad()
bindViewModel()
}
func bindViewModel(){
sharedAppDelegate().openURLSubject.subscribeNextAs { (request:OpenURLRequest) -> () in
println("got url request")
}
RemindersLoadedSignal.sendNext(NSBoolTrue)
}
}
I dont understand how the subscription in didFinishLaunchingWithOptions works and the one in application:openURL:... doesnt.
I ended up loading the view with doNext and Zipping the signal with the RemindersLoadedSignal.
If you see any potential problems with that please let me know.
Thanks
.doNext { (_:AnyObject!) -> Void in
if let myTabBar = self.window?.rootViewController as? UITabBarController {
myTabBar.selectTabWithClass(RemindersViewController.self)
}
}
.zipWith(RemindersLoadedSignal)