Shortcut Items, Quick Actions 3D Touch swift - ios

I am for some reason having some troubles figuring out the Shortcut Items in my app. I have followed all the tutorials and I believe that I have done everything right; however, it is acting really weird. The problem is that when you completely close out that app and do the 3D Touch Shortcut item on the home screen, it takes you to the correct part of the app as it should; however, when the app is open in the background (like you just click the home button) it doesn't take you to the correct part of the app, it just opens it. This is really odd and I am not sure what to do because I have followed all the instructions. Thank you so much!
Code:
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: #escaping (Bool) -> Void) {
if shortcutItem.type == "com.myapp.newMessage" {
let sb = UIStoryboard(name: "Main", bundle: nil)
let vc = sb.instantiateViewController(withIdentifier: "RecentVC") as! UITabBarController
vc.selectedIndex = 2 //this takes me to the contacts controller
let root = UIApplication.shared.keyWindow?.rootViewController
root?.present(vc, animated: false, completion: {
completionHandler(true)
})
} else if shortcutItem.type == "com.myapp.groups" {
let sb = UIStoryboard(name: "Main", bundle: nil)
let vc = sb.instantiateViewController(withIdentifier: "RecentVC") as! UITabBarController
vc.selectedIndex = 1 //this takes me to the groups controller
let root = UIApplication.shared.keyWindow?.rootViewController
root?.present(vc, animated: false, completion: {
completionHandler(true)
})
} else {
//more short cut items I will add later
}
}
NOTE: these are static Shortcut Items, and I configured them in the info.plist file, This function that was presented is the only place in the app besides the info.plist file that have anything about the Shortcut Items.
EDIT: This seems like an issue with the RootViewController, because I have tried multiple different ways of doing this, and it still gives me the same warning message
2017-11-22 22:36:46.473195-0800 QuickChat2.0[3055:1068642] Warning: Attempt to present on whose view is not in the window hierarchy!
I just tried to accomplish my goals this way through this post but it gives me the exact same results as before.

You should set a variable in your performActionForShortcutItem that tells you the shortcut type and move the code you have in there into applicationDidBecomeActive. Your could should look like this
var shortcut: String?
func applicationDidBecomeActive(application: UIApplication) {
if let shortcutItem = shortcut {
shortcut = nil
if shortcutItem == "com.myapp.newMessage" {
let sb = UIStoryboard(name: "Main", bundle: nil)
let vc = sb.instantiateViewController(withIdentifier: "RecentVC") as! UITabBarController
vc.selectedIndex = 2 //this takes me to the contacts controller
let root = UIApplication.shared.keyWindow?.rootViewController
root?.present(vc, animated: false, completion: nil)
} else if shortcutItem == "com.myapp.groups" {
let sb = UIStoryboard(name: "Main", bundle: nil)
let vc = sb.instantiateViewController(withIdentifier: "RecentVC") as! UITabBarController
vc.selectedIndex = 1 //this takes me to the groups controller
let root = UIApplication.shared.keyWindow?.rootViewController
root?.present(vc, animated: false, completion: nil)
} else {
//more short cut items I will add later
}
}
}
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: #escaping (Bool) -> Void) {
shortcut = shortcutItem.type
completionHandler(true)
}

I think a cleaner method is to set your flags in the performActionFor method, then check for them once your VC loads. Using this notification will handle the shortcut every time the app becomes active, including launch.
override func viewDidLoad() {
super.viewDidLoad()
// Sends notification if active app is reentered to handle quick actions
NotificationCenter.default.addObserver(self, selector: #selector(handleQuickActions), name: UIApplication.didBecomeActiveNotification, object: nil)

Related

Continuing userActivity while app I closed

I am trying to implement universal linking into my app using the AppDelegate class. It is working perfectly fine when the app is already open in the background, but it is acting strange while the app is NOT in the background, specifically it will only work 1 time if the app is not in the background. For example: When I boot up my app for the first time, multi-task then swipe out of the app I can click a link and it will navigate to the correct place in the app. If I try that again it will not work, but If I reboot the app it will work once again. Keep in mind if the app is in the background the universal linking will work every time.
Here is what my AppDelegate looks like:
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
if let url = userActivity.webpageURL {
self.continueWithLink(url: url)
}
return true
}
in didFinsihLaunchingWithOptions function:
self.window = UIWindow(frame: UIScreen.main.bounds)
if let url = launchOptions?[UIApplication.LaunchOptionsKey.url] as? URL {
self.continueWithLink(url: url)
} else {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewController(withIdentifier: "MainTabBarViewController")
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
}
The continueWithLink function will decipher where the link is wanting to navigate to and then will call a function that sets the desired viewController like this:
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
if let _ = appDelegate.window?.rootViewController as? BannedViewController { return }
let storyboard = UIStoryboard(name: "Profile", bundle: nil)
let profileVC = storyboard.instantiateViewController(withIdentifier: "ProfileView") as! ProfileViewController
profileVC.shouldNavigateToHome = true
profileVC.shouldNavigateToHomeAction = {
self.loadMainStoryboard()
}
let navigationVC = UINavigationController(rootViewController: profileVC)
appDelegate.window?.rootViewController = navigationVC
appDelegate.window?.makeKeyAndVisible()

How to dismiss viewcontroller in appdelegate?

I create launchScreen for pause view like this.
func applicationWillResignActive(_ application: UIApplication) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let launchScreen = storyboard.instantiateViewController(withIdentifier: "launchScreen")
launchScreen.restorationIdentifier = "launchScreen"
var rootViewController = UIApplication.shared.keyWindow?.rootViewController
while let presentController = rootViewController?.presentedViewController {
rootViewController = presentController
}
rootViewController?.present(launchScreen, animated: false, completion: nil)
}
func applicationDidEnterBackground(_ application: UIApplication) {
guard let passcodeManageView = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "passcodeManageView") as? PasscodeManageViewController else { return }
passcodeManageView.state = State.loginMode
passcodeManageView.modalPresentationStyle = .overFullScreen
var rootViewController = UIApplication.shared.keyWindow?.rootViewController
while let presentController = rootViewController?.presentedViewController {
rootViewController = presentController
}
rootViewController?.present(passcodeManageView, animated: false, completion: nil)
}
But, How to dismiss launchScreen in applicationDidEnterBackground(:_)??
How can I find specific view controller and that dismiss??
According to Apple document for applicationDidEnterBackground(_:)`
Use this method to release shared resources, invalidate timers, and store enough app state information to restore your app to its current state in case it is terminated later. You should also disable updates to your app’s user interface and avoid using some types of shared system resources (such as the user’s contacts database). It is also imperative that you avoid using OpenGL ES in the background.
You shouldn't dismiss launch screen after app entered background. But if you still want to achieve it, use window?.rootViewController? to dismiss because at this time, window?.rootViewController? is launch screen
func applicationDidEnterBackground(_ application: UIApplication) {
if (window?.rootViewController?.isKind(of: YOUR_LAUNCH_SCREEN_CLASS.self))! {
window?.rootViewController?.dismiss(animated: true, completion: nil)
}
}

PresentViewController is showing nil of my RootViewController

So let me explain.
I have two ViewControllers (Login-Page and Main-Page).
Login page contains two buttons (FB and G+ login buttons).
Everything works fine with login credentials and is presenting the next view which is Main-Page.
In Main-Page I have button (sign-out).Button should work as singing out and bring me back to Login-Page, unfortunately is not working.
Below is my code
let vc = self.storyboard?.instantiateViewController(withIdentifier: "LoginPage") as! LoginPage
vc.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
self.present(vc, animated: true, completion: nil)
Is showing
fatal error: unexpectedly found nil while unwrapping an Optional value
and the output is nil for all declared IBOutlet in Main-Page
Please I need help!!!??
First we verify if the view controller has a Storyboard ID on the Identity inspector:
Then:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let loginViewController = storyboard.instantiateViewController(withIdentifier: "LoginPage")
let window = UIApplication.shared.windows[0] as UIWindow
UIView.transition(with: window,
duration: 0.5,
options: UIViewAnimationOptions.transitionCrossDissolve,
animations: {
window.rootViewController = loginViewController
}, completion: nil)
With that, you are replacing the presented view controller for your login view controller.
Try using notifications to change the root when you did logout for the app.
First in your appDelegate addObserver:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Notification used in logout action
let notf = NSNotificationCenter.defaultCenter()
notf.addObserverForName("logout", object: nil, queue: NSOperationQueue.mainQueue()) { (notification) in
let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("LoginPage") as! LoginPage
self.window?.rootViewController = controller
}
return true
}
then in the logoutAction simply postNotification
func logout() {
NSNotificationCenter.defaultCenter().postNotificationName("logout", object: nil)
}

How to handle static QuickActions for iOS app using ViewControllers managed by a TabBarController in Xcode written in Swift

I already created some (static) QuickActions in Info.plist for my iOS app created in Xcode and written in Swift.
I have problems with making them able to open a ViewController. Of cause, I already googled, but nothing worked for me. If this counts: I'm using ViewController managed by a TabBarController. Most tutorials seem to use NavigationController. But, I think it will be done with the segues, right? What code do I need to handle it?
Can anybody provide it please? Or does anybody know a simple manual/tutorial?
Regards, David.
P.S.:
I tried this code, but it seems to work only with NavigationController?!
Code:
func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void)
{
self.handleShortcutItem(shortcutItem)
completionHandler(true)
}
func handleShortcutItem(shortcutItem: UIApplicationShortcutItem)
{
switch shortcutItem.type {
case "icons.quickaction.home":
self.presentComposeViewController()
default: break
}
}
func presentComposeViewController()
{
guard let navigationController = window?.rootViewController as? UINavigationController else { return }
let identifier = "MyViewController"
let composeViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier(identifier)
navigationController.pushViewController(composeViewController, animated: false)
}
I finally found the solution with the help from #ILikeTau.
I'm using the following code to open my ViewControllers managed by a TabBarController with QuickAction:
#available(iOS 9.0, *)
func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {
if(shortcutItem.type == "app.quickaction.search"){
let sb = UIStoryboard(name: "Main", bundle: nil)
let vc = sb.instantiateInitialViewController()
window?.rootViewController = vc
guard let tabBarController = window?.rootViewController as? UITabBarController else { return };
tabBarController.selectedIndex = 2
}
else if(shortcutItem.type == "app.quickaction.home"){
let sb = UIStoryboard(name: "Main", bundle: nil)
let vc = sb.instantiateInitialViewController()
window?.rootViewController = vc
guard let tabBarController = window?.rootViewController as? UITabBarController else { return };
tabBarController.selectedIndex = 0
}
}
This code works from both modes: app is in background mode and app is closed. I think this way is easier and shorter than the common way with multiple functions.

3D touch quick actions preview view controller only one time

I have the following issue. When I run my app on my phone, 3d touch the icon and select the quick action it launches the app presenting the correct view controller but when I put the app in background and try to invoke the quick action it just opens the app where I have left it. So to make it work I have to kill my app every time.
Here is my code:
func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {
if shortcutItem.type == "com.traning.Search" {
let sb = UIStoryboard(name: "Main", bundle: nil)
let searchVC = sb.instantiateViewControllerWithIdentifier("searchVC") as! UINavigationController
let root = UIApplication.sharedApplication().keyWindow?.rootViewController
root?.presentViewController(searchVC, animated: false, completion: { () -> Void in
completionHandler(true)
})
}
}
Thanks in advance.
I'm guessing you're trying to present a view controller from a view controller that's not visible. You can use extensions like:
extension UIViewController {
func topMostViewController() -> UIViewController {
if self.presentedViewController == nil {
return self
}
if let navigation = self.presentedViewController as? UINavigationController {
return navigation.visibleViewController.topMostViewController()
}
if let tab = self.presentedViewController as? UITabBarController {
if let selectedTab = tab.selectedViewController {
return selectedTab.topMostViewController()
}
return tab.topMostViewController()
}
return self.presentedViewController!.topMostViewController()
}
}
extension UIApplication {
func topMostViewController() -> UIViewController? {
return self.keyWindow?.rootViewController?.topMostViewController()
}
}
You can place both of these in your app delegate.swift, above your app delegate class, to get the currently visible view controller. Then present the search view controller on that. For example:
func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {
if shortcutItem.type == "com.traning.Search" {
let sb = UIStoryboard(name: "Main", bundle: nil)
let searchVC = sb.instantiateViewControllerWithIdentifier("searchVC") as! UINavigationController
let topViewController = UIApplication.sharedApplication.topMostViewController()
topViewController.presentViewController(searchVC, animated: false, completion: { () -> Void in
completionHandler(true)
})
}
}

Resources