I've been looking at view controllers for a few days now (searching xcode help, google and stack overflow), and I think I have some understanding of it, but not a lot.
I have some code, that I have trouble understanding.
So here I have the following code I found in the book I'm reading, and I'm not sure If I understand it correctly.
-(void)prepareForSegue(UIStoryboardsegue *)segue sender:(id)sender{
((ViewController2 *)segue.destinationViewController).delegate = self;
}
First, I have no idea why we typecast to our second view controller(viewController2) here.
I think I get the rest though, we take the method's segue parameter (which holds information about the view controllers involved in the segue), and we access the destinationViewController(meaning that we want to access the view controller that we are going to). We then set the delegate property of the destination view controller to self. I believe we set the delegate property to self, because we want to send messages to a delegate in the view controller we're going to.
Heres the last one I don't get:
In the header file:
#property (weak, nonatomic)id delegate;
In the implementation file: (the controllerVisisble property is a boolean, and is changed to YES when the user hits a button to perform a manual segue to the second view controller, which is this one)
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
((ViewController *)self.delegate).controllerrVisisble=NO;
}
Heres what I think this does: the viewWillDisappear method is called when a view is closing/removed.
I'm not quite sure about [super viewWillDisappear:animated], but I'm guessing that it gives an animation when the view disappears?However, I remove that line, and my dismissViewControllerAnimated method still gives an animation when the view controller is dismissed.
Here's the part that really confuses me. We need to access the first view controllers dateChooserVisible property somehow, to set it to NO, so we can access the second view controller again through our button. But, I don't understand why we have to typecast (viewController *), and type in self.delegate. Nor, do I understand why we created a property called delegate in the header file, to use here.
A lot of these questions are more generic than just within the context of view controllers.
[super methodName] calls the superclasses implementation of the method named methodName. In your case, Apple has written some code (that we don't have access to) in UIViewController's viewWillDisappear: method. ALWAYS call super when overriding a method from a superclass.
Note that viewWillDisappear: is just callback triggered whenever the view is set to disappear. My guess is that the super implementation of this method forwards this callback down to child view controllers, especially in the case of standard container classes like UINavigationController and UITabBarController.
The type casting really doesn't seem necessary, you can always call methods without compiler errors/warnings if the receiver is either type id or provides a declaration for the called method in its #interface.
As far as the delegates go, protocols and delegation are a major part of Objective-C, and widely used in Apple's APIs. This link might help you understand how they work; it helped me immensely.
Related
In UINavigationViewController, if I wanna pass values from one controller to next, just call - (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender , but what should I do if I wanna pass values from one viewController to previous viewController
I remember running across this same issue a few projects back. I can't find the my code to answer this question, but I did find a few tutorials.
http://prateekvjoshi.com/2014/02/16/ios-app-passing-data-between-view-controllers/
http://www.infragistics.com/community/blogs/torrey-betts/archive/2014/05/29/passing-data-between-view-controllers-ios-obj-c.aspx
and hence the concept of delegate came forth from segues.
Basically Segues are transition from one view to another but the child view is over the parent view, (inside a stack) so the parent view is still loaded.
So if u put segues everywhere and pass values between them then objects will keep on be creating and stored inside a stack and thus the cycle carries on.
So delegates was introduced.
Delegate is a method by which a child view controller(the later one) sends information using the inbuild delegate methods or self created protocol methods to the Previous view controller(the first one).
Here the one sending the information(later view) declares a delegate object, and a delegate method.
Which is then implemented by the recieving class(first view). So even after the later view is popped from the stack, the information is sent back to the root view by the delegate method.
Go through the documentation, its given in a more appropriate way
Hope this helps
Set previous view controller as delegate of current view controller and pass any values you want. This is standard approach.
I have a view controller that displays a button. When I click the button, the corresponding selector needs to be called. However, with ARC, the application crashed with an EXC_BAD_ACCESS message.
-(IBAction)reseauPushed:(id)sender{
self.reseauVC = [[ReseauVCIpad alloc]initWithNibName:#"ReseauVCIpad" bundle:nil];
[self.viewCenter addSubview:self.reseauVC.view];
}
with
#property (strong, nonatomic) ReseauVCIpad *reseauVC;
and the crash log :
-[ReseauVCIpad performSelector:withObject:withObject:]: message sent to deallocated instance
I use ARC.
The button action :
-(IBAction)helloPushed:(id)sender{
NSLog("hello);
}
This ReseauVCIpad view controller is obviously getting deallocated some how. Either you accidentally are setting self.reseauVC to nil somewhere, or, more likely, the parent view controller, itself, is somehow getting deallocated. Is there any chance you did this addSubview technique for any of the preceding view controllers? And if not, how did you instantiate the root view controller?
To diagnose where the problem is, I'd suggest you add dealloc methods to all of your various view controllers so that you can confirm if any are getting deallocated prematurely. (Either set breakpoints or put in NSLog statements.) I'd wager you're seeing the parent of ReseauVCIpad getting deallocated, which is, in turn, allowing ReseauVCIpad itself to be deallocated.
By the way, as others have pointed out, the typical answer to this problem is to make sure you're doing the appropriate containment calls (show below), or if this child view controller takes up the whole screen, you should just be pushing to it or modally presenting it. Clearly, you have a strong reference to ReseauVCIpad, so the lack of containment calls isn't the source of the problem with ReseauVCIpad itself (though I wonder if you are doing this addSubview trick without containment calls with one or more of ReseauVCIpad's parent view controllers).
But you still should be doing these containment calls (or do a proper modal/push transition), regardless, to ensure your view controller hierarchy stays in sync with your view hierarchy (see WWDC 2011 video Implementing UIViewController Containment for lengthy discussion of why this is important). The appropriate containment calls for adding a subview with its own controller is, at a minimum, as follows:
- (IBAction)reseauPushed:(id)sender
{
self.reseauVC = [[ReseauVCIpad alloc]initWithNibName:#"ReseauVCIpad" bundle:nil];
[self addChildViewController:self.reseauVC];
[self.viewCenter addSubview:self.reseauVC.view];
[self.reseauVC didMoveToParentViewController:self];
}
For a more detailed description, see the video I referenced above, or see the Creating Custom Container View Controllers section of the View Controller Programming Guide for iOS.
And when you want to remove it, you should do the appropriate containment calls there, too:
- (void)removeReseau
{
[self.reseauVC willMoveToParentViewController:nil];
[self.reseauVC.view removeFromSuperview];
[self.reseauVC removeFromParentViewController];
self.reseau = nil;
}
This generally solves this issue (where the child view controller was deallocated). It won't solve your reseauVC problem (because you already have strong reference), but (a) you should do this wherever you do addSubview with a view controller, regardless; and (b) I show you the pattern in case you're doing addSubview elsewhere without maintaining a strong reference.
Several things. You should not add one view controller's content view to another view controller unless you set up a parent/child view controller relationship. This was added in iOS 5, and expanded in iOS 6 (and probably further expanded in iOS 7...) Look at methods like addChildViewController:, removeFromParentViewController, isMovingToParentViewController, and didMoveToParentViewController:
The easiest way to set up a parent/child view controller relationship is to use storyboards (which requires iOS 5) and an embed segue. (Which I believe was added in iOS 6). That takes care of all the housekeeping for setting up parent/child view controller relationships for you.
Your second view controller is being saved to a strong property, so I'm not clear on why it's being deallocated. I'm also not clear where the call to performSelector:withObject:withObject: is coming from. What source line is crashing, and are you using performSelector:withObject:withObject: in your code anywhere?
I've got a question regarding the two mentioned methods, since in my tests I don´t make clear the order they are called. I thought that, firstly, viewDidLoad is called when the viewController is loaded for first time (as the name indicates), and inmediately after the init method. Then, I thought that once viewDidLoad returns, viewWillAppear is called. If you display another viewController, and then you return to this one, then it should be already loaded and only viewWillAppear will be called.
However, while developing I make the impression that there is no order when calling viewDidLoad and viewWillAppear... I couldn´t find a clear description of this lifecycle in Apple's documentation, how does this actually work?
Thanks!
I would like to add to Caleb's answer: Don't confuse the view controller and the view! The name viewDidLoad clearly indicates that the method is invoked after the view has been loaded. It is the view controller that does the loading.
Some pointers regarding the lifecycle of views and the order in which messages are sent:
Not an official Apple document, but I find this diagram really useful because it includes pretty much all of UIViewController's lifecycle overrides.
In the section Resource Management in View Controllers from Apple's "View Controller Programming Guide" there is a flowchart that depicts how views are initially loaded. It explains loadView and viewDidLoad, also in conjunction with storyboards.
The section Responding to Display-Related Notifications from Apple's "View Controller Programming Guide" explains how to respond to views appearing and disappearing (viewWillAppear: et al)
If you are planning on implementing a container view controller: The UIViewController class reference has a good overview of how messages need to be sent by your subclass.
I'm stopping here. You can find more stuff yourself by googling for "uiviewcontroller life cycle".
-viewDidLoad is called when the controller loads its view, which is not necessarily right after initialization. View controllers don't load their views until they need them, either to display or for any other reason.
-viewWillAppear is called just before the view is displayed. This will be after -viewDidLoad, but you don't know exactly how long after. -viewWillAppear is called every time the view is displayed; -viewDidLoad will only be called a second time if the view is unloaded at some point (such as didReceiveMemoryWarning). These days that's unusual, but it can happen.
Or if the viewController is set to nil, which can usually happen if a view controller is kicked off the navigation stack, and therefore next time it is brought to the navigation stack it needs to call -viewDidLoad again.
I thought that, firstly, viewDidLoad is called when the viewController
is loaded for first time (as the name indicates), and inmediately after the init method
No. The name indicates that the controller's view has been loaded (not the controller itself). Actually the docs state that this method will get called after the view hierarchy has been loaded into memory (either via loadView or through a nib for example).
Then, I thought that once viewDidLoad returns, viewWillAppear is
called
Again, no. loadView (and as a consequence viewDidLoad) method will get called the first time that view property is to be accessed and is nil (which is the case when you're initializing a controller). Think of this simple scenario:
MyViewController *vc = [[MyViewController alloc] init];
UIView *view = vc.view; // <= loadView & viewDidLoad will fire but it certainly didn't appear...
However, while developing I make the impression that there is no order
when calling viewDidLoad and viewWillAppear...
Well there is an order. We know for sure that viewWillAppear will always be called after viewDidLoad (if both of them are to be called of course).
As you said, ViewDidLoad is only calling once after loading the view. So we can initialize the instances in the viewDidLoad. It is mainly meant for the initialization.
viewWillAppear will invoke whenever we reach to this view. So if there is any changes in UI, we can done it in viewWillAppear.
I ran a trace on when all these calls are made: http://thecodist.com/article/ios_arc_storyboards_and_uiviewcontroller_trace
I'm currently refactoring my app to be sure the it's MVC compliant.
I would like to split the controller (MyController which extends UIController) and the view (HomeView which extends UIView) I set the view in myController using
self.view = [[HomeView alloc] init];
When I push an UIButton, a method is called in the view, and in this method I would like to call a method from the controller.
In my view
[zenModeBtn addTarget:self action:#selector(touchZenMode:) forControlEvents:UIControlEventTouchDown];
...
- (void) touchZenMode:(id) sender {
[myController playZenMode];
}
But having a reference to the controller in the view is really a bad practice isn't it ?
EDIT :
So in my UIViewController I've made this :
- (id) init {
HomeView* myHomeView = [[HomeView alloc] init];
[myHomeView.arcadeModeBtn addTarget:self action:#selector(touchArcadeMode) forControlEvents:UIControlEventTouchUpInside];
self.view = myHomeView;
return self;
}
is that correct ?
The view talking to your controller is no problem, as outlined by some answers here. E.g. a text field can notify its controller via the defined delegate methods.
However, your design is still seriously flawed. Your view has absolutely no business handling a button press itself. Your intuition that the view should not know about its controller is correct.
Your controller should know about the button and how to react to it being tapped. That's why a controller has button IBOutlets to tell the button to e.g. change its title or enabled state. And it has button handlers to react to UI events. It is the controller's job to handle this logic. It is the view's job to display the title, gray out or send a tap event back to the controller.
The only code you should put into a view is basically how to draw itself. Everything that cannot be handled by a controller.
The basic idea of the MVC pattern, as used in Cocoa Touch:
As described here: The Model-View-Controller Design Pattern
What you want to achieve, is a form of loose-, even blind maybe, coupling. By using protocols (for delegation mechanism), a View only knows that there is an object that adopts a specific protocol, it can 'talk' to.
Take the UITableView for instance. It does not need to know that there is a certain type of UIViewController that helps it gather data, but only that there is an object that adopts the UITableViewDatasourceDelegate and/or UITableViewDelegate; that object can be of any type.
In your edit, you use the target-action mechanism, which is another way of achieving loose-coupling. You set up the connection at runtime; your View does not know your Controller. Therefor: correct, apart from the comment #Mundi made about your init implementation being incomplete.
The view needs some way to communicate things back to the controller, ask it questions about what to do next, etc. So it's perfectly fine for the view to know something about the controller.
Some of the built-in views, like UITextField, define protocols they use to tell their delegate about what's going on, or ask it to do something. You typically implement the protocol in your controller. That way the view doesn't really know much about the controller, just enough to communicate. That makes your view more generic and reusable.
What you want to avoid is for your view to have direct links to your model. The role of the controller is to mediate between the view and the model. You should be able to completely change how the view is implemented without touching the model, and vice-versa.
You can put that method in a protocol in your view's interface:
#protocol MyViewDelegateProtocol <NSObject>
-(void)myMethod;
#end
you put a new property of NSObject type called delegate in your view.
you make your view controller comply to that protocol and when it inits the view assign the delegate property to self.
you implement myMethod in your view controller implementation.
and now you just call [delegate myMethod] from your view whenever you need it.
I am probably overcomplicating this but is there a way to access a property of a modal's parent?
So, I call "presentModalViewController" and then I can access some properties on the view controller that just called it from the modal.
Thanks,
Ashley
if you are using iOS 5, you can call self.presentingViewController to access the parent view controller
here is apple reference
Your answer was nearly correct - you want presentingViewController rather than parentViewController, however this could lead to tight coupling (dependency between the two classes, meaning they can only work with each other) if you're not careful.
It's best to define a protocol still, but your delegate property is not necessary - it will have the same value as the presentingViewController property.
Ok, so since asking this question I have tried a few techniques which looked as though they would work but didn't.
Such an example is this.
((pController *)self.parentViewController).testString;
However, while it was a regular UIViewController that presented the modal, the parent was actually a UITabBarController, and even using selectedViewController didn't work.
My solution was to add to my modal's .h file
id delegate;
and
#property (nonatomic, assign) id delegate;
After synthesizing it in the implementation file, alloc/init the modalViewController, but just before presenting it I set
modalViewController.delegate = self;
This way I could call self.delegate from within my modal. This still wasn't enough as this doesn't say which view controller it is, so I can't say
self.delegate.testString;
But the casting I learnt earlier allowed me to get a fully working solution of
((pController *)self.delegate).testString;
I hope I haven't just babbled my way through this, and I hope that this can help someone in the future.