I have a cancel button, when i press cancel button then i pop my current viewcontroller. Before popping my controller, i want to access one member (which is a class Student) of previous view controller. So i am doing this way:
StudentProfileViewController *controller = (StudentProfileViewController*)self.parentViewController;
NSLog(#"%#", controller.student);
My app crashes on line NSLog, error is this:
[UINavigationController student]: unrecognized selector sent to instance 0x6865180
Strange part is its says "[UINavigationController student]" but my controller is UIViewController.
Can anyone shed a light on this. I know some silly mistake is being done.
Thanks
The parentViewController would return the controller you are looking for only if you had presented modally from that view in the first place. It looks to be that you are trying to reference the previous controller in the stack, not the presenting view.
In your case, the parentViewController is the navigationController if that is how you presented. You are casting it to the controller class you wish it to be but that doesn't make it so.
More appropriate method would be to have passed the object you wish to reference in the init method or, most preferably, make a delegate method to tell the former view when this controller is complete, then let the former view react as intended.
Related
I have a view controller that I need to refresh it self so, I basically reload it with the following code.
-(void)check{
GameController*myNewVC = [[GameController alloc] init];
[self presentModalViewController:myNewVC animated:NO];
}
I can call the method above in gamecontroller and it works fine, but in a button sub class I use the method below and it doesn't work because nothing happens.
.h
#interface CellButton : UIButton {
}
.m
GameController*myNewVC = [[GameController alloc] init];
[myNewVC check];
What can I do to get this working?
I have a view controller that I need to refresh it self so, I basically reload it
Don't do that. Your view controller isn't refreshing itself, it's replacing itself, and it's hard to think of a reason that it should need to do that.
Put the code the loads the data in a separate method, and call that method on the existing view controller instead of creating a whole new view controller. For example, many view controllers that manage a UITableView will call the table's -reloadData method to tell the table to discard any cells that are currently visible and request new ones. No matter what kind of view(s) your view controller manages, you can do something similar.
I can call the method above in gamecontroller and it works fine, but
in a button sub class I use the method below and it doesn't work
because nothing happens.
That's most likely because you say you're using the code in a UIButton subclass, and the code says:
[self presentModalViewController:myNewVC animated:NO];
So, the button is telling itself to present the view controller. However, UIButton doesn't have a presentModalViewController:animated: method. I'm surprised that "nothing happens" -- I'd expect an exception due to the unimplemented method. It should work fine if you replace self above with a pointer to your view controller. Or, much better, put the code in an IBAction method in the view controller, set the buttons action to that method, and its target to the view controller.
(from your comment...)
There is a function in the button class that will dictate weather or
not the view controller will refresh it self.
That sounds like a poor plan -- in a well designed MVC application, logic that controls whether the view controller will refresh belongs in the view controller. Have the view controller enable/disable or show/hide the button based on whatever conditions control the refreshing behavior.
i initialize tables, data etc in my main ViewController. For more settings, i want to call another Viewcontroller with:
DateChangeController *theController = [self.storyboard instantiateViewControllerWithIdentifier:#"dateChangeController"];
[self presentViewController:theController animated:YES completion:^{NSLog(#"View controller presented.");}];
And some clicks later i return with a segue (custom:)
NSLog(#"Scroll to Ticket");
[self.sourceViewController presentModalViewController:self.destinationViewController animated:YES];
My Problem:
After returning to my main ViewController, viewDidLoad is called (everytime).I read, that the ARC releasing my mainView after "going" to the other ViewController and calling the viewDidUnload Method, but i want to keep all my data and tables i initialize at the beginning..
Any solution? Thank you very much!
The problem is that you are doing this:
main view controller ->
date change controller ->
a *different* main view controller
In other words, although in your verbal description you use the words "returning to my main ViewController", you are not in fact returning; you are moving forward to yet another instance of this main view controller every time, piling up all these view controllers on top of one another.
The way to return to an existing view controller is not to make a segue but to return! The return from presentViewController is dismissViewController. You do not use a segue for that; you just call dismissViewController. (Okay, in iOS 6 you can in fact use a segue, but it is a very special and rather complicated kind of segue called an Unwind or Exit segue.)
If you do that, you'll be back at your old view controller, which was sitting there all along waiting for your return, and viewDidLoad will not be called.
So, this was a good question for you to ask, because the double call of viewDidLoad was a sign to you that your architecture was all wrong.
I think you're taking the wrong approach - viewDidLoad is supposed to be called when it is called - it's a notification to you that the view is being refreshed or initially loaded. What you want to do is move that table initialization code somewhere else, or, at least, set a Boolean variable so that it is only called once. Would it work to create an object that has your table data when viewDidLoad is first called, then to check it to see if it's already been called?
I have seen a lot of posts on stack overflow stating that the viewDidLoad method of controllers is only called the first time the controller is accessed and not necessarily every time but always at least once.
This is not what I am seeing at all! I put together a simple test to highlight this:
https://github.com/imuz/ViewDidLoadTest
It seems for navigation controller segues and modal views viewDidLoad is always called. The only time it is not called is when switching between tabs.
Every explanation of viewDidLoad I can find contradicts this:
When is viewDidLoad called?
UIViewController viewDidLoad vs. viewWillAppear: What is the proper division of labor?
http://www.manning-sandbox.com/thread.jspa?threadID=41506
And apples own documentation indicate that a view is only unloaded when memory is low.
I am currently doing initialisation in viewDidLoad making the assumption that it is called with every segue transition.
Am I missing something here?
Phillip Mills' answer is correct. This is just an enhancement of it.
The system is working as documented.
You are seeing viewDidLoad because the view controller being pushed onto the navigation controller is a new instance. It must call viewDidLoad.
If you investigate a little further, you would see that each of those view controllers are deallocated when they are popped (just put a breakpoint or NSLog in dealloc). This deallocation has nothing to do with the view controller container... it does not control the life of the controller it uses... it is just holding a strong reference to it.
When the controller is popped off the navigation controller stack, the nav controller releases its reference, and since there are no other references to it, the view controller will dealloc.
The navigation controller only holds strong references to view controllers that are in its active stack.
If you want to reuse the same controller, you are responsible for reusing it. When you use storyboard segues, you relinquish that control (to a large extent).
Let's say you have a push segue to view controller Foo as the result of tapping some button. When that button is tapped, "the system" will create an instance of Foo (the destination view controller), and then perform the segue. The controller container now holds the only strong reference to that view controller. Once it's done with it, the VC will dealloc.
Since it creates a new controller each time, viewDidLoad will be called each time that controller is presented.
Now, if you want to change this behavior, and cache the view controller for later reuse, you have to do that specifically. If you don't use storyboard segues, it's easy since you are actually pushing/popping the VC to the nav controller.
If, however, you use storyboard segues, it's a bit more trouble.
There are a number of ways to do it, but all require some form of hacking. The storyboard itself is in charge of instantiating new view controllers. One way is to override instantiateViewControllerWithIdentifier. That is the method that gets called when a segue needs to create a view controller. It's called even for controllers that you don't give an identifier to (the system provides a made-up unique identifier if you don't assign one).
Note, I hope this is mostly for educational purposes. I'm certainly not suggesting this as the best way to resolve your problems, whatever they may be.
Something like...
#interface MyStoryboard : UIStoryboard
#property BOOL shouldUseCache;
- (void)evict:(NSString*)identifier;
- (void)purge;
#end
#implementation MyStoryboard
- (NSMutableDictionary*)cache {
static char const kCacheKey[1];
NSMutableDictionary *cache = objc_getAssociatedObject(self, kCacheKey);
if (nil == cache) {
cache = [NSMutableDictionary dictionary];
objc_setAssociatedObject(self, kCacheKey, cache, OBJC_ASSOCIATION_RETAIN);
}
return cache;
}
- (void)evict:(NSString *)identifier {
[[self cache] removeObjectForKey:identifier];
}
- (void)purge {
[[self cache] removeAllObjects];
}
- (id)instantiateViewControllerWithIdentifier:(NSString *)identifier {
if (!self.shouldUseCache) {
return [super instantiateViewControllerWithIdentifier:identifier];
}
NSMutableDictionary *cache = [self cache];
id result = [cache objectForKey:identifier];
if (result) return result;
result = [super instantiateViewControllerWithIdentifier:identifier];
[cache setObject:result forKey:identifier];
return result;
}
#end
Now, you have to use this storyboard. Unfortunately, while UIApplication holds onto the main storyboard, it does not expose an API to get it. However, each view controller has a method, storyboard to get the storyboard it was created from.
If you are loading your own storyboards, then just instantiate MyStoryboard. If you are using the default storyboard, then you need to force the system to use your special one. Again, there are lots of ways to do this. One simple way is to override the storyboard accessor method in the view controller.
You can make MyStoryboard be a proxy class that forwards everything to UIStoryboard, or you can isa-swizzle the main storyboard, or you can just have your local controller return one from its storyboard method.
Now, remember, there is a problem here. What if you push the same view controller on the stack more than once? With a cache, the exact same view controller object will be used multiple times. Is that really what you want?
If not, then you now need to manage interaction with the controller containers themselves so they can check to see if this controller is already known by them, in which case a new instance is necessary.
So, there is a way to get cached controllers while using default storyboard segues (actually there are quite a few ways)... but that is not necessarily a good thing, and certainly not what you get by default.
I believe the Apple documentation is describing a situation where the view controller is not being deallocated. If you use a segue, then you are causing the instantiation of a new destination controller and, being a new object, it needs to load a view.
In xib-based apps, I have sometimes cached a controller object that I knew I might re-use frequently. In those cases, they behaved in keeping with the documentation in terms of when a view had to be loaded.
Edit:
On reading the links you included, I don't see any contradiction in them. They, too, are talking about things that happen during the lifespan of a view controller object.
It is called every time the controller's view is loaded from scratch (i.e. requested but not yet available). If you deallocate the controller and the view goes along with it, then it will be called again the next time you instantiate the controller (for example when you create the controller to push it modally or via segue). View controllers in tabs are not deallocated because the tab controller keeps them around.
I add a viewcontroller using
[self presentModalViewController:vectorView animated:YES];
But then from within would like to call a method inside the parent viewcontroller.
I read this question and found that something like this could be done to set a variable in the parent:
self.parentViewController.myString = aString;
But is there a way to call a method and send a few variables through that way?
For example
self.parentViewController.myMethod; //Which does not work
It's no different from any other message you would send:
[self.parentViewController myMethod:param1 withParam:param2];
Note that the behavior of the parentViewController property has changed in iOS 5:
Prior to iOS 5.0, if a view did not have a parent view controller and was being presented modally, the view controller that was presenting it would be returned. This is no longer the case. You can get the presenting view controller using the presentingViewController property.
To add to Ole Begemann answer, you can cast your view controller to silence warnings
if ([self.parentViewController isKindOfClass:[YourParentViewController class]])
[(YourParentViewController*)self.parentViewController yourMethod];
I shift to my view by
[[self navigationController] popToViewController:controller animated:YES];
In that ViewController, I'm not able to get a notice, that it comes back to front (e.g. by viewWillAppear). I want to reload a table, as soon as the view is visible again.
How do I get a notice, that the view comes back on the screen?
----> solved: See my last comment on Corey's answer
viewWillAppear should be called if you are using a UINavigationController.
Are you sure you have added it correctly to the view hierarchy?
Did you check if viewWillDisappear gets called when it goes offscreen?
Did you try viewDidAppear just to make sure?
Did you spell the method name correctly?
To add:
Is the instance of UINavigationController added directly to the UIWindow instance?
The delegate methods like viewWillappear are sent from UIApplication (I believe). UIApplication only "knows" about viewControllers whose views are either:
Added Directly to UIWindow.
Added to a
UINavigationController/UITabBarCOntroller
that is added directly to UIWindow
(or a chain of these that leads to UIWindow).