Push view controller not working in Swift - ios

let storyboard = UIStoryboard(name: "Main", bundle: nil)
let playGameViewController = (storyboard.instantiateViewController(withIdentifier: "PlayGameViewController") as! PlayGameViewController)
self.navigationController?.pushViewController(playGameViewController, animated: true)
push not worked but I tried present working, I want navigate with push.

Try running the below code to know where you have gone wrong.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
guard let playGameViewController = storyboard.instantiateViewController(withIdentifier: "PlayGameViewController") as? PlayGameViewController else {
print("This means you haven't set your view controller identifier properly.")
return
}
guard let navigationController = navigationController else {
print("This means you current view controller doesn't have a navigation controller")
return
}
navigationController.pushViewController(playGameViewController, animated: true)
Try using breakpoints to figure out if any variable is nil. In your case, there is more probability for having your navigationController being nil.

Related

EXC_BAD_ACCESS (code=2, address=0x16a60ffe0) [duplicate]

Issue
I started taking a look on the Swift Programming Language, and somehow I am not able to correctly type the initialization of a UIViewController from a specific UIStoryboard.
In Objective-C I simply write:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"StoryboardName" bundle:nil];
UIViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:#"ViewControllerID"];
[self presentViewController:viewController animated:YES completion:nil];
Can anyone help me on how to achieve this on Swift?
This answer was last revised for Swift 5.4 and iOS 14.5 SDK.
It's all a matter of new syntax and slightly revised APIs. The underlying functionality of UIKit hasn't changed. This is true for a vast majority of iOS SDK frameworks.
let storyboard = UIStoryboard(name: "myStoryboardName", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "myVCID")
self.present(vc, animated: true)
Make sure to set myVCID inside the storyboard, under "Storyboard ID."
For people using #akashivskyy's answer to instantiate UIViewController and are having the exception:
fatal error: use of unimplemented initializer 'init(coder:)' for class
Quick tip:
Manually implement required init?(coder aDecoder: NSCoder) at your destination UIViewController that you are trying to instantiate
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
If you need more description please refer to my answer here
This link has both the implementations:
Swift:
let viewController:UIViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("ViewController") as UIViewController
self.presentViewController(viewController, animated: false, completion: nil)
Objective C
UIViewController *viewController = [[UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil] instantiateViewControllerWithIdentifier:#"ViewController"];
This link has code for initiating viewcontroller in the same storyboard
/*
Helper to Switch the View based on StoryBoard
#param StoryBoard ID as String
*/
func switchToViewController(identifier: String) {
let viewController = self.storyboard?.instantiateViewControllerWithIdentifier(identifier) as! UIViewController
self.navigationController?.setViewControllers([viewController], animated: false)
}
akashivskyy's answer works just fine! But, in case you have some trouble returning from the presented view controller, this alternative can be helpful. It worked for me!
Swift:
let storyboard = UIStoryboard(name: "MyStoryboardName", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("someViewController") as! UIViewController
// Alternative way to present the new view controller
self.navigationController?.showViewController(vc, sender: nil)
Obj-C:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MyStoryboardName" bundle:nil];
UIViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"someViewController"];
[self.navigationController showViewController:vc sender:nil];
Swift 4.2 updated code is
let storyboard = UIStoryboard(name: "StoryboardNameHere", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "ViewControllerNameHere")
self.present(controller, animated: true, completion: nil)
// "Main" is name of .storybord file "
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
// "MiniGameView" is the ID given to the ViewController in the interfacebuilder
// MiniGameViewController is the CLASS name of the ViewController.swift file acosiated to the ViewController
var setViewController = mainStoryboard.instantiateViewControllerWithIdentifier("MiniGameView") as MiniGameViewController
var rootViewController = self.window!.rootViewController
rootViewController?.presentViewController(setViewController, animated: false, completion: nil)
This worked fine for me when i put it in AppDelegate
If you want to present it modally, you should have something like bellow:
let vc = self.storyboard!.instantiateViewControllerWithIdentifier("YourViewControllerID")
self.showDetailViewController(vc as! YourViewControllerClassName, sender: self)
I would like to suggest a much cleaner way. This will be useful when we have multiple storyboards
1.Create a structure with all your storyboards
struct Storyboard {
static let main = "Main"
static let login = "login"
static let profile = "profile"
static let home = "home"
}
2. Create a UIStoryboard extension like this
extension UIStoryboard {
#nonobjc class var main: UIStoryboard {
return UIStoryboard(name: Storyboard.main, bundle: nil)
}
#nonobjc class var journey: UIStoryboard {
return UIStoryboard(name: Storyboard.login, bundle: nil)
}
#nonobjc class var quiz: UIStoryboard {
return UIStoryboard(name: Storyboard.profile, bundle: nil)
}
#nonobjc class var home: UIStoryboard {
return UIStoryboard(name: Storyboard.home, bundle: nil)
}
}
Give the storyboard identifier as the class name, and use the below code to instantiate
let loginVc = UIStoryboard.login.instantiateViewController(withIdentifier: "\(LoginViewController.self)") as! LoginViewController
No matter what I tried, it just wouldn't work for me - no errors, but no new view controller on my screen either. Don't know why, but wrapping it in timeout function finally made it work:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.0) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "TabletViewController")
self.present(controller, animated: true, completion: nil)
}
Swift 3
let settingStoryboard : UIStoryboard = UIStoryboard(name: "SettingViewController", bundle: nil)
let settingVC = settingStoryboard.instantiateViewController(withIdentifier: "SettingViewController") as! SettingViewController
self.present(settingVC, animated: true, completion: {
})
Swift 4:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let yourVC: YourVC = storyboard.instantiateViewController(withIdentifier: "YourVC") as! YourVC
If you have a Viewcontroller not using any storyboard/Xib, you can push to this particular VC like below call :
let vcInstance : UIViewController = yourViewController()
self.present(vcInstance, animated: true, completion: nil)
I know it's an old thread, but I think the current solution (using hardcoded string identifier for given view controller) is very prone to errors.
I've created a build time script (which you can access here), which will create a compiler safe way for accessing and instantiating view controllers from all storyboard within the given project.
For example, view controller named vc1 in Main.storyboard will be instantiated like so:
let vc: UIViewController = R.storyboard.Main.vc1^ // where the '^' character initialize the controller
Swift 5
let vc = self.storyboard!.instantiateViewController(withIdentifier: "CVIdentifier")
self.present(vc, animated: true, completion: nil)
I created a library that will handle this much more easier with better syntax:
https://github.com/Jasperav/Storyboardable
Just change Storyboard.swift and let the ViewControllers conform to Storyboardable.
guard let vc = storyboard?.instantiateViewController(withIdentifier: "add") else { return }
vc.modalPresentationStyle = .fullScreen
present(vc, animated: true, completion: nil)
if let destinationVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "DestinationVC") as? DestinationVC{
let nav = self.navigationController
//presenting
nav?.present(destinationVC, animated: true, completion: {
})
//push
nav?.pushViewController(destinationVC, animated: true)
}
I use this helper:
struct Storyboard<T: UIViewController> {
static var storyboardName: String {
return String(describing: T.self)
}
static var viewController: T {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
guard let vc = storyboard.instantiateViewController(withIdentifier: Self.storyboardName) as? T else {
fatalError("Could not get controller from Storyboard: \(Self.storyboardName)")
}
return vc
}
}
Usage (Storyboard ID must match the UIViewController class name)
let myVC = Storyboard.viewController as MyViewController

Properly redirect to a view controller inside tab bar's navigation controller when push notification is clicked

I have a tabBar. Each of its tabs' ViewControllers have been embedded inside their respective NavigationControllers.
Hierarchy:
Tabbarcontroller --> NavigationController --> VC1 --> VC2 -->...VCn (for tab1)
I have to redirect to one of the viewcontrollers say VC2 when i click on push notifications. The code i am using is as follows.
let navigationController = UINavigationController()
let storyboard = UIStoryboard.init(name: "Main", bundle: Bundle.main)
if let chatViewController = storyboard.instantiateViewController(withIdentifier: "chatViewController") as? ChatViewController {
navigationController.viewControllers = [chatViewController]
window?.rootViewController = navigationController
}
By using this, I am redirected to the respective ViewController. However, i am not able to get back to the tabBar. So is it possible to redirect in such a way that it allows me the get back to the tab bar, thus providing the same hierarchy as in the UI?
EDIT:
The image below shows my layout.
I would like to accomplish the following:
When the user taps the push notification, the app should be directed to VC-B. *It should not create new object for VC-B and add to the navigation stack if VC-B is already on top of navigation stack.
If the app had been terminated and the user taps on the notification, it should open VC-B.
For determining if the app had been terminated, I set a flag as:
func applicationWillTerminate(_ application: UIApplication) {
UserDefaults.standard.set(true, forKey: UserDefaultsKey.isTerminated)
}
This flag is set false at the end of didFinishLaunchingWithOptions function.
For redirection, I check this flag to determine if the app had been terminated:
func performRedirectionToSuitableViewController(userInfo: [AnyHashable: Any]) {
let isTerminated = UserDefaults.standard.object(forKey: UserDefaultsKey.isTerminated) as! Bool
if isTerminated {
let storyboard = UIStoryboard.init(name: "Main", bundle: Bundle.main)
let tab = storyboard.instantiateViewController(withIdentifier: "tabBar") as! UITabBarController
tab.selectedIndex = 0
let nav = tab.viewControllers![0] as! UINavigationController
let chatViewController = storyboard.instantiateViewController(withIdentifier: "chatViewController") as! ChatViewController
chatViewController.chatThreadId = userInfo["thread_id"] as? String
nav.pushViewController(chatViewController, animated: true)
} else {
if let tab = window?.rootViewController?.presentedViewController as? UITabBarController {
let storyboard = UIStoryboard.init(name: "Main", bundle: Bundle.main)
let chatViewController = storyboard.instantiateViewController(withIdentifier: "chatViewController") as! ChatViewController
chatViewController.chatThreadId = userInfo["thread_id"] as? String
if let nav = tab.selectedViewController as? UINavigationController {
nav.pushViewController(chatViewController, animated: true)
}
}
}
}
With this code, my first requirement is partially fulfilled. Need to determine if the viewcontroller is at the top of the navigation stack.
And, in the case of terminated app, clicking the push notification opens the tab bar, with the default selection index being selected.
I have spent several days trying to fix this. But cannot get it work.
I suppose you have to do something like this:
let tabBar: UITabBarController // get your tab bar
tabBar.selectedIndex = 0 // eg. zero. To be sure that you are on correct tab
if let navigation = tabBar.viewControllers?[tabBar.selectedIndex] as? UINavigationController {
let storyboard = UIStoryboard.init(name: "Main", bundle: Bundle.main)
if let chatViewController = storyboard.instantiateViewController(withIdentifier: "chatViewController") as? ChatViewController {
navigation.pushViewController(chatViewController, animated: true)
}
}
Well, you are setting current window.rootViewController, which should be that TabBarViewController I'd say. That's why you cannot get back to the TabBar
What you should do is
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
if let chatViewController = storyboard.instantiateViewController(withIdentifier: "chatViewController") as? ChatViewController, let tabBar = storyboard.instantiateViewController(withIdentifier: "tabBar") as? UITabBarController {
let navigationController = UINavigationController(rootViewController: chatViewController)
navigationController.viewControllers = [chatViewController]
tabBar.viewControllers = [navigationController]
window?.rootViewController = tabBar
}
To get the current navigation controller from the tabbar controller. You can use this function
func getVisibleViewController(_ rootViewController: UIViewController?) -> UIViewController? {
var rootVC = rootViewController
if rootVC == nil {
rootVC = UIApplication.shared.keyWindow?.rootViewController
}
if rootVC?.presentedViewController == nil {
return rootVC
}
if let presented = rootVC?.presentedViewController {
if presented.isKind(of: UINavigationController.self) {
let navigationController = presented as! UINavigationController
return navigationController.viewControllers.last!
}
if presented.isKind(of: UITabBarController.self) {
let tabBarController = presented as! UITabBarController
return tabBarController.selectedViewController!
}
//UIAlertController
if presented.isKind(of: UIAlertController.self) {
let alertController = presented as! UIAlertController
return alertController.presentingViewController
}
return getVisibleViewController(presented)
}
return nil
}
Using the function you can navigate your viewcontroller like below.
let mainStoryboardIpad : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let yourView = mainStoryboardIpad.instantiateViewController(withIdentifier: "yourView") as! NewnoteViewController
let navi = self.getVisibleViewController(self.window!.rootViewController) as! UINavigationController
navi.pushViewController(yourView, animated: true)
You can create a method in your RootViewController that will redirect user to a specific view after receiving a push notification. Here's what I did in my previous project.
class RootViewController: UIViewController {
private var currentView: UIViewController
init() {
self.currentView = ViewController()
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
addChildViewController(currentView) // 1
currentView.view.frame = view.bounds // 2
view.addSubview(currentView.view) // 3
currentView.didMove(toParentViewController: self) // 4
}
func showDetailsScreen() {
if let updateDetailView = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "UpdateDetailsNotif") as? UpdateDetailsViewController{
let navController = UINavigationController(rootViewController: updateDetailView)
addChildViewController(navController)
navController.view.frame = view.bounds
view.addSubview(navController.view)
navController.didMove(toParentViewController: self)
currentView.willMove(toParentViewController: nil)
currentView.view.removeFromSuperview()
currentView.removeFromParentViewController()
currentView = navController
}
}
}
Then you can call that method on your AppDelegate like this:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
let application = UIApplication.shared
AppDelegate.shared.rootViewController.showDetailsScreen()
completionHandler()
}
}

Dissmiss from UITabBarController

I guess it can be duplicated, but I looking everywhere and didn't find a solution for me.
So about my question. I have something like this
open this image to see more
In my AppDelegate I have func
func logIn() {
let userExist = UserDefaults.standard.string(forKey: "auth_key_user")
if userExist != nil && userExist != "" {
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let whereToGo = storyboard.instantiateViewController(withIdentifier: "AllPostsOfUserTabBarController") as! AllPostsOfUserTabBarController
window?.rootViewController = whereToGo
}
}
If user exist it lead me to first view controller inside tab bar controller. There I have navigation button with action to logout.
I need log out(send data to the server) and then go to my first view controller with text field and button where I can again log in.
How do I need to implement it?
Try this code after logout-
DispatchQueue.main.sync() {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let loginVC = storyboard.instantiateInitialViewController()
appDelegate.window?.rootViewController = loginVC
}
Hope it helps!
Put this code in function of your logout button. It will throw you back to your root controller.
func logoutBtnClicked()
{
DispatchQueue.main.sync()
{
(send your data to server)
self.navigationController?.popToRootViewController(animated: true)
}
}
Try this in appdelegate
While Login
self.window?.rootViewController = whereToGo
While Logout
func setupLoginViewController() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let loginVC: UIViewController? = storyboard.instantiateViewController(withIdentifier: "loginVCID") as? UIViewController
let navController: UINavigationController? = UINavigationController(rootViewController: loginVC!) as UINavigationController
window?.rootViewController = navController
}

How to presentViewController embedded in UITabBarController and UINavigationBarController

When using 3D Touch Shortcuts from the home screen I am trying to segue to different view controller.
The application is embedded within a UITabBarController and each tab root controller is a UINavigationController.
Here is how I attempted handling the shortcuts to load the view controller for each shortcut.
private func handleShortcutItem(shortcutItem: UIApplicationShortcutItem) {
if let rootViewController = window?.rootViewController, let shortcutItemType = ShortcutItemType(shortcutItem: shortcutItem) {
let sb = UIStoryboard(name: "main", bundle: nil)
let helloVC = sb.instantiateViewControllerWithIdentifier("HelloVC") as! HelloViewController
let goodbyeVC = sb.instantiateViewControllerWithIdentifier("GoodbyeVC") as! GoodbyeViewController
switch shortcutItemType {
case .Hello:
rootViewController.presentViewController(helloVC, animated: true, completion: nil)
break
case .Goodbye:
rootViewController.presentViewController(goodbyeVC, animated: true, completion: nil)
break
}
}
}
With this code the shortcuts only open the application to the initial view controller and not to the helloVC and goodbyeVC controllers which are in different tabs.
I am presuming this is because the ViewControllers I am trying to load are embedded within a UINavigationController as well as embedded within the UITabBarController.
How can I presentViewController which is embedded within the UITabBarController and UINavigationController ?
UPDATE
I am unsure if the following works as I have not got a iPhone 6S with me atm. But I have changed the code to the following, hopefully this will load the selected tab index when the 3D Touch Action is performed. From there it should post a notification to the view controller to perform a segue.
private func handleShortcutItem(shortcutItem: UIApplicationShortcutItem) {
if let rootViewController = window?.rootViewController, let shortcutItemType = ShortcutItemType(shortcutItem: shortcutItem) {
let tababarController = rootViewController as! UITabBarController
switch shortcutItemType {
case .Hello:
tababarController.selectedIndex = 1
NSNotificationCenter.defaultCenter().postNotificationName("performsegueHello", object: nil)
break
case .Goodbye:
tababarController.selectedIndex = 4
NSNotificationCenter.defaultCenter().postNotificationName("performsegueGoodbye", object: nil)
break
}
}
}
Please try the following code. I think you are not reaching the navigation controller of tab bar.you can do this by :
let insideNvc = tvc?.selectedViewController as? UINavigationController
Now, here is your navigation controller you can present or push anything on it.
private func handleShortcutItem(shortcutItem: UIApplicationShortcutItem) {
let nvc = self.window?.rootViewController as? UINavigationController
let tvc = nvc?.topViewController as? TabBarController
let insideNvc = tvc?.selectedViewController as? UINavigationController
if let shortcutItemType = ShortcutItemType(shortcutItem: shortcutItem) {
let sb = UIStoryboard(name: "main", bundle: nil)
let helloVC = sb.instantiateViewControllerWithIdentifier("HelloVC") as! HelloViewController
let goodbyeVC = sb.instantiateViewControllerWithIdentifier("GoodbyeVC") as! GoodbyeViewController
switch shortcutItemType {
case .Hello:
insideNvc?.presentViewController(helloVC, animated: true, completion: nil)
break
case .Goodbye:
insideNvc?.presentViewController(goodbyeVC, animated: true, completion: nil)
break
}
}
}

using presentviewcontroller inside a function swift

I am trying to use this inside my function to present a view controller after a function runs, so included it inside
let window = UIWindow()
let rootViewController = window.rootViewController
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
let setViewController = mainStoryboard.instantiateViewControllerWithIdentifier("EmployeeTBCIdentifier")
rootViewController?.presentViewController(setViewController, animated: true, completion: nil)
It's not doing anything, it works if I use it inside viewDidLoad()
here's the main idea, and would it be possible to use completions according to this example,
func logInType(completion(Void1, Void2)->()) {
if login == employer{
void1
}else if login == employee{
void2
}
}
when using the function I want to return this
func logInType{(void1, void2) -> void in
void1 = self.presentview //I want to push view to another if this code executed
void2 = self.presentview //same idea
}
updated : code is working but it's not pushing to the viewcontroller identified.
dispatch_async(dispatch_get_main_queue()){
let window = UIWindow()
let rootViewController = window.rootViewController
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
let setViewController = mainStoryboard.instantiateViewControllerWithIdentifier("EmployeeTBCIdentifier")
rootViewController?.presentViewController(setViewController, animated: true, completion: nil)
}
Seems to me that there might be a problem in how you are getting the rootViewController. I think the let window = UIWindow() call is going to return a new UIWindow object instead of what you want which is the window that the root view controller (and your app) is associated with. Continuing this, rootViewController will be nil and your optional chain call rootViewController?.presentViewController(setViewController, animated: true, completion: nil) won't do anything. Use an explicit unwrap via rootViewController!.presentViewController(setViewController, animated: true, completion: nil) and you should see the error
To rectify, get the correct UIWindow another way, either from the UIAppDelegate or from another view controller using OtherViewController.view!.window!
Try using it like so:
dispatch_async(dispatch_get_main_queue())
{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("mainView") as! MainViewController
self.presentViewController(vc, animated: true, completion: nil)
}

Resources