Continuing userActivity while app I closed - ios

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()

Related

navigation bar disappears when opening view controller via deep link

I've used a tutorial to implement a method in app delegate to open specific view controllers from a HTML button from safari. my app delegate looks like this:
func application(_ app: UIApplication, open URL: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
print("url \(url)")
print("url host :\(url.host!)")
print("url path :\(url.path)")
let urlPath : String = (url.path as String?)!
let urlHost : String = (url.host as String?)!
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
if(urlHost != "mywebsite.ir")
{
print("Host is not correct")
return false
}
if(urlPath == "/index"){
let innerPage: ViewController = mainStoryboard.instantiateViewController(withIdentifier: "HomeViewController") as! ViewController
self.window?.rootViewController = innerPage
}
self.window?.makeKeyAndVisible()
return true
}
and my HTML button reference is:
open app
but when my app home page opens, the navigation bar is gone and the user gets stuck and doesn't have any access to menu buttons or back button(in case of internal view controllers).
screenshots of home view controller before and after deep link:
Please update your code as below..you should have to put your navigation controller as a window root view controller..
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
print("url \(url)")
print("url host :\(url.host!)")
print("url path :\(url.path)")
let urlPath : String = (url.path as String?)!
let urlHost : String = (url.host as String?)!
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
if(urlHost != "mywebsite.ir")
{
print("Host is not correct")
return false
}
if(urlPath == "/index"){
let innerPage: ViewController = mainStoryboard.instantiateViewController(withIdentifier: "HomeViewController") as! ViewController
let nav = UINavigationController.init(rootViewController: innerPage)
self.window?.rootViewController = nav
}
self.window?.makeKeyAndVisible()
return true
Does HomeViewController is the initial ViewController? If not, you could call the initial view controller that should be the view controller with a navigation controller.
Try to do this:
mainStoryboard.instantiateInitialViewController()
If the behaviour persists you could embed a NavigationController on code
let viewController = mainStoryboard.instantiateViewController(withIdentifier: "HomeViewController") as! ViewController
let navigationController = UINavigationController(rootViewController: viewController)
self.window?.rootViewController = navigationController
Also you don't need to call window.makeKeyAndVisible()

Shortcut Items, Quick Actions 3D Touch swift

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)

Trying to Embed VC into NavigationVC from performActionFor shortcutItem via 3d touch on App icon

I have an app that is using 3d touch to jump to a certain VC in my app. The issue is that when the app is launched normally, all of my VC's are embedded into a Navigation View Controller. But since I am skipping the launch sequence and jumping right into a VC, the VC that I jump to isn't embedded into the Nav VC.
Here is what I am trying in the App Delegate
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: #escaping (Bool) -> Void) {
guard TouchActions(rawValue: shortcutItem.type) != nil else {
print("3d not working")
completionHandler(false)
return
}
print("3d touch workig")
let mainStoryboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let addPersonVC : AddPersonViewController = mainStoryboard.instantiateViewController(withIdentifier: "AddPerson") as! AddPersonViewController
// pass the stack to the addPersonVC
addPersonVC.coreDataStack = coreDataStack
let navController:UINavigationController = mainStoryboard.instantiateViewController(withIdentifier: "navController") as! UINavigationController
navController.pushViewController(addPersonVC, animated: true)
// self.window? = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = navController
self.window?.makeKeyAndVisible()
completionHandler(true)
}
This code works, but you when I try to leave this VC, the app just sits there unresponsive. I some how need to embedded addPersonVC into the main Nav VC that is set up in storyboard. (The embedded navigation controller was set up in storyboard, incase that matters.)
Option1: Add a Storyboard Identifier to your UINavigationController , and instantiate the UINavigationController instead.
Option2: Delete the UINavigationController from storyboard. Just initialize a new UINavigationController by code and set the rootView programatically.
I am not a Swift developer, but since you don't see the examples I wrote I did some quick pasting to help you understand the basics, try this code:
let mainStoryboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let addPersonVC : AddPersonViewController = mainStoryboard.instantiateViewController(withIdentifier: "AddPerson") as! AddPersonViewController
// pass the stack to the addPersonVC
addPersonVC.coreDataStack = coreDataStack
let navController:UINavigationController = mainStoryboard.instantiateViewController(withIdentifier: "navController") as! UINavigationController
navController.viewControllers = [addPersonVC]
self.window?.rootViewController?.present(navController, animated: true, completion: nil)
self.window?.makeKeyAndVisible()

how can I make the uiviewcontroller visible only once during first run of the app (e.g. tutorial)?

I'm creating an iOS swift app and I want to show tutorial screen when user runs the app for the very first time. Later on, with each run of the app the tutorial should be hidden and another view controller should be visible as a starting point. So far my storyboard looks like this:
It contains two screens of tutorial (1st and last) and tab bar (which is a main window of my app).
As for now, in storyboard I chose the tab bar to be an initial view controller:
And with that approach the tutorial screen is never seen. How can I show it only once on first launch app and then skip it each time user opens the app?
In didFinishLaunchingWithOptions method of AppDelegate check for NSUserDefaults value like this way.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
let defaults = NSUserDefaults.standardUserDefaults()
if defaults.objectForKey("isFirstTime") == nil {
defaults.setObject("No", forKey:"isFirstTime")
let storyboard = UIStoryboard(name: "main", bundle: nil) //Write your storyboard name
let viewController = storyboard.instantiateViewControllerWithIdentifier("ViewController") as! ViewController
self.window.rootViewController = viewController
self.window.makeKeyAndVisible()
}
return true
}
Note: I have created the object of ViewController you need to create the object of your FirstPage tutorial screen after that assign it to the rootViewController.
For swift 4 make these changes.
let defaults = UserDefaults.standard
if defaults.object(forKey: "isFirstTime") == nil {
defaults.set("No", forKey:"isFirstTime")
defaults.synchronize()
...
}
Simplified Swift 4 version of this answer.
https://stackoverflow.com/a/39353299/1565913
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
if !UserDefaults.standard.bool(forKey: "didSee") {
UserDefaults.standard.set(true, forKey: "didSee")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let viewController = storyboard.instantiateViewController(withIdentifier: "YourViewController")
self.window?.rootViewController = viewController
self.window?.makeKeyAndVisible()
}
return true
}
Add this is Scene Delegate
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
if UserDefaults.standard.bool(forKey: "introLaunched") == false{
UserDefaults.standard.set(true, forKey: "introLaunched")
let storyboard = UIStoryboard(name: "Main", bundle: .main)
let vc = storyboard.instantiateViewController(identifier: "IntroScreenViewController") as! IntroScreenViewController
self.window?.rootViewController = UINavigationController(rootViewController: vc)
} else {
let storyboard = UIStoryboard(name: "Main", bundle: .main)
let vc = storyboard.instantiateViewController(withIdentifier: "ViewController") as! ViewController
self.window?.rootViewController = UINavigationController(rootViewController: vc)
}
}

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.

Resources