How to open needed controller from Spotlight Search? In Swift - ios

I made spotlight search in my app. I have UIPageViewController as RootViewController and seven different ViewControllers. I made when user found information in spotlight from my app it can open needed controller. But I made it with help NSNotificationCenter that I can open a needed controller. I was able to do it and I have works. I think it is wrong way and I don't know how can I make it otherwise. How can I it otherwise?
I show my realization and it works but I think that it is not a good
AppDelegate
func application(application: UIApplication, continueUserActivity userActivity: NSUserActivity, restorationHandler: ([AnyObject]?) -> Void) -> Bool {
if userActivity.activityType == CSSearchableItemActionType {
let identifier = Int(userActivity.userInfo![CSSearchableItemActivityIdentifier] as! String)!
startIndex = identifier
notificatioCenter.postNotificationName("openSpotlight", object: nil)
}
return true
}
UIPageViewController as RootViewController
override func viewDidLoad() {
super.viewDidLoad()
notificationCenter.addObserver(self, selector: "openSearchFromSpotligt", name: "openSpotlight", object: nil)
}
func openSearchFromSpotligt() {
let startIndex = appDel.startIndex
let startingController = viewControllerAtIndex(startIndex)! // 0
pageViewController.setViewControllers([startingController], direction: .Forward, animated: false, completion: { done in })
}

Related

iOS13: Universal Links not opening if the app was not running

would appreciate any help. We have implemented handling of universal links in our app and I am struggling with the following issues:
Universal Links opens when the app is running in the background (working fine)
When running on the device with iOS13 installed, opening a universal link only works properly if the app is running in the background. If it has been terminated, after tapping the
link the app is getting launched but this method not called
application(continue userActivity:.., restorationHandler:..)
Any ideas? Appreciate!
enter code here
var window: UIWindow?
var tabBarController1: UITabBarController?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool
{
presentAppLaunchVC()
return true
}
func presentVC(navController : UINavigationController)
{
if var topController = UIApplication.shared.keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
topController.present(navController, animated: false, completion: nil)
}
}
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
if userActivity.activityType == NSUserActivityTypeBrowsingWeb
{
guard let url = userActivity.webpageURL else {
return false
}
if !isValidDeepLink(web_url: url)
{
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
else
{
scrapDeepLinkingUrl(url : url)
}
}
return true
}
func isValidDeepLink(web_url :URL) -> Bool
{
guard let components = URLComponents(url : web_url,resolvingAgainstBaseURL : true) else {
return false
}
guard let host = components.host else {
return false
}
switch host {
case "www.domain.com":
return true
default:
return false
}
}
func scrapDeepLinkingUrl(url : URL)
{
}
else
{
presentAppLaunchVC()
}
}
func presentAppLaunchVC()
{
let storyBoard = UIStoryboard(name: storyboard_name, bundle: nil)
let screen = storyBoard.instantiateViewController(withIdentifier: identifier)
if identifier == "dashboardVC" {
tabBarController1 = screen as? UITabBarController
}
self.window?.rootViewController = screen
}
You need to check the URL in didFinishLaunchingWithOptions method as well.
It can be an URL:
launchOptions[UIApplicationLaunchOptionsURLKey]
or it can be an Universal link:
launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey]
What I would do is add conditional scene delegate support. That way, you would get the message in scene(_:willConnectTo:). Okay, this is going to be more work, but you need to get in sync with the native scene support in iOS 13 and later, and this seems to be the moment to do so.

How to redirect to a certain screen when opening a dynamic link?

I'm developing an app that can receive Firebase's Dynamic Link. What I want is when a user click a Dynamic Link, the app redirects it to a certain UIViewController. So I have a code that looks like this on my AppDelegate.swift file:
#available(iOS 9.0, *)
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool {
//return GIDSignIn.sharedInstance().handle(url)
return application(app, open: url, sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String, annotation: "")
}
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
// On progress
if let dynamicLink = DynamicLinks.dynamicLinks().dynamicLink(fromCustomSchemeURL: url) {
print("open url = open dynamic link activity")
print("url = \(dynamicLink)")
let destinationVC = UIStoryboard(name: "DynamicLink", bundle: nil).instantiateViewController(withIdentifier: "DynamicLinkView") as? DynamicLinkVC
self.window?.rootViewController?.navigationController?.pushViewController(destinationVC!, animated: true)
} else {
print("open url = none")
}
return GIDSignIn.sharedInstance().handle(url)
}
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
// On progress
let handled = DynamicLinks.dynamicLinks().handleUniversalLink(userActivity.webpageURL!) { (dynamiclink, error) in
print("dynamic link = \(dynamiclink)")
}
if handled {
let destinationVC = UIStoryboard(name: "DynamicLink", bundle: nil).instantiateViewController(withIdentifier: "DynamicLinkView") as? DynamicLinkVC
self.window?.rootViewController?.navigationController?.pushViewController(destinationVC!, animated: true)
}
return handled
}
So what happened when I click the link the app opens up immediately but it doesn't redirects to the desired UIViewController that I wanted (in this case destinationVC). It directly went to the login page as usual. But in the debug area, the link appears like this =
dynamic link = Optional(https://xxxx], match type: unique, minimumAppVersion: N/A, match message: (null)>)
Unfortunately I couldn't record the log messages when the app is not built by Xcode.
I'm very confused by this, what's wrong with my code? I'm new to iOS development so I'm not sure where did I do wrong. If you need more information feel free to ask and I will provide it to you. Any help would be appreciated. Thank you.
If your rest-of-code is working fine, and you are just facing issue while navigating to another view controller, then this solution will work for you.
If you want to open particular ViewController, while clicking on dynamic link, then update your code written within restorationHandler, below updated code will help you to redirect/navigate to particular View Controller
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
// On progress
let handled = DynamicLinks.dynamicLinks().handleUniversalLink(userActivity.webpageURL!) { (dynamiclink, error) in
print("dynamic link = \(dynamiclink)")
}
if handled {
let mainStoryboardIpad : UIStoryboard = UIStoryboard(name: "DynamicLink", bundle: nil)
if let initialViewController : UIViewController = (mainStoryboardIpad.instantiateViewController(withIdentifier: "DynamicLinkView") as? DynamicLinkVC) {
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
}
return handled
}
Hope this will resolve your issue.

How to I instantiate multiple View Controllers from AppDelegate?

There is a container view which holds 3 view controllers (V1, V2 and V3). I'm able to switch from A, B or C by swiping left or right. Both A or B contain their own collection view. If I tap on any cell in the collection view inside A or B, the PlayerVC (named Player in the pic above ^^) launches and a video begins to play using AVPlayer.
The problem is: Since I am using universal links the user goes straight to the playerView to play a video but when they press the done button the app crashes. The issue I beleive is because the rest on the view controllers are NOT initialized? How do I initialize the container view and the other view controllers? Or if this is not the issue please let me know what the problem is.
Here is the app delegate with some sample code. Please provide code if possible to help out!
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if let pagingViewController = window?.rootViewController as? PagingViewController {
pagingViewController.videoPlaybackManager = videoPlaybackManager
}
return true
}
class AppDelegate: UIResponder, UIApplicationDelegate {
// Other App Delegate methods.....
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Void) -> Bool {
// 1) Make sure the passed `user activity` has expected characteristics.
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb, let url = userActivity.webpageURL else {
return false
}
// HELP: I need to get to `PlayerVC` from here?
return true
// If we can't do the above we default to opening the page in safari
}
}
EDIT - Additional code of how I am instantiating the 3 VCs inside PagingViewController ( container View)
private func setupViewControllers() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
page1 = storyboard.instantiateViewController(withIdentifier: StoryboardIdentifiers.feedViewController.rawValue) as! FeedViewController
page1.view.translatesAutoresizingMaskIntoConstraints = false
page1.delegate = self
scrollView.addSubview(page1.view)
addChildViewController(page1)
page1.didMove(toParentViewController: self)
// Inject dependency.
page1.videoPlaybackManager = videoPlaybackManager
page2 = storyboard.instantiateViewController(withIdentifier: StoryboardIdentifiers.favoritesViewController.rawValue) as! FavoritesViewController
page2.view.translatesAutoresizingMaskIntoConstraints = false
page2.delegate = self
scrollView.addSubview(page2.view)
addChildViewController(page2)
page2.didMove(toParentViewController: self)
page3 = storyboard.instantiateViewController(withIdentifier: StoryboardIdentifiers.settingsViewController.rawValue) as! SettingsViewController
page3.view.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(page3.view)
addChildViewController(page3)
page3.didMove(toParentViewController: self)
......
If you are dealing with UIPageViewController, to set the proper child UIViewController you can:
// In PagingViewController
func loadChildViewControllerFromUniversalLink(myParameter: Bool) {
// The condition you need to satisfied in order to present
// the correct child view controller
if myParameter { // is satisfied
let universalLinkVc = MyChildViewController() // load your VC here
let direction : UIPageViewControllerNavigationDirection = .forward // or .reverse, as you wish
self.setViewControllers([universalLinkVc], direction: direction, animated: animated, completion: nil)
}
}
And then in your AppDelegate's application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Void) -> Bool:
myPagingViewControllerInstance.loadChildViewControllerFromUniversalLink(true)

3D Touch shortcuts won't work due to Unexpected Crash

I am trying to add 3D Touch Shortcuts to an application, I have managed to have the shortcuts appear when using 3DTouch on the app icon from the homescreen; however when using the shortcut the application crashes on load and I am unsure why.
I have managed to get the application to load for the bookmarks shortcut but it does not initiate the BookmarksViewController, it just loads the InitialViewController.
The application is embedded within a UITabBarController and a UINavigationController for each Tab. Both View Controllers I am trying to load are in different tabs but the first view in the navigation controller stack.
Does anyone know where I am going wrong ?
info.plist file
App Delegate
enum ShortcutItemType: String {
case Bookmarks
case Favourites
init?(shortcutItem: UIApplicationShortcutItem) {
guard let last = shortcutItem.type.componentsSeparatedByString(".").last else { return nil }
self.init(rawValue: last)
}
var type: String {
return NSBundle.mainBundle().bundleIdentifier! + ".\(self.rawValue)"
}
}
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
if let shortcutItem = launchOptions?[UIApplicationLaunchOptionsShortcutItemKey] as? UIApplicationShortcutItem {
handleShortcutItem(shortcutItem)
}
return true
}
private func handleShortcutItem(shortcutItem: UIApplicationShortcutItem) {
if let rootViewController = window?.rootViewController, let shortcutItemType = ShortcutItemType(shortcutItem: shortcutItem) {
let sb = UIStoryboard(name: "main", bundle: nil)
let favouritesVC = sb.instantiateViewControllerWithIdentifier("FavouritesVC") as! FavouritesTableViewController
let bookmarksVC = sb.instantiateViewControllerWithIdentifier("BookmarksVC") as! BookmarksNotesViewController
switch shortcutItemType {
case .Bookmarks:
rootViewController.presentViewController(bookmarksVC, animated: true, completion: nil)
break
case .Favourites:
rootViewController.presentViewController(favouritesVC, animated: true, completion: nil)
break
}
}
}
func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {
handleShortcutItem(shortcutItem)
}
}
Storyboard ID
These are the StoryboardID for the View Controllers.
I think you forgot the 's' in com.appName.Bookmark
Or you need to remove the 's' from Bookmarks here:
enum ShortcutItemType: String {
case Bookmarks
case Favourites
Instead, try using a shortcut like this:
if shortcutItem.type == "com.appName.Bookmark"
#available(iOS 9.0, *)
func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {
if shortcutItem.type == "com.appName.Bookmark" {
}
}

Spotlight search doesn't work when app not running

I want to tell first UIViewController that Spotlight opened it. I try to make it by NSNotificationCenter. But I tried several methods and they don't it when I make my key like "spotlightOpen". When I use standard name like UIApplicationDidFinishLaunchingNotification it works for me. Below I wrote several methods which I tried
In
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
NSNotificationCenter.defaultCenter().postNotificationName("selectSong", object: nil)
return true
}
In First controller
NSNotificationCenter.defaultCenter().addObserverForName("selectSong", object: nil, queue: NSOperationQueue.mainQueue()) { (NSNotification) -> Void in
print("Song table is loaded")
}
Still I made it in the first controller. But it too didn't work for me.
NSNotificationCenter.defaultCenter().addObserver(self, selector: "selectedSong", name: "selectSong", object: nil)
func application(application: UIApplication, continueUserActivity userActivity: NSUserActivity, restorationHandler: ([AnyObject]?) -> Void) -> Bool {
print("continueUserActivity")
userDefault.setBool(true, forKey: "applicationDelegateOpen")
if userActivity.activityType == CSSearchableItemActionType {
print("CSSearchableItemActionType")
if let identifier = userActivity.userInfo?[CSSearchableItemActivityIdentifier] {
userDefault.setValue(identifier, forKey: "spotlightIdentifier")
userDefault.setBool(true, forKey: "spotlightBool")
print(identifier)
return true
}
}
return false
}
It's very likely that on a cold launch that your view controller hasn't been initialized yet. You need to temporarily save that notification data until something like loadView or viewDidLoad is called.
In the continueUserActivity callback of UIApplicationDelegate, you can handle the NSUserActivity of type CSSearchableItemActionType for spotlight actions, if you are using the CoreSpotlight API. If you are using NSUserActivity API, you can also handle those in this delegate callback.
You create a core spotlight item with your song ID:
let item = CSSearchableItem(uniqueIdentifier: songId, domainIdentifier: "com.mycompany.song", attributeSet: attributeSet)
// ... Save it and all that
You can handle the spotlight launch using:
func application(application: UIApplication, continueUserActivity userActivity: NSUserActivity, restorationHandler: ([AnyObject]?) -> Void) -> Bool {
if userActivity.activityType == CSSearchableItemActionType {
if let songId = userActivity.userInfo?[CSSearchableItemActivityIdentifier] as? String {
// Do notification with songId
// Or let view controllers know about selected songId
}
}
return true
}
I made it. It works for me
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.tableView.reloadData()
self.tabBarController!.tabBar.hidden = false
fetchFilesFromFolder()
//
checkSpotlightResult()
}
func checkSpotlightResult() {
print("checkSpotlightResult")
// print(arrayForCheckSpot)
// userDefault.setValue(identifier, forKey: "spotlightIdentifier")
// userDefault.setBool(true, forKey: "spotlightBool")
// var boolCheckSpot: Bool!
// var identifierCheckSpot: String!
boolCheckSpot = userDefault.boolForKey("spotlightBool")
if boolCheckSpot != nil {
if boolCheckSpot == true {
identifierCheckSpot = userDefault.valueForKey("spotlightIdentifier") as! String
if arrayForCheckSpot.contains(identifierCheckSpot) {
// print("Array title contains \(identifierCheckSpot)")
let index = arrayForCheckSpot.indexOf(identifierCheckSpot)!
let myIndexPath = NSIndexPath(forRow: index, inSection: 0)
print(myIndexPath)
self.tableView.selectRowAtIndexPath(myIndexPath, animated: true, scrollPosition: .None)
self.performSegueWithIdentifier("listenMusic", sender: self)
userDefault.setBool(false, forKey: "spotlightBool")
}
}
}
}

Resources