Custom segue to a different storyboard - ios

Question:
How might one write a custom segue that would allow you to embed view controllers from a different storyboard?
Context:
I am trying to write a custom segue with which I can link from one storyboard to another. A good article on atomicobject.com illustrates how to create a segue that originates from a button / event etc. Translated into swift, and allowing for non UINavigationControllers, the code looks like:
public class SegueToStoryboard : UIStoryboardSegue {
private class func viewControllerInStoryBoard(identifier:String, bundle:NSBundle? = nil)
-> UIViewController?
{
let boardScene = split(identifier, { $0 == "." }, maxSplit: Int.max, allowEmptySlices: false)
switch boardScene.count {
case 2:
let sb = UIStoryboard(name: boardScene[0], bundle: bundle)
return sb.instantiateViewControllerWithIdentifier(boardScene[1]) as? UIViewController
case 1:
let sb = UIStoryboard(name: boardScene[0], bundle: bundle)
return sb.instantiateInitialViewController() as? UIViewController
default:
return nil
}
}
override init(identifier: String!,
source: UIViewController,
destination ignore: UIViewController) {
let target = SegueToStoryboard.viewControllerInStoryBoard(identifier, bundle: nil)
super.init(identifier: identifier, source: source,
destination:target != nil ? target! : ignore)
}
public override func perform() {
let source = self.sourceViewController as UIViewController
let dest = self.destinationViewController as UIViewController
source.addChildViewController(dest)
dest.didMoveToParentViewController(source)
source.view.addSubview(dest.view)
// source.navigationController?.pushViewController(dest, animated: true)
}
}
Problem:
The problem that I am having with both their Obj-C and the above Swift code is that when you try to use the via a container view (with semantics of an embed segue - starting with an embed segue, deleting the segue, and then use the above custom segue), it crashes before ever calling the segue code with the following method-not-found error:
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<UIStoryboardSegueTemplate 0x7ffc8432a4f0>
setValue:forUndefinedKey:]: this class is not key value
coding-compliant for the key containerView.'
I have tried to inspect the address listed but get no meaningful results. I do the see the bold statement that it expecting the containerView but don't know how one might isolate, satisfy, and/or work around this problem.
Summary:
My end goal is to embed view controllers defined in separate storyboards to facilitate collaboration and testing without having to write additional code (a non invasive solution). Does anyone have any insight into how to accomplish this greater task? I could fall back to hybrid approach of calling performSegue, but would like to find a single, contained, and complete solution. The above code gets there for event driven (buttons etc) segues, but not with the embed segue.
Any input is appreciated, thanks in advance.

Your approach works fine for custom segues to push / display modally other view controllers but not for embed segues. The reason for this is that the "Embed" segue is not a subclass of UIStoryboardSegue but inherits from UIStoryboardSegueTemplate, which is a private API.
Unfortunately I couldn't find a better way to achieve what you want than with the hybrid approach.

My way is to link the containerView and delete the viewDidLoad segue from it. and manually call the segue on viewdidLoad
public protocol EmbeddingContainerView {
var containerView: UIView! { get set }
}
public class CoreSegue: UIStoryboardSegue {
public static func instantiateViewControllerWithIdentifier(identifier: String) -> UIViewController {
let storyboard = UIStoryboard(name: "Core", bundle: NSBundle(forClass: self))
let controller = storyboard.instantiateViewControllerWithIdentifier(identifier) as! UIViewController
return controller
}
var isPresent = false
var isEmbed = false
override init!(identifier: String?, source: UIViewController, destination: UIViewController) {
if var identifier = identifier {
if identifier.hasPrefix("present ") {
isPresent = true
identifier = identifier.substringFromIndex(advance(identifier.startIndex, count("present ")))
}
if identifier.hasPrefix("embed ") {
isEmbed = true
identifier = identifier.substringFromIndex(advance(identifier.startIndex, count("embed ")))
}
let controller = CoreSegue.instantiateViewControllerWithIdentifier(identifier)
super.init(identifier: identifier, source: source, destination: controller)
} else {
super.init(identifier: identifier, source: source, destination: destination)
}
}
public override func perform() {
if let source = sourceViewController as? UIViewController, dest = destinationViewController as? UIViewController {
if isPresent {
let nav = UINavigationController(rootViewController: dest)
nav.navigationBarHidden = true // you might not need this line
source.presentViewController(nav, animated: true, completion: nil)
} else if isEmbed {
if let contentView = (source as? EmbeddingContainerView)?.containerView {
source.addChildViewController(dest)
contentView.addSubview(dest.view)
dest.view.fullDimension() // which comes from one of my lib
}
} else {
source.navigationController?.pushViewController(destinationViewController as! UIViewController, animated: true)
}
}
}
}
and later in your code:
class MeViewController: UIViewController, EmbeddingContainerView {
#IBOutlet weak var containerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
performSegueWithIdentifier("embed Bookings", sender: nil)
}
}

Related

Framework Delegates in swift

Hi I created a simple framework with a delegate to return back a value to my main app.
Steps I did:
Created a framework in Xcode
Created a protocol as public
Imported my framework in main app project and integrated my protocol successfully
But not able to grab the value from framework
In framework
public protocol MyDataSendingDelegateProtocol: NSObject {
func sendDataToFirstViewController(myData: String)
}
public var delegate: MyDataSendingDelegateProtocol? = nil
Sending a value form framework as
delegate?.sendDataToFirstViewController(myData: "hello world")
In the main app
class ViewController: UIViewController, MyDataSendingDelegateProtocol{
func sendDataToFirstViewController(myData: String) {
print("from frame work \(myData)")
}
}
Accessing my framework VC
public class OOB_View : UIViewController {
public func registraionView() -> UIViewController {
let storyboard = UIStoryboard.init(name: "main", bundle: Bundle(for: oobRegVc.self))
let homeVC = storyboard.instantiateViewController(withIdentifier: "view") as! oobRegVc
return homeVC
}
}
In your framework modify the registraionView method to accept the delegate parameter of type MyDataSendingDelegateProtocol and then set it as homeVC's delegate, i.e.
public class OOB_View : UIViewController {
public func registraionView(delegate: MyDataSendingDelegateProtocol) -> UIViewController {
let storyboard = UIStoryboard.init(name: "main", bundle: Bundle(for: oobRegVc.self))
let homeVC = storyboard.instantiateViewController(withIdentifier: "view") as! oobRegVc
homeVC.delegate = delegate
return homeVC
}
}
Now, in your main app, call the method registraionView(delegate:) like so,
let oobClass = OOB_View()
let x = oobClass.registraionView(delegate: self)
x.modalPresentationStyle = .fullScreen
self.present(x, animated: true, completion: nil)
The above code must be somewhere in the ViewController conforming to MyDataSendingDelegateProtocol.

iOS New-Contact-Style Segue in Swift

I am trying to emulate the iOS contacts segueing between two view controllers.
I have a simple Person class given by:
class Person {
var name = ""
}
and a UIViewController that contains an array of Person, which is embedded in a UINavigationController:
class PeopleViewController: UIViewController {
var people = [Person]()
var selectedPerson: Person?
switch segueIdentifier(for: segue) {
case .showPerson:
guard let vc = segue.destination as? PersonViewController else { fatalError("!") }
vc.person = selectedPerson
}
}
This controller uses a Show segue to PersonViewController to display the selectedPerson:
class PersonViewController: UIViewController {
var person: Person!
}
PeopleViewController can also add a new Person to the array of Person. The NewPersonViewController is presented modally, however:
class NewPersonViewController: UIViewController {
var person: Person?
}
If a new Person is added, I want NewPersonViewController to dismiss but show the new Person in the PersonViewController that is part of the navigation stack. My best guess for doing this is:
extension NewPersonViewController {
func addNewPerson() {
weak var pvc = self.presentingViewController as! UINavigationController
if let cvc = pvc?.childViewControllers.first as? PeopleViewController {
self.dismiss(animated: false, completion: {
cvc.selectedPerson = self.person
cvc.performSegue(withIdentifier: .showPerson, sender: nil)
}
}
}
}
However, (1) I'm not too happy about forcing the downcast to UINavigationController as I would have expected self.presentingViewController to be of type PeopleViewController? And (2), is there a memory leak in the closure as I've used weak var pvc = self.presentingViewController for pvc but not for cvc? Or, finally (3) is there a better way of doing this?
Many thanks for any help, suggestions etc.
(1) I'm not too happy about forcing the downcast to UINavigationController as I would have expected self.presentingViewController to be of type PeopleViewController?
There is nothing wrong in downcasting. I would definitely remove force unwrapping.
(2), is there a memory leak in the closure as I've used weak var pvc = self.presentingViewController for pvc but not for cvc?
I think, there is none.
(3) is there a better way of doing this?
You can present newly added contact from NewContactVC. When you about to dismiss, call dismiss on presentingVC.
// NewPersonViewController.swift
func addNewPerson() {
// New person is added
// Show PeopleViewController modally
}
Note: Using presentingViewController this way will dismiss top two/one Modal(s). You will see only top view controller getting dismissed.
If you can't determine how many modals going to be, you should look-into different solution or possibly redesigning navigation flow.
// PeopleViewController.swift
func dismiss() {
if let presentingVC = self.presentingViewController?.presentingViewController {
presentingVC.dismiss(animated: true, completion: nil)
} else {
self.dismiss(animated: true, completion: nil)
}
}

How to push user to ViewController from non UIView class [duplicate]

This question already has answers here:
How to launch a ViewController from a Non ViewController class?
(7 answers)
Closed 5 years ago.
I would like to know how can I push user back to specific ViewController from regular swift class without being non UIView Class
Example
class nonUI {
function Push() {
//How to send user back to specific VC here?
}
}
This is a generic method you can use with in the class or outside the class for push if required else it will pop if the instance of view controller is in the stack:
func pushIfRequired(className:AnyClass) {
if (UIViewController.self != className) {
print("Your pushed class must be child of UIViewController")
return
}
let storyboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
var isPopDone = false
let mainNavigation = UIApplication.shared.delegate?.window??.rootViewController as? UINavigationController
let viewControllers = mainNavigation!.viewControllers
for vc in viewControllers {
if (type(of: vc) == className) {
mainNavigation?.popToViewController(vc, animated: true)
isPopDone = true
break
}
}
if isPopDone == false{
let instanceSignUp = storyboard.instantiateViewController(withIdentifier: NSStringFromClass(className)) // Identifier must be same name as class
mainNavigation?.pushViewController(instanceSignUp, animated: true)
}
}
USES
pushIfRequired(className: SignupVC.self)
You could also utilise the NotificationCenter to achieve a loosely coupled way to "request a view controller"; if you will.
For example, create a custom UINavigationController that observes for the custom Notification and upon receiving one, looks for the requested UIViewController and pops back to it.
class MyNavigationController : UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(forName: NSNotification.Name("RequestViewController"), object: nil, queue: OperationQueue.main) { [unowned self] (note) in
guard let targetType = note.object as? UIViewController.Type else {
print("Please provide the type of the VC to display as an `object` for the notification")
return
}
// Find the first VC of the requested type
if let targetVC = self.viewControllers.first(where: { $0.isMember(of: targetType) }) {
self.popToViewController(targetVC, animated: true)
}
else {
// do what needs to be done? Maybe instantiate a new object and push it?
}
}
}
}
Then in the object you want to go back to a specific ViewController, post the notification.
#IBAction func showViewController(_ sender: Any) {
NotificationCenter.default.post(Notification(name: NSNotification.Name("RequestViewController"), object: ViewController2.self))
}
Now, it's also fairly easy to adopt this method for other presentation-styles.
Instead of using the NotificationCenter, you could also muster up a Mediator to achieve the loose coupling.
You can't. UIViewController and its subclass only can handle navigate between screen.
In your case, need pass link (variable) to navigation controller in custom class.
Like:
class nonUI {
var navigationController: UINavigationController?
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
function Push() {
//How to send user back to specific VC here?
navigationController?.popViewController(animated: true)
}
}

Prevent runtime crash for function instantiateViewController(withIdentifier:)

Swift 3.0
From what I found here is UIStoryboard always return non-optional instance in function instantiateViewController(withIdentifier:).
open class UIStoryboard : NSObject {
...
open func instantiateViewController(withIdentifier identifier: String) -> UIViewController
}
The crash happen if we adding wrong identifier value, without noticed.
This case might happen in complex projects which have large number of controller in many storyboards, and a controller StoryboardId is missed.
The only solution I found is making a function to temporary create all controllers when start app delegate (this function should be called in debugging mode only).
func validateControllers() {
guard let _ = xyzStoryboard.instantiateViewController(withIdentifier: "ABC") as? ABCViewController else {
fatalError("fail in init controller ABC from storyboard XYZ")
}
...
}
But I wonder if we could have another way to handle this situation. Or should I raise this issue to Swift team? Thanks!
as far as i know there is no solution for preventing crashes like this. but this is a good thing and it's meant to crash like this to show you there is something wrong with your code and you need to fix them!
Writing this answer to just to let you know how we handled the accidental typo(s) of a View Controller Identifier, which could lead to error when you try to create an ViewController from Storyboard(s) using view controller's identifier.
We had a complex project which had almost 15-20 ViewControllers, we didn't put them in a single storyboard instead we shared these VCs across multiple story boards and then we created an object called StoryBoardManager, which would create an VC from various storyboard(s) and hand it over to us.
We also created couple of enums to represent various storyboard(s) and viewController(s) inside.
It somewhat looks like this,
enum Storyboards : String {
case main = "Main"
case signup = "SignUp"
case discover = "Discover"
case utility = "Utility"
case event = "Event"
}
enum ViewControllers : String {
case login = "login"
case onBoard = "on_board"
case signup = "signup"
case signupNavigation = "signupNavigaitonVC"
case discoverNavigation = "discoverNavigation"
case individualProfileSetUp = "individualProfileSetUp"
case organizationProfileSetUp = "organizationProfileSetUp"
case discover = "discover"
case imagePickerMenuVC = "imagePickerMenuVC"
case eventDiscoverMapNavigation = "eventDiscoverMapNavigationVC"
case eventDiscoverMapVC = "eventDiscoverMapVC"
case event = "event"
}
class StoryboardManager {
static func storyboard(name:Storyboards) -> UIStoryboard {
let aStoryboard = UIStoryboard(name: name.rawValue, bundle: nil)
return aStoryboard
}
static func viewController(storyboardId:Storyboards, viewControllerId:ViewControllers) -> UIViewController {
let storyboard = StoryboardManager.storyboard(storyboardId)
let viewcontroller = storyboard.instantiateViewControllerWithIdentifier(viewControllerId.rawValue)
return viewcontroller
}
}
We mainly did this to avoid the typo mistakes for the ViewController identifier(s), which would lead to an runtime error. You will add all the viewController(s) identifiers to ViewControllers enum and storyboard(s) names to the Storyboards enum as a enum case. We updated both the enums whenever we introduced a new storyboard or a new ViewController in any of the storyboards and this helped all the team members not to make the typo for ViewController identifier.
Then we created ViewController(s) using the below code,
let loginVC = StoryboardManager.viewController(.main, viewControllerId: .login)
HTH :)
Short brief
Currently, I have a complex project that have 5 storyboards, with 20 controllers. In my case, using storyboard segue seems not a good idea. Therefore, creating controller's instance from storyboard and make a transition by code is much better.
But the problem is that instantiateViewController(withIdentifier:) always return non-optional instance, and it will be crashed if we put an invalid identifier (which didn't define in storyboard).
Solution (Swift 3.0)
There are steps to prevent this problem.
In *.storyboard, use controller class name as StoryboardID
Create UIStoryboard+Ext.swift to define all storyboards & controllers belong
Create BaseViewController.swift to define the initialization from storyboard
Create function to init all storyboards & controllers, to prevent crashing in runtime.
Download sample code from if you want to try yourself: https://github.com/nahung89/StoryboardPattern
Detail (TL;DR)
Step 1:
Step 2:
extension UIStoryboard {
enum Identifier: String {
case main = "Main"
case explore = "Explore"
case search = "Search"
case profile = "Profile"
}
static func name(for controller: UIViewController.Type) -> String? {
var identifier: Identifier?
switch controller.className {
case HomeViewController.className:
identifier = .main
case ExploreViewController.className:
identifier = .explore
case SearchViewController.className:
identifier = .search
case ProfileViewController.className:
identifier = .profile
default:
break
}
return identifier?.rawValue
}
}
extension UIStoryboard {
static func instanceFromIdentifier(_ identifier: Identifier) -> UIStoryboard {
return UIStoryboard(name: identifier.rawValue, bundle: Bundle.main)
}
static func instanceFromName(_ name: String) -> UIStoryboard? {
guard let identifier = Identifier(rawValue: name) else { return nil }
return instanceFromIdentifier(identifier)
}
}
Step 3:
extension UIViewController {
var className: String {
return String(describing: type(of: self))
}
class var className: String {
return String(describing: self)
}
}
class BaseViewController: UIViewController {
static var controllerId: String {
return String(describing: self) // return slass name, i.e "ExploreViewController"
}
static func instanceFromStoryboard() -> Self? {
guard let storyboardName = UIStoryboard.name(for: self) else { return nil }
return instantiateFrom(storyboardName: storyboardName)
}
private static func instantiateFrom<VC: UIViewController>(storyboardName: String) -> VC? {
let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: controllerId) as? VC
return controller
}
}
Step 4
extension AppDelegate {
func validateStoryboards() {
guard
let _ = UIStoryboard.instanceFromName(UIStoryboard.Identifier.main.rawValue),
let _ = UIStoryboard.instanceFromName(UIStoryboard.Identifier.explore.rawValue),
let _ = UIStoryboard.instanceFromName(UIStoryboard.Identifier.profile.rawValue),
let _ = UIStoryboard.instanceFromName(UIStoryboard.Identifier.search.rawValue)
else {
fatalError("fail to init storyboard by name")
}
guard let _ = HomeViewController.instanceFromStoryboard(),
let _ = ExploreViewController.instanceFromStoryboard(),
let _ = ProfileViewController.instanceFromStoryboard(),
let _ = SearchViewController.instanceFromStoryboard()
else {
fatalError("fail to init controller from storyboard")
}
}
}

UIViewController extension to instantiate from storyboard

I'm trying to write a little extension in Swift to handle instantiation of a UIViewController from a storyboard.
My idea is the following: Since UIStoryboard's method instantiateViewControllerWithIdentifier needs an identifier to instantiate a given storyboard's view controller, why don't assign every view controller in my storyboard an identifier equal to its exact class name (i.e a UserDetailViewController would have an identifier of "UserDetailViewController"), and, create a class method on UIViewController that would:
accept a UIStoryboard instance as a unique parameter
get the current class name as a string
call instantiateViewControllerWithIdentifier on the storyboard instance with the class name as a parameter
get the newly created UIViewController instance, and return it
So, instead of (which repeats the class name as a string, not very nice)
let vc = self.storyboard?.instantiateViewControllerWithIdentifier("UserDetailViewController") as UserDetailViewController
it would be:
let vc = UserDetailViewController.instantiateFromStoryboard(self.storyboard!)
I used to do it in Objective-C with the following category:
+ (instancetype)instantiateFromStoryboard:(UIStoryboard *)storyboard
{
return [storyboard instantiateViewControllerWithIdentifier:NSStringFromClass([self class])];
}
But I'm completely stuck with the Swift version. I hope is that there is some kind of way to do it.
I tried the following:
extension UIViewController {
class func instantiateFromStoryboard(storyboard: UIStoryboard) -> Self {
return storyboard.instantiateViewControllerWithIdentifier(NSStringFromClass(Self))
}
}
Returning Self instead of AnyObject allows the type inference to work. Otherwise, I would have to cast every single return of this method, which is annoying, but maybe you have a better solution?
This gives me the error: Use of unresolved identifier 'Self'
The NSStringFromClass part seems to be the problem.
What do you think?
Is there any way to return Self from class functions?
How would you get this working without the need to cast the return value every time? (i.e keeping -> Self as return value)
How about writing an extension to UIStoryboard instead of UIViewController?
extension UIStoryboard {
func instantiateVC<T: UIViewController>() -> T? {
// get a class name and demangle for classes in Swift
if let name = NSStringFromClass(T.self)?.componentsSeparatedByString(".").last {
return instantiateViewControllerWithIdentifier(name) as? T
}
return nil
}
}
Even adopting this approach, cost of an use side is low as well.
let vc: UserDetailViewController? = aStoryboard.instantiateVC()
Thanks to MartinR and his answer, I know the answer:
UPDATE: rewritten with a protocol.
Instantiable
protocol StringConvertible {
var rawValue: String {get}
}
protocol Instantiable: class {
static var storyboardName: StringConvertible {get}
}
extension Instantiable {
static func instantiateFromStoryboard() -> Self {
return instantiateFromStoryboardHelper()
}
private static func instantiateFromStoryboardHelper<T>() -> T {
let identifier = String(describing: self)
let storyboard = UIStoryboard(name: storyboardName.rawValue, bundle: nil)
return storyboard.instantiateViewController(withIdentifier: identifier) as! T
}
}
//MARK: -
extension String: StringConvertible { // allow string as storyboard name
var rawValue: String {
return self
}
}
StoryboardName
enum StoryboardName: String, StringConvertible {
case main = "Main"
//...
}
Usage:
class MyViewController: UIViewController, Instantiable {
static var storyboardName: StringConvertible {
return StoryboardName.main //Or you can use string value "Main"
}
}
let viewController = MyController.instantiateFromStoryboard()
you can create UIViewController Instance like this:
Create enum with all your storyboard name.
enum AppStoryboard: String {
case main = "Main"
case profile = "Profile"
}
Then, here is the extension for instantiate UIViewController
extension UIViewController {
class func instantiate<T: UIViewController>(appStoryboard: AppStoryboard) -> T {
let storyboard = UIStoryboard(name: appStoryboard.rawValue, bundle: nil)
let identifier = String(describing: self)
return storyboard.instantiateViewController(withIdentifier: identifier) as! T
}
}
Usage:
let profileVC: ProfileVC = ProfileVC.instantiate(appStoryboard: .profile)
self.navigationController?.pushViewController(profileVC,animated:true)
We are porting our objective c project to swift. We have split the project into modules. Modules have their own storyboards. We have extended your(even our's as well) problem's solution to one more level by avoiding explicit storyboard names.
// Add you modules here. Make sure rawValues refer to a stroyboard file name.
enum StoryModule : String {
case SomeModule
case AnotherModule = "AnotherModulesStoryBoardName"
// and so on...
}
extension UIStoryboard {
class func instantiateController<T>(forModule module : StoryModule) -> T {
let storyboard = UIStoryboard.init(name: module.rawValue, bundle: nil);
let name = String(T).componentsSeparatedByString(".").last
return storyboard.instantiateViewControllerWithIdentifier(name!) as! T
}
}
// Some controller whose UI is in a stroyboard named "SomeModule.storyboard",
// and whose storyboardID is the class name itself, ie "MyViewController"
class MyViewController : UIViewController {
// Controller Code
}
// Usage
class AClass
{
// Here we must alwasy provide explicit type
let viewController : MyViewController = UIStoryboard.instantiateController(forModule: StoryModule.SomeModule)
}
Two things:
Class constructors in Objective-C are convenience initializers in Swift. Use convenience init rather than class func.
NSStringFromClass(Self) with NSStringFromClass(self.type).
Here is a modern Swift example, based on #findall's solution:
extension UIStoryboard {
func instantiate<T>() -> T {
return instantiateViewController(withIdentifier: String(describing: T.self)) as! T
}
static let main = UIStoryboard(name: "Main", bundle: nil)
}
Usage:
let userDetailViewController = UIStoryboard.main.instantiate() as UserDetailViewController
I think it is ok to fail when trying to instantiate a view controller from a storyboard as this kind of problem should be detected soon.
Or, you can do so
func instantiateViewControllerWithIdentifier<T>(_ identifier: T.Type) -> T {
let identifier = String(describing: identifier)
return instantiateViewController(withIdentifier: identifier) as! T
}
Use protocol in UIViewController to reach your thoughts
let vc = YourViewController.instantiate(from: .StoryboardName)
You can see the use of my link :D
https://github.com/JavanC/StoryboardDesignable
You can add this extension :-
extension UIStoryboard{
func instantiateViewController<T:UIViewController>(type: T.Type) -> T? {
var fullName: String = NSStringFromClass(T.self)
if let range = fullName.range(of:".", options:.backwards, range:nil, locale: nil){
fullName = fullName.substring(from: range.upperBound)
}
return self.instantiateViewController(withIdentifier:fullName) as? T
}
}
And can instantiate view controller like this :-
self.storyboard?.instantiateViewController(type: VC.self)!
In complement for the version of #ChikabuZ, here mine that takes into account which bundle the storyboard is in (for example, if your storyboads are in another bundle than your app). I added also a small func if you want to use xib instead of storyboad.
extension UIViewController {
static func instantiate<TController: UIViewController>(_ storyboardName: String) -> TController {
return instantiateFromStoryboardHelper(storyboardName)
}
static func instantiate<TController: UIViewController>(_ storyboardName: String, identifier: String) -> TController {
return instantiateFromStoryboardHelper(storyboardName, identifier: identifier)
}
fileprivate static func instantiateFromStoryboardHelper<T: UIViewController>(_ name: String, identifier: String? = nil) -> T {
let storyboard = UIStoryboard(name: name, bundle: Bundle(for: self))
return storyboard.instantiateViewController(withIdentifier: identifier ?? String(describing: self)) as! T
}
static func instantiate<TController: UIViewController>(xibName: String? = nil) -> TController {
return TController(nibName: xibName ?? String(describing: self), bundle: Bundle(for: self))
}
}
I had a similar thought and settled on using the extension below. It still uses the normal instantiation process, but removes reliance on stringly typed Storyboard and View Controller names:
let myVC = UIStoryboard(.main).instantiate(MyViewController.self)
The return type above is pre-cast to MyViewController, not the standard UIViewController.
extension UIStoryboard {
enum Name: String {
case main = "Main"
case launch = "LaunchScreen"
case other = "Other"
}
convenience init(_ name: Name, bundle: Bundle? = nil) {
self.init(name: name.rawValue, bundle: bundle)
}
func instantiate<T: UIViewController>(_ type: T.Type) -> T {
instantiateViewController(withIdentifier: String(describing: type)) as! T
}
}
Note that you must ensure that each VC's Storyboard Identifier exactly matches its class name! Failure to do so will result in the exception:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Storyboard (<UIStoryboard: 0x6000035c04e0>) doesn't contain a view controller with identifier 'MyViewController''

Resources