What is the functional difference between instantiating a View Controller from the storyboard and creating a new instance of it? For example:
#import "SomeViewController.h"
...
SomeViewController *someViewController = [SomeViewController new];
versus
#import "SomeViewController.h"
...
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil];
SomeViewController *someViewController = [storyboard instantiateViewControllerWithIdentifier:#"SomeViewController"];
In either case, is someViewController effectively the same thing?
The main difference is in how the subviews of your UIViewController get instantiated.
In the second case, all the views you create in your storyboard will be automatically instantiated for you, and all the outlets and actions will be set up as you specified in the storyboard.
In the first, case, none of that happens; you just get the raw object. You'll need to allocate and instantiate all your subviews, lay them out using constraints or otherwise, and hook up all the outlets and actions yourself. Apple recommends doing this by overriding the loadView method of UIViewController.
In the second case, the view controller will load its view from the storyboard and you will be happy.
In the first case, it won't. Unless you've taken other steps (like overriding loadView or viewDidLoad or creating a xib named SomeViewController.xib), you'll just get an empty white view and be sad.
In Swift you can do the same with,
var someVC = self.storyboard?.instantiateViewControllerWithIdentifier("SomeViewController") as! SomeViewController
You will need to give the Identifier in the Storyboard to the SomeViewController and tick the checkmark to Use Storyboard ID
It is not the same thing. In the storyboard you probably have some UI elements laid out. They might have constraints and properties setup through the storyboard. When you instantiate the viewcontroller via the storyboard, you are getting all the instructions for where those subviews are and what their properties are. If you just say [SomeViewController new] you are not getting all the instructions that the storyboard has for the view controller.
A nice test will be to add a UIViewController to a storyboard and drag a red view onto it. Instantiate it using both methods and see what the differences are.
simple swift 3 extension
fileprivate enum Storyboard : String {
case main = "Main"
}
fileprivate extension UIStoryboard {
static func loadFromMain(_ identifier: String) -> UIViewController {
return load(from: .main, identifier: identifier)
}
static func load(from storyboard: Storyboard, identifier: String) -> UIViewController {
let uiStoryboard = UIStoryboard(name: storyboard.rawValue, bundle: nil)
return uiStoryboard.instantiateViewController(withIdentifier: identifier)
}
}
// MARK: App View Controllers
extension UIStoryboard {
class func loadHomeViewController() -> HomeViewController {
return loadFromMain("HomeViewController") as! HomeViewController
}
}
In case you don't want to instantiate a new VC using instantiateViewControllerWithIdentifier but accessing the instance created by the storyboard from the AppDelegate:
create a property in AppDelegate.h so it will be accessible from classes using it
#property (nonatomic, strong) myViewControllerClass*vC;
in viewDidLoad inside myViewControllerClass.m I access the shared instance of AppDelegate and feed the property with self: [AppDelegate sharedInstance].vC = self;
I had to use this solution in a complex storyboard and still can't get over the fact that I cannot find an easy way to access all (or at least the ones I need) objects in storyboard simply by addressing their identifiers.
another thing to check for is if the viewcontroller that's throwing the error has a storyboardIdentifier, you can check the storyboard xib file.
the identifier was missing in my case, the error stopped when i added it
Related
Why can't I create the view controller objects using sth like
resultVC = ResultViewController() instead of the following way.
let storyboard = UIStoryboard (name: "Main", bundle: nil)
let resultVC = storyboard.instantiateViewController(withIdentifier: "ResultViewController") as! ResultViewController
// Communicate the match
resultVC.match = self.match
self.navigationController?.pushViewController(resultVC, animated: true)
Everything is depends on your logic. There are three basic ways by which you can create UIViewController.
Storyboard : you have storyboard, design your VC and instantiate it via storyboard.
In this case, you have to tell system in which storyboard your VC have and what is its ID.
As you done in above code.
Referal : https://developer.apple.com/library/content/documentation/General/Conceptual/Devpedia-CocoaApp/Storyboard.html
Xib/Nib: Like storyboard, you can use xib/nib and design your VC. Here just you need to alloc the VC by the xib name.
Referal : https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/LoadingResources/CocoaNibs/CocoaNibs.html
Programatically: Here you donot need any type of xib/ storyboard. You have to do everything by code. your VC design will be in your respective VC file. Here you have to just alloc that file.
Referal : How to make a view controller without a xib or a storyboard vc identifier?
or
starting ios project without storyboard
Difference: Which is more efficient way? StoryBoard or XIB?
If you still unclear, then ask.
You use this method to create view controller objects that you want to
manipulate and present programmatically in your application. Before
you can use this method to retrieve a view controller, you must
explicitly tag it with an appropriate identifier string in Interface
Builder.
So, you doesn't just allocate your class, but associate your class with appropriative screen in your Interface Builder.
Anyway, you can create your controller like you want resultVC = ResultViewController() but you should create all UI in code instead of using Interface Builder
Using resultVC = ResultViewController() will create a view controller of type ResultViewController but without any links to your storyboard, which is unlikely to be of any use to you (why are you using a storyboard if not to gain these links?). You need to instantiate it from the storyboard to gain these links.
I've this situation when I've decided to reuse and extend one of my AViewController which also extends UITableViewController with its view in UIStoryboard A.
So I've created a new protocol. BViewController which extends AViewController in the other UIStroryboard. I've added other UITabelViewController with class BViewController, Cell creation and all the other tableViews is on AViewController side. In the extended view I want to provide a new source of data for TableView.
I thought, since I'm extending existing VC with view. I don't have to recreate IBOutlets like tableView, cell etc. in a new view.
But at this point it seams that I should recreate a view in the other Storyboard? I'm I missing something?
The content of the view hierarchy for a scene in your storyboard is not inherited by another scene regardless of if you use the same subclass of UIViewController or not. If you don't want to recreate the view hierarchy in your new scene, then you may need to put your view hierarchy in a .xib file and use UINib to do manual nib loading.
You may try this solution.
if let destinationVC = storyboard.instantiateViewController(withIdentifier: "AViewControllerIdentify") as? AViewController {
/// Runtime to set new sub viewcontroller class.
object_setClass(destinationVC, BViewController.self)
if let vc = destinationVC as? BViewController {
/// add you logic here.
}
}
While using Typhoon I came across this issue, but first some background.
I'm using a storyboard.
The storyboard starts in a home screen, then flows to login, then to the main screen (UITabBarController). I use navigation controller as root controller.
If the user is already logged in I want to show the main screen without showing the home or login screens. This could be done in the home screen viewDidLoad (other suggestions welcome).
The few things i've tried are:
Using a segue from the home to the tabController but the home screen can be seen and the transition is animated (don't want this).
Instantiating the tab controller (as below) from the storyboard but the dependencies are not injected. I understand that this is because the Typhoon storyboard is not used.
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *vc1 = [storyboard instantiateViewControllerWithIdentifier:#"MyAuth"];
I also tried using factory with Typhoon for the storyboard.
public dynamic func storyboard() -> AnyObject {
return TyphoonDefinition.withClass(TyphoonStoryboard.self){
(definition) in
definition.useInitializer("storyboardWithName:factory:bundle:"){
(initializer) in
initializer.injectParameterWith("Main")
initializer.injectParameterWith(self)
initializer.injectParameterWith( NSBundle.mainBundle() )
}
definition.scope = TyphoonScope.Singleton; //Let's make this a singleton
}
}
///Injection for tabbar controller
public dynamic func tabBarViewController() -> AnyObject {
return TyphoonDefinition.withClass(TabBarViewController.self){
(definition) in
}
}
On the viewDidLoad I push the tabBarViewController (using the injected assembly) to the navigation controller but it doesn't have the tabs as specified on the storyboard.
func viewDidLoad() {
super.viewDidLoad()
if(userLoggedIn){
self.navigationController?.pushViewController(self.injectedAssembly.storyboard().tabBarController(), animated: false)
}
}
Thanks,
ad 1. You can disable animations on segues you created in a storyboard by selecting the segue in the storyboard editor, switching to the attributes inspector of the segue and disabling the "Animates" checkbox.
ad 2. If the UIViewController which contains the code you posted was instantiated within a Typhoon injected UIViewController (for instance if you have this code within the home viewcontroller, you use plist integration, and the home viewcontroller is set as the initial viewcontroller in your storyboard), then you can access self.storyboard in the UIViewController. This storyboard will be a TyphoonStoryboard, therefore it will work.
ad 3. Just because you give Typhoon instructions on how to create your MainStoryboards and TabBarViewControllers, it doesn't mean Typhoon knows it should combine one with the other. Try using one of the withFactory: methods provided by TyphoonDefinition to instantiate your UIViewController using the correct storyboard (sorry for Obj-C instead of Swift)
- (MYViewController *)myViewController {
return [TyphoonDefinition
withFactory:[self storyboard]
selector:#selector(instantiateViewControllerWithIdentifier:)
parameters:^(TyphoonMethod *factoryMethod) {
[factoryMethod injectParameterWith:#"MYViewControllerIdentifier"];
}
configuration:^(TyphoonFactoryDefinition *definition) {
definition.classOrProtocolForAutoInjection = [MYViewController class];
}];
}
I have some variable lets say isMenuVisible = false; I want to set some function when this var is changed:
isMenuVisible: Bool!{
didSet{
callFunctionFromOtherViewController()
}
}
How is that possible? Do I need to create instance of VC in order to access that function? Or I need to make that function public?
To call a method/function from another VC (or from any other class), you have 2 choices:
Create a class method in this other view controller:
In your MyViewController.h
+(void)myClassMethod;
Wherever you need to use it
[MyViewController myClassMethod];
Create an instance method in this other view controller:
In your MyViewController.h
-(void)myClassMethod;
Wherever you need to use it
MyViewController *myViewControllerInstance = [[MyViewController alloc] init];
[myViewControllerInstance myClassMethod];
definitely no need to create instance for the viewController,
if it is a rootViewController you can get it like that :
var appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
var vc = (appDelegate.window?.rootViewController as? MyViewController)!
if your view controller is parent, it is like that:
var myViewfromParent = self.parentViewController as? MyViewController
if you use storyboard with id, you can get it like that:
let storyboard = UIStoryboard(name: "MyStoryboardName", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("someViewController") as! UIViewController
hope it helps
If you're using viewController containment and you can easily reference the view controller you can simply create a public method in your .h file and call that method... however, with viewControllers it's often the case that a. you don't have / want universal access to the viewController instance in other classes and b. the viewController may not even exist when this method is changed. For these reasons I'd go with NSNotifications when triggering actions in viewControllers.
NSNotificationCenter addObserver in Swift
If this isn't acceptable and you can guarantee that this class will be linked in an architecturally sound way, I would create a delegate. That way you can set the delegate when you create the viewController and there's no mess when trying to get back to your viewController... I'd only recommend this if there's a genuine connection between the viewController and the class that contains the action (in this case the bool)
In objective c you'd do
MyViewController *myViewController = [[MyViewController alloc] init];
myViewController.delegate = theClassContainingTheBool; // could be self.. might not be... If you can't do this line of code, then you should use notifications
In swift
Delegates in swift?
How to define optional methods in Swift protocol?
I have one UIViewController named "MainView" and that has one UITableView named "tblLists"
tblLists generating the customCell - "customCellList".
My question is can I get the MainView's instance(self) in customCellList class.
I tried with superview thing but not get MainView. I want to achieve this without protocol.
So need your help in this.
You can use the responder chain to gain access to the view controller. Assuming your customCell class is a UITableViewCell subclass, the following method should do the job:
#implementation customCell
- (UIViewController *)getViewController
{
id vc = [self nextResponder];
while(![vc isKindOfClass:[UIViewController class]] && vc!=nil)
{
vc = [vc nextResponder];
}
return vc;
}
#end
The above code is courtesy of the Sensible TableView framework.
What you are asking is not a good idea, you should find another way around. It breaks the MCV pattern.
By the way, if you are worried with memory concern using ARC and targeting iOS>=5 you can create a weak reference to the table view itself and get the view controller as its delegate or data source property (of course if the VC is one them). Or you can create a weak reference to the VC itself.
As pointed in the comments is not a good idea, better find another way around. If you need to update you cells value there are a lot of methods to reload tableview data! By means of using KVO, notification, delegation etc on your VC from the model, you can simply trigger a reload to the table view without involving weird references in cells.
Hope this helps.
For those rare times when you want to break MVC.. This assumes you are using a Navigation controller as the rootVC on your window. Updated for Swift 2
func visibleVC() -> UIViewController? {
if let navVC: UINavigationController = UIApplication.sharedApplication().keyWindow?.rootViewController as? UINavigationController {
if let vc: UIViewController = navVC.visibleViewController as? MyViewControllerClass {
return vc
}
}
return nil
}
Also you can access to rootViewController:
UIViewController *controller = [UIApplication sharedApplication].keyWindow.rootViewController;
I have similar question, my case is to change array in View Controller when the value of textfield in Table View custom cell changed.
My solution is add delegate for UITextFiled in cellForRowAt method of tableview, then I can do all my data changing in textFieldDidEndEditing method. Because they are all in one class, the ViewController Class.