16

In my code I have this line, but I was wondering if there is way to check whether @"SomeController" exists before I use it with the "instantiateViewControllerWithIdentifier" method. If the identifier doesn't exist then the app crashes.

It's not a huge problem if there isn't a good way to do it, I can just be a bit more careful not to fat finger the identifier names, but I was hoping I could handle it more gracefully.

UIViewController *newTopViewController = [self.storyboard    instantiateViewControllerWithIdentifier:@"SomeController"];
Eric
  • 3,865
  • 4
  • 32
  • 44
whisperstream
  • 1,897
  • 3
  • 20
  • 25
  • The idea is that the storyboard is part of your project, along with your code. If the identifier is ever incorrect it's a logic error in your code, not a recoverable failure due to some unforeseen circumstances at runtime. – Jon Hess Aug 27 '13 at 10:01

6 Answers6

7

As Tom said, the best solution to this problem is the try-catch block:

@try {
        UIViewController *newViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"identifier"];

    }
    @catch (NSException *exception) {
        UIAlertView *catchView;

        catchView = [[UIAlertView alloc]
                     initWithTitle: NSLocalizedString(@"Error", @"Error")
                     message: NSLocalizedString(@"Identifier not found on SB".", @"Error")
                     delegate: self
                     cancelButtonTitle: NSLocalizedString(@"OK", @"Error") otherButtonTitles: nil];

        [catchView show];
    }

I hope it helps! even though the answer is really late.

VaroX
  • 430
  • 4
  • 17
7

You can use valueForKey: on UIStoryboards. UIStoryboards have a key called "identifierToNibNameMap", its value is an NSDictionary with the UIViewControllers in that storyboard. This inner NSDictionary uses the viewcontroller's names as keys so you can actually check if a viewcontroller exists in a storyboard with the following code:

if ([[storyboard valueForKey:@"identifierToNibNameMap"] objectForKey:myViewControllerName]) {
    // the view controller exists, instantiate it here
    UIViewController* myViewController = [storyboard instantiateViewControllerWithIdentifier:myViewControllerName];
} else {
    //the view controller doesn't exist, do fallback here
}

Note: Apple has been known to reject apps that query the underlying properties of cocoa classes using valueForKey:. These underlying properties could change at any time in the future, breaking app functionality without warning. There is no deprecation process for these things.

Rajesh Loganathan
  • 11,129
  • 4
  • 78
  • 90
Kevin
  • 2,739
  • 33
  • 57
7

@Kevin's solution works. Here is a pretty the same piece of code for Swift 3 as function, that I am using in my code:

func instantiateViewController(fromStoryboardName storyboardName: String, withIdentifier identifier: String) -> UIViewController? {
    let mainStoryboard = UIStoryboard(name: storyboardName, bundle: nil)
    if let availableIdentifiers = mainStoryboard.value(forKey: "identifierToNibNameMap") as? [String: Any] {
        if availableIdentifiers[identifier] != nil {
            if let poiInformationViewController = mainStoryboard.instantiateViewController(withIdentifier: identifier) as? UIViewController {
                return viewController
            }
        }
    }
    return nil
}

Use this function as follows:

if let viewController = self.instantiateViewController(fromStoryboardName: "YourStoryboardName", withIdentifier: "YourViewControllerStoryboardID") {
    // Here you are sure your viewController is available in the Storyboard
} else {
    print("Error: The Storyboard with the name YourStoryboardName or the Storyboard identifier YourViewControllerStoryboardID is not available")
}
dzensik
  • 695
  • 11
  • 20
3

No, there is no check for this. However, you don't need to. This method will return nil if the identifier doesn't exist, so just check for that with an NSAssert.

EDIT Actually this is wrong!! That's weird...the return value section of the documentation contradicts another portion...but still the answer is ultimately no (there is no method to check for the existence of an identifier)

borrrden
  • 33,256
  • 8
  • 74
  • 109
1

You can wrap the code with try-catch exception handling and decide how to react if such an exception occurs. I use this method to dynamically instantiate view controllers without having to know if they are represented in the Storyboard or a nib file.

Tom Winter
  • 19
  • 1
1

Swift 4.2.

Declare an extension below.

extension UIStoryboard {
    func instantiateVC(withIdentifier identifier: String) -> UIViewController? {
        // "identifierToNibNameMap" – dont change it. It is a key for searching IDs 
        if let identifiersList = self.value(forKey: "identifierToNibNameMap") as? [String: Any] {
            if identifiersList[identifier] != nil {
                return self.instantiateViewController(withIdentifier: identifier)
            }
        }
        return nil
    }
}

Use this methods like this anywhere:

if let viewController = self.storyboard?.instantiateVC(withIdentifier: "yourControllerID") {
            // Use viewController here
            viewController.view.tag = 0; // for example
        }

or

    if let viewController = UIStoryboard(name: "yourStoryboardID", bundle: nil).instantiateVC(withIdentifier: "yourControllerID") {
        // Use viewController here
        viewController.view.tag = 0; // for example
    }

Replace "yourControllerID" with your controller's ID.

Agisight
  • 1,778
  • 1
  • 14
  • 15
  • Does Apple accept this? Note: Apple has been known to reject apps that query the underlying properties of cocoa classes using valueForKey:. These underlying properties could change at any time in the future, breaking app functionality without warning. There is no deprecation process for these things. – Mahesh NFC Dec 11 '19 at 07:04