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)
Related
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 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
I am a new developer, and I am currently making my first app.
Sometimes when I try to open the app, the icon dims, but nothing happens, and the whole phone briefly becomes unresponsive. My guess is this is happening in app delegate. I'm not sure what is happening, but I think I should be using grand central dispatch.
this is currently my code in app delegate,
import UIKit
import Firebase
import FirebaseMessaging
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
//Facebook SharedInstance FBSDKApplicationDelegate.sharedInstance().application(application, didFinishLaunchingWithOptions: launchOptions)
//Firebase Database
FIRApp.configure()
FIRDatabase.database().persistenceEnabled = true
//Onesignal PushNotifications
_ = OneSignal(launchOptions: launchOptions, appId: "3b2b4845-0b6a-4e92-892f-254c2cf51da8", handleNotification: nil)
_ = OneSignal(launchOptions: launchOptions, appId: "3b2b4845-0b6a-4e92-892f-254c2cf51da8", handleNotification: { (message, additionalData, isActive) in
NSLog("OneSignal Notification opened:\nMessage: %#", message)
if additionalData != nil {
NSLog("additionalData: %#", additionalData)
// Check for and read any custom values you added to the notification
// This done with the "Additonal Data" section the dashbaord.
// OR setting the 'data' field on our REST API.
if let customKey = additionalData["customKey"] as! String? {
NSLog("customKey: %#", customKey)
}
}
}, autoRegister: true)
print("didFinishLaunchingWithOptions launchOptions")
return true
}
func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool {
print("application")
return FBSDKApplicationDelegate.sharedInstance().application(application, openURL: url, sourceApplication: sourceApplication, annotation: annotation)
}
Is it possible, that this has nothing to do with app delegate?
if it is in app delegate how can use GCD to make this work
remove
import FirebaseMessaging
and
FIRDatabase.database().persistenceEnabled = true
from the AppDeleget and try running it
I have added 3D Touch on my app icon to show Quick Action menu. I think I should have it all set up correctly.
The problem is that when I am choosing one of the items in the Quick Action menu, the iPhone freezes for a few seconds before it opens up the application.
This is my AppDelegate.swift:
import UIKit
import Parse
#available(iOS 9.0, *)
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
Parse.setApplicationId("xx",
clientKey: "xx")
let currentInstallation: PFInstallation = PFInstallation.currentInstallation()
currentInstallation.badge = 0
currentInstallation.saveEventually()
return true
}
func applicationWillResignActive(application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
func applicationDidEnterBackground(application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {
let rootNavigationViewController = window!.rootViewController as? UINavigationController
let rootViewController = rootNavigationViewController?.viewControllers.first as UIViewController?
rootNavigationViewController?.popToRootViewControllerAnimated(false)
if shortcutItem.type == "JEGHARALDRI" {
rootViewController?.performSegueWithIdentifier("JEGHARALDRISEGUE", sender: nil)
}
if shortcutItem.type == "PLING" {
rootViewController?.performSegueWithIdentifier("PLINGSEGUE", sender: nil)
}
if shortcutItem.type == "FLASKETUTENPEKERPĆ
" {
rootViewController?.performSegueWithIdentifier("FLASKETUTENPEKERPĆ
SEGUE", sender: nil)
}
if shortcutItem.type == "KORTETTALER" {
rootViewController?.performSegueWithIdentifier("KORTETTALERSEGUE", sender: nil)
}
}
}
I think your app delegate should more looks like something like this
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
//MARK: - Properties
var window: UIWindow?
lazy var quickActionManager: QuickActionsManager = {
return QuickActionsManager()
}()
//MARK: - AppDelegate Methods
func application(application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
{
return self.setupQuickActions(launchOptions)
}
func application(application: UIApplication, performActionForShortcutItem
shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void)
{
completionHandler(self.quickActionManager.handleShortcut(shortcutItem))
}
//MARK: - Private Methods
private func setupQuickActions(launchOptions: [NSObject: AnyObject]?) -> Bool
{
guard let shortcutItem = launchOptions?[UIApplicationLaunchOptionsShortcutItemKey]
as? UIApplicationShortcutItem else { return false }
return self.quickActionManager.handleShortcut(shortcutItem)
}
}
And so then you get all the logic to handle a quick action in your quick action manager, which would looks something like this
//MARK: - Public Methods
func handleShortcut(shortcut: UIApplicationShortcutItem?) -> Bool
{
guard let shortcut = shortcut else { return false }
// Get the key of the shortcutItem
let key = self.shortKeyForType(shortcut.type)
// Check if that key is the key of a knowed viewController
guard let viewControllerKey = ViewControllerKeys(rawValue: key) else { return false }
// Try to show This View Controller
return self.showViewController(viewControllerKey)
}
Assuming you got an enum of viewController to display matching each quick actions.
I hope this answer your question, let me know if you got some more.
I'm implementing some 3D touch quick actions for my iOS 9 app in swift, and I have a curious issue. When my app is in the background and I launch with the quick action, everything goes as planned. When my app is totally dead (i.e. I killed it from the multitasking menu), and I launch with the quick action, the app crashes. I'm having trouble debugging this as once I kill the app, the debug session in Xcode gets detached. Is there a way for me to connect to the app to debug like normal, or is there something in my code that would be causing it? Thanks in advance.
Code:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
{
var launchedFromShortCut = false
//Check for ShortCutItem
if let shortcutItem = launchOptions?[UIApplicationLaunchOptionsShortcutItemKey] as? UIApplicationShortcutItem
{
launchedFromShortCut = true
self.handleShortCutItem(shortcutItem)
}
return !launchedFromShortCut
}
func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void)
{
self.handleShortCutItem(shortcutItem)
}
func handleShortCutItem(shortcutItem: UIApplicationShortcutItem)
{
//Get type string from shortcutItem
if let shortcutType = ShortcutType.init(rawValue: shortcutItem.type)
{
//Get root navigation viewcontroller and its first controller
let rootNavigationViewController = window!.rootViewController as? UINavigationController
if let rootViewController = rootNavigationViewController?.viewControllers.first as! LaunchViewController?
{
//Pop to root view controller so that approperiete segue can be performed
rootNavigationViewController?.popToRootViewControllerAnimated(false)
switch shortcutType
{
case .Compose:
rootViewController.shouldCompose()
break
}
}
}
}
Thanks!
In Xcode, open Product -> Schemes -> Edit Schemes
In your Run Scheme, change the Launch setting to 'Wait for executable to be launched'
Now, if you turn on debugging and run your app, Xcode will wait for you to launch your app from the home screen so you are able to test launching it using a 3D Touch Shortcut Item.
I finally got this working. Here's what my AppDelegate.swift file ended up as;
class AppDelegate: UIResponder, UIApplicationDelegate {
// Properties
var window: UIWindow?
var launchedShortcutItem: UIApplicationShortcutItem?
func applicationDidBecomeActive(application: UIApplication) {
guard let shortcut = launchedShortcutItem else { return }
handleShortcut(shortcut)
launchedShortcutItem = nil
}
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
var shouldPerformAdditionalDelegateHandling = true
// If a shortcut was launched, display its information and take the appropriate action
if let shortcutItem = launchOptions?[UIApplicationLaunchOptionsShortcutItemKey] as? UIApplicationShortcutItem {
launchedShortcutItem = shortcutItem
// This will block "performActionForShortcutItem:completionHandler" from being called.
shouldPerformAdditionalDelegateHandling = false
}
return shouldPerformAdditionalDelegateHandling
}
func handleShortcut( shortcutItem:UIApplicationShortcutItem ) -> Bool {
// Construct an alert using the details of the shortcut used to open the application.
let alertController = UIAlertController(title: "Shortcut Handled", message: "\"\(shortcutItem.localizedTitle)\"", preferredStyle: .Alert)
let okAction = UIAlertAction(title: "OK", style: .Default, handler: nil)
alertController.addAction(okAction)
// Display an alert indicating the shortcut selected from the home screen.
window!.rootViewController?.presentViewController(alertController, animated: true, completion: nil)
return handled
}
func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {
completionHandler(handleShortcut(shortcutItem))
}
Much of this was taken from Apple's sample code for UIApplicationShortcuts, and while I'm having my app launch an alert to prove that it is recognizing the proper shortcut was chosen, this could be adapted to your code to pop the view controller.
I think the func applicationDidBecomeActive was the critical part that I was missing, and removing the self.handleShortCut(shortcutItem) from didFinishLaunchingWithOptions (otherwise it was calling handleShortCut twice, it seemed).
For Swift 4.2
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
var isLaunchedFromQuickAction = false
if let shortcutItem = launchOptions?[UIApplication.LaunchOptionsKey.shortcutItem] as? UIApplicationShortcutItem {
isLaunchedFromQuickAction = true
handleQuickAction(shortcutItem: shortcutItem)
}
return isLaunchedFromQuickAction
}
I tried all the above, and it didn't solve the problem
than I tried handling the shortcut after delay in handleShortcut method:
self.performSelector("action1", withObject: self, afterDelay: 0.5)
and added a method for every action, and it worked like a charm
Replace your didfinishlaunching method with this one.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
if let shortcutItem =
launchOptions?[UIApplicationLaunchOptionsShortcutItemKey]
as? UIApplicationShortcutItem {
handleShortcut(shortcutItem)
return false
}
return true
}
XCode 11.6, Swift 5
We can attach a process at runtime. XCode will wait until the process is started and will get attached to it when an App is launched manually.
XCode -> Debug -> Attach to process by PID or Name -> ("Enter the name of an app in the pop-up")
Directions:
Make sure an application is freshly installed on a device or a simulator.
Kill the application.
In XCode attach the name of the process as mentioned above.
Open the application through the desired shortcut
P.S: If you are using SceneDelegate a shortcutItem can be found in
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
switch connectionOptions.shortcutItem?.localizedTitle {
case "Search":
break
case "DoSomething":
break
default:
break
}
}
Happy Debugging :)