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
Related
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.
In Objective-C if you need to pass an object to a ViewController you can do:
in destination .h file
#property NSObject * contact;
in sending .m file
MyVC *destVC = [self.storyboard instantiateViewControllerWithIdentifier:#"myVC"];
destVC.contact = contact;
[self.navigationController pushViewController:destVC animated:YES];
I am trying to do this in Swift with this code:
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let destVC = storyBoard.instantiateViewController(withIdentifier: "contactDetail")
//Following line causes error
//destVC.contact = latestContact
self.navigationController!.pushViewController(destVC, animated: true)
The VC launches ok but when I try to pass it a variable with destVC.contact = latestContact then compiler says
value of type ViewController has no such member contact.
Cast it to your subclassed view controller. Say your contact details view controller class name is ContactDetailsViewController
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
if let destVC = storyBoard.instantiateViewController(withIdentifier: "contactDetail") as? ContactDetailsViewController {
destVC.contact = latestContact
self.navigationController!.pushViewController(destVC, animated: true)
}
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
}
I am using this code for push SHOW and MODALLY programmatically in iOS Objective C.
And now want to know about Swift 3.
NewsDetailsViewController *vc = instantiateViewControllerWithIdentifier:#"NewsDetailsVCID"];
vc.newsObj = newsObj;
//--this(SHOW)
[self.navigationController pushViewController:vc animated:YES];
//-- or this(MODAL)
[self presentViewController:vc animated:YES completion:nil];
Push
do like
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("NewsDetailsVCID") as NewsDetailsViewController
vc.newsObj = newsObj
navigationController?.pushViewController(vc,
animated: true)
or safer
if let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "NewsDetailsVCID") as? NewsDetailsViewController {
viewController.newsObj = newsObj
if let navigator = navigationController {
navigator.pushViewController(viewController, animated: true)
}
}
present
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = self.storyboard?.instantiateViewControllerWithIdentifier("NewsDetailsVCID") as! NewsDetailsViewController
vc.newsObj = newsObj
present(vc!, animated: true, completion: nil)
or safer
if let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "NewsDetailsVCID") as? NewsDetailsViewController
{
vc.newsObj = newsObj
present(vc, animated: true, completion: nil)
}
With an elegant way.
Create an Navigatable protocol:
protocol Navigatable {
/// Storyboard name where this view controller exists.
static var storyboardName: String { get }
/// Storyboard Id of this view controller.
static var storyboardId: String { get }
/// Returns a new instance created from Storyboard identifiers.
static func instantiateFromStoryboard() -> Self
}
Create a default instantiate controller implementation:
/**
Extension of Navigatable protocol with default implementations.
*/
extension Navigatable {
static func instantiateFromStoryboard() -> Self {
let storyboard = UIStoryboard(name: self.storyboardName, bundle: nil)
guard
let viewController = storyboard
.instantiateViewController(withIdentifier: self.storyboardId) as? Self else {
fatalError("Cannot instantiate the controller.")
}
return viewController
}
}
Extends the UIViewController to push a view controller:
extension UIViewController {
/**
Pushes a view controller of the provided type.
- Parameter viewControllerType: Type of view controller to push.
- Parameter completion: Function to be executed on completion.
Contains the view controller that was pushed when successful and nil otherwise.
*/
func pushViewControllerOfType<T: Navigatable>(viewControllerType: T.Type, completion: (T) -> Void) {
let viewController = T.instantiateFromStoryboard()
if let vc = viewController as? UIViewController {
self.pushViewController(vc, animated: true)
}
completion(viewController)
}
/**
Pushes a view controller of the provided type.
- Parameter viewControllerType: Type of view controller to push.
*/
func pushViewControllerOfType<T: Navigatable>(viewControllerType: T.Type) {
self.pushViewControllerOfType(viewControllerType: viewControllerType) { _ in }
}
}
Then you can use the Navigatable protocol for a specific view controller.
class MySuperViewController {
override func viewDidLoad() {
...
}
// ...
}
extension MySuperViewController: Navigatable {
static var storyboardName: String {
return "Main"
}
static var storyboardId: String {
return "MySuperViewControllerId" // From your story board name Main
}
}
// Instantiate your controller
let vc = MySuperViewController.instantiateFromStoryboard()
// Or
//
// Push your view controller
// testViewController.swift
self.pushViewControllerOfType(viewControllerType: MySuperViewController)
//Create object of view controller
let obj = self.storyboard?.instantiateViewController(withIdentifier: "ViewControllerIdentifier”) as! ViewControllerName
//Push Controller
self.navigationController?.pushViewController(obj, animated: true)
//Present Controller
self.navigationController?.present(obj, animated: true, completion: nil)
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)
}