Swift Open specific view controller from button in today extension - ios

I am trying to open specific view controller on button click in today extension, but can not show it. The main app is opening correctly on the main root controller using url scheme but not the specific view controller I set up in App Delegate.
I tried ALL tutorials and answers I could find on Google, for examble:
How to open Specific View controller on Widgets/ Today Extension click
but I can't get it to work.
I tried to figure out where the problem is and I think the method in app delegate do not call. I wrote some print() Methods but none of them appeared in console. I have configured the URL Scheme correctly like in the answers from the link above, so I don't know why the method in App Delegate don't execute.
Hope someone has a tip what else I can try.
Code from Button IBAction in TodayViewController:
#IBAction func addButtonOnePressed(_ sender: UIButton) {
let url = URL(string: "OpenURL://")!
self.extensionContext?.open(url, completionHandler: nil)
}
Code from App Delegate Method (I tried all I could find of them):
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool
{
if url.scheme == "OpenURL"
{
let storyboard = UIStoryboard(name: "Groups", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "Group") as! MasterViewController
self.window?.rootViewController = vc
self.window?.makeKeyAndVisible()
}
return true
}
UPDATE:
I solved it myself. I have to do all the stuff in SceneDelegate and not in AppDelegate and take care of the Tabbar Controller and the navigation Controller.
New Code in SceneDelegate.swift:
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
if let url = URLContexts.first?.url {
if url.scheme == "OpenURL" {
guard let rootViewController = (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window?.rootViewController else {
return
}
let storyboard = UIStoryboard(name: "Groups", bundle: nil)
if let rootVC = storyboard.instantiateViewController(withIdentifier: "Group") as? MasterViewController,
let tabBarController = rootViewController as? UITabBarController,
let navController = tabBarController.selectedViewController as? UINavigationController {
navController.pushViewController(rootVC, animated: true)
}
}
}
}

Related

Could not cast value of type 'UIViewController' to 'name.mainVC' - signal SIGABRT

I'm very new to coding and trying to make an onboarding screen. I use 3 screens in my Onboarding storyboard and they work fine until I try to switch storyboards. It shows me 'Signal SIGABRT' in the line
let mainVC = storyboard.instantiateViewController(identifier: "mainVC") as! mainVC
Error:
2020-02-25 13:19:17.128892+0100 ihopeyoureokay[89853:2434827]
[Storyboard] Unknown class mainVC in Interface Builder file. Could not
cast value of type 'UIViewController' (0x110a5b940) to
'ihopeyoureokay.mainVC' (0x1060cc918). 2020-02-25 13:19:17.129648+0100
ihopeyoureokay[89853:2434827] Could not cast value of type
'UIViewController' (0x110a5b940) to 'ihopeyoureokay.mainVC'
(0x1060cc918).
My full code in this file is:
class ThirdViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func Gotoactual(_ sender: Any) {
let storyboard = UIStoryboard(name: "actual", bundle: nil)
let mainVC = storyboard.instantiateViewController(identifier: "mainVC") as! mainVC
self.present(mainVC, animated: true, completion: nil)
}
}
and my sceneDelegate looks like this:
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let launchedBefore = UserDefaults.standard.bool(forKey: "hasLaunched")
let launchStoryboard = UIStoryboard(name: "Main", bundle: nil)
let mainStoryboard = UIStoryboard(name: "actual", bundle: nil)
var vc: UIViewController
if launchedBefore {
vc = mainStoryboard.instantiateInitialViewController()!
} else {
vc = launchStoryboard.instantiateViewController(identifier: "firststoryboard")
}
UserDefaults.standard.set(true, forKey: "hasLaunched")
self.window?.rootViewController = vc
self.window?.makeKeyAndVisible()
guard let _ = (scene as? UIWindowScene) else { return }
}
}
Here also another screenshot that could maybe help:
Does anyone know what I might be doing wrong?
You need to check your inspector, select your viewcontroller and check that must be as below:
The as! mainVC required you are have a class named mainVC
Also you are not need to create two different storyboards, instated you can create a two different controllers, and make your view controller according to your condition.
Put storyboard id as mainVC to get the code running!
First of all you don't need to use SceneDelegate.swift if you are not using SwiftUI. You should do following:
Make sure you have a storyboard named actual.storyboard.
Set MainViewController storyboard id to mainVC in property navigator.
Don't use force unwrap, use guard let or if let statements.
Here is a sample code:
class MainViewController: UIViewController {
...
}
class ThirdViewController: UIViewController {
...
#IBAction func goToActual(_ sender: Any) {
let storyboard = UIStoryboard(name: "actual", bundle: Bundle(for: MainViewController.self))
guard let mainVC = storyboard.instantiateViewController(identifier: "mainVC") as? MainViewController else {
print("Failed to get MainViewController instance!")
return
}
self.present(mainVC, animated: true, completion: nil)
}
}

why my nav controller is nil even after embedding navigation controller in story board and using push segue?

I have tried to read several threads on Stackoverflow, but I think that I have implemented all of them but still it doesn't work. here is my storyboard
so I hide (uncheck) the navigation bar visibility like the image below, because I want to implement my own 'navigation header' like in the image above (in the right) :
and when the back button is pressed, I use the code below:
self.navigationController?.popViewController(animated: true)
but unfortunately, after I check, the navigation controller is nil and I can't get back to previous VC.
I set some code in the app delegate like this, in order to set the navigation. if the user has already login then it will be navigated to HomeVC (main tab bar), otherwise it will be directed to login sequence like my storyboard above
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// to print Local Database Location, uncomment the line below if you want to trace the location of Realm Database / User Default
// print("Location of Realm Database: \(Realm.Configuration.defaultConfiguration.fileURL)")
checkHasLoggedInOrNot()
return true
}
}
extension AppDelegate {
// MARK: - Helper Methods
func checkHasLoggedInOrNot() {
let userHasLoggedIn = AuthService.shared.hasLoggedIn
if userHasLoggedIn {
goToMainTabBar()
} else {
goToAuthVC()
}
}
}
extension AppDelegate {
// MARK: - Navigation
func goToMainTabBar() {
let storyboard = UIStoryboard(name: StoryBoardName.Main.rawValue, bundle: nil)
let mainTabBar = storyboard.instantiateViewController(withIdentifier: MainStoryboardData.StoryBoardID.MainTabBar.rawValue)
window?.rootViewController = mainTabBar
}
func goToAuthVC() {
let storyboard = UIStoryboard(name: StoryBoardName.Auth.rawValue, bundle: nil)
let authVC = storyboard.instantiateViewController(withIdentifier: AuthStoryboardData.StoryBoardID.AuthVC.rawValue)
window?.rootViewController = authVC
}
}
maybe the problem is the code below ?
func goToAuthVC() {
let storyboard = UIStoryboard(name: StoryBoardName.Auth.rawValue, bundle: nil)
let authVC = storyboard.instantiateViewController(withIdentifier: AuthStoryboardData.StoryBoardID.AuthVC.rawValue)
window?.rootViewController = authVC
}
because it is pointed to AuthVC ? not to Navigation Controller ?
what went wrong in here ?
Push/Pop only be possible if there is Navigation stack in window.
Replace your goToAuthVC with following -
func goToAuthVC() {
let storyboard = UIStoryboard(name: StoryBoardName.Auth.rawValue, bundle: nil)
let authVC = storyboard.instantiateViewController(withIdentifier: AuthStoryboardData.StoryBoardID.AuthVC.rawValue)
let navigationController = UINavigationController(rootViewController: authVC)
window?.rootViewController = navigationController
window?.makeKeyAndVisible()
}

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

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