Confused about self and delegation - iphone

This code is from Utility app, added by Apple when creating a project.
What's the difference between this:
- (void)flipsideViewControllerDidFinish:(FlipsideViewController *)controller
{
[self dismissModalViewControllerAnimated:YES];
}
And this:
- (void)flipsideViewControllerDidFinish:(FlipsideViewController *)controller
{
[controller dismissModalViewControllerAnimated:YES];
}
They both work, but the first one is the main code from Utility app, added by Apple.
So is there any difference, and why does self works? self is MainViewController, not FlipsideViewController. I have no idea, does it has something to do with delegation?
Thank you.

It all depends on which object is executing flipsideViewControllerDidFinish; if it is the same as the controller, then it is just the same.
In practice, what happens is that sometimes the class that is delegating (in your case FlipsideViewController), also implements the delegate protocol (ie., acts as a delegate). In such case, self and controller are the same. This would correspond to a delegate intialization (eg. in the init method) like:
self.delegate = self;
But you could have your delegate be a different class (eg., the application delegate, or whatever) and in this case they would be different. In this case you would say (eg in init):
self.delegate = [UIApplication sharedApplication].delegate;
In this case, you application delegate would receive the call to FlipsideViewController and the controller argument tells which object it refers to; in this case, self != controller.
Another case is when your delegate is acting as a delegate for more than one object; in this case, the controller argument tells which object is the delegating one.
In practice, within the delegate method implementation you can safely use the controller argument without much thinking.

I know that an answer has been already chosen as correct and Sergio have done a good job explaining delegation, but on this specific case the answer is far more simple. self and controller in this case are not the same thing (as you say also in the question). self is the presenting controller and controller is the presented one. So, the reason that both calls work is this:
The presenting view controller is responsible for dismissing the view
controller it presented. If you call this method on the presented view
controller itself, however, it automatically forwards the message to
the presenting view controller.
From Apple's documentation on UIViewController

Related

Call popViewController from Class Method

I'd like to go back to my root view from within a class method of view 1. When in an instance method of view 1, i would just say
[self.navigationController popViewControllerAnimated:YES];
but since self doesn't apply in a class method, I am wondering how to accomplish this. Pertaining to the illustration below, I am currently in a class method of View1Controller.m and I'd like to get back to Rootview. Thanks.
You can declare another method:
-(void)closeThisViewController
{
[self.navigationController popViewControllerAnimated:YES];
}
Then use NotificationCenter:
[[NSNotificationCenter defaultCenter] postNotificationName:#"notif_closeThisVC" selector:#selector(closeThisViewController) object:nil];
Although as jonkroll said, you're dealing with view controller stuff, we don't understand why you would put view controller related code inside a class method.
Edit
Sorry bad code above.
I meant to say you can use NSNotificationCenter to post a notification:
-(void)postNotificationName:(NSString *)notificationName object:(id)notificationSender
Then in the same view controller declare a NSNotificationCenter observer:
- (void)addObserver:(id)notificationObserver selector:(SEL)notificationSelector name:(NSString *)notificationName object:(id)notificationSender
My brain was quicker than my fingers, so I kinda combined the two into one when I tried to explain the solution :P
It should more like this:
// posting a notification with NSNotificationCenter
[[NSNotificationCenter defaultCenter] postNotificationName:#"notif_closeThisVC" object:nil];
In your viewDidLoad method somewhere (I recommend at the top), add this:
-(void)viewDidLoad
{
// adding an observer with NSNotificationCenter
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(closeThisViewController) name:#"notif_closeThisVC" object:nil];
...
}
Hope that helps.
If you don't want to refactor this from a class method to an instance method (and there are certainly cases where you wouldn't want to do that), I'd suggest you add a completion block parameter to your class method:
+ (void)doSomethingWithCompletion:(void(^)())completion {
/* Do your thing... */
completion();
}
- (IBAction)doSomething:(id)sender {
[self.class doSomethingWithCompletion:^{
[self.navigationController popViewControllerAnimated:YES];
}];
}
This would allow you to cleanly separate the instance-less operation the class method performs from instance-specific dismissing of the view controller. You could also make the completion block accept an error object if the operation can fail.
You could do something similar with a delegate object or even by passing in the view controller to dismiss, but this design seems to offer the cleanest separation with the most modern feel.
There are going to be legitimate arguments that encourage you to refactor so that you do have access to the current view controller and can access the navigation controller via currentVC.navigationController. Remember, it can still be a class method, just give it an extra argument when you call it (or start the call chain that calls it) from the VC.
However, I also had to solve this in one of my apps, so I just made sure that the navigation controller was globally accessible to everyone, always via pointer ("weak ref")
If you declare a global variable like this (say, in "Navigation.h")
extern UINavigationController *gNavController;
and define it in your AppDelegate.m (pays to review distinction between declaration/definition if you're rusty on that):
UINavigationController* gNavController;
and then assign it when you start up in application:didFinishLaunchingWithOptions::
(assuming the delegate has a property called viewController that is your navigation controller):
gNavController = viewController;
Then as long as you #import Navigation.h, you'll always have access to the navigation controller. This also makes getting a handle to its view for popups/popovers much simpler.
This also assumes your nav controller is never released for the lifetime of the app (probably true unless you're doing something unusual).

iOS: How to Recognize that We Got Back from a Child UIViewController within the Parent UIViewController?

Let's say that I have 2 UIViewControllers on a stack within a UINavigationController. In the "parent" we call "[self.navigationController pushViewController:childViewController animated:YES];" upon some user action and in the "child" we call "[self.navigationController popViewControllerAnimated:YES];" upon some user action.
How can we recognize within the parent that we just got back?
Is there some "event" driven method that can recognize that this popViewControllerAnimated action was called from the child?
It seems like you're using this child controller as a modal in that it can be 'dismissed'. If that is the case, try to follow Apple's patterns that they use for UIAlertViews.
If that is the case, I'd do either of the following to implement a delegate pattern(delegate vs block is a huge debate that I will not get into here) so the owner(the one that pushes the child on) knows when its dismissed:
Create a protocol (ChildControllerDelegate), have one method in it childControllerWasDismissed:(ChildController *)
add a block property(make sure its a copy property, not retain) to the ChildController
You'll then want to call the delegate method or block on viewDidDisappear. If you want finer grain control, have a delegate method or block that corresponds viewWillDisappear / viewDidDisappear.
I'd successfully resolved this by setting navigationController?.delegate = self and then implementing this method to determine whether the current view controller is shown again after a pop.
func navigationController(navigationController: UINavigationController, didShowViewController viewController: UIViewController, animated: Bool) {
if viewController == self {
// we got back
} else {
// some other controller was pushed
}
}
There's a few way to hint at that. What you can do, is call the popViewControllerAnimated from the parent. You can do that by passing a block to the child controller that would then execute the said block and thus popping would be done by the parent controller.
You can also use the UINavigationController delegate to be notified when a UIViewController will be dismissed:
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
This method will let you know which VC will be shown and you can check if the current (not yet popped) VC is the child you were looking for.
You can also do some trick with - (void)viewWillAppear: but this might require some hacks.
First read this, it will help you understand what is going on with view controllers.
Then implement viewWillAppear: and viewDidAppear: in your parent view controller to log a message.

viewWillAppear not called in UITableViewController?

I have a couple of UITableViewController classes and I just noticed that these methods aren't being called:
-(void)viewWillAppear:(BOOL)animated;
-(void)viewDidAppear:(BOOL)animated;
I read in http://discussions.apple.com/thread.jspa?threadID=1529769&tstart=0 that I would have to call those methods myself when pushing view controllers, but that's strange, since it works for anything but UITableViewController.
Also makes it a bit of an issue when I need to have a UITableViewCell deselected in the UIViewController that pushed the UITableViewController.
I can't find it in the documentation, but I think this might be because you are using a UINavigationController.
How about setting the UINavigationController's delegate property and then implementing UINavigationControllerDelegate? It provides two optional methods:
– navigationController:willShowViewController:animated:
– navigationController:didShowViewController:animated:
For example, navigationController:willShowViewController:animated: might look something like this:
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
if ([viewController isKindOfClass:[UITableViewController class]]) {
[viewController viewWillAppear:animated];
}
}
Anyway, this will get you the behavior you want without having to hack calls to viewWillAppear: all over your project.
Did anyone resolve this because the original post is correct - simply using UITableViewController and pushing the table view of that controller onto the navController does NOT trigger these methods despite the fact that it should. I have a series of UITableViewControllers and table views that are pushed and popped to display hierarchical data - nothing fancy, but the "viewWill/Did/Appear/Disappear" methods are never called. Only the viewDidLoad and viewDidUnload are called.
There must be a wiring problem in both our setups, but simply pushing a view into the navigationController should be all that is required (?) - hard to believe that this could have gone unnoticed as a fundamental bug this long.
?
These two methods are called by default to notify for the changes. UITableViewController is a subclass of UIViewController, so there will be the same behavior. You can see more in the View Controller Programming Guide
The viewWillAppear: and viewDidAppear: methods give subclasses a chance to perform any additional actions related to the appearance of the view.
How do you know that these methods are not called? Can you provide some more codes, or at least you test them with a NSLog() to see if there are some messages printed.
I'm seeing the same problem. I have a simple UIView from the IB and I do a addSubview with a class that extends UITableViewController.
I can see the view of the TableViewController without problems in my application, but the viewWillAppear function is never called in this situation.
Well, the discussion linked from the question has the answer right in it. UINavigationController needs to receive the "viewWillAppear" message in order for it to send those messages to the view controllers you push onto it.
So ironically if you don't do what Apple recommends, and you subclass UINavController for your view controller, then everything works great.
However, if you just create a UINavController inside of your view controller, then you need to implement "viewWillAppear", "viewDidAppear" and so on and forward those to your nav controller.
Note that this is especially important if you're using Three20, because its view controller hierarchy expects the "viewWillAppear" message to be received. If its not you can end up with TTTableViews that don't draw.
The same can occur if you use a UITabViewController. You need to force the viewWillAppear call by implementing either the UITabViewControllerDelegate or UINavigationControllerDelegate callbacks
This explanation may help: http://www.mlsite.net/blog/?p=210

How is a delegate object called?

my protocol:
#protocol ElectricalSystemEngineDelegate
-(void)didRequestMainMenu:(id)sender;
#end
I designed this protocol in order to handle dismissal of a modal View Controller inside my rootView controller. My rootView controller adopts this protocol and is declared as follows:
#import "ElectricalSystemEngineDelegate.h"
#interface RootViewController: UIViewController <ElectricalSystemEngineDelegate>
//other ivars & methods including instantiation of my modalViewController.
I use an out-of-the-box:
-(IBAction)displayElectricalViewController
-to display the modal controller... which works fine. I am, however, confused on how to proceed further with the implementation of this protocol to handle dismissal of the controller..
//The implementation of my root view controller.
-(void)didRequestMainMenu:(id)sender {
[self dismissModalViewControllerAnimated: YES];
}
Obviously, I correctly implemented the protocol by using the prescribed method. I want the method to dismiss my view controller when called. I also want it to be called by the tapping of a back button in the modalViewController.
As the apple docs say, "In some cases, an object might be willing to notify other objects of its actions so that they can take whatever collateral measures might be required." My goal is for my ElecticalViewController to notify its parent (the RootViewController) that it should be dismissed. That dismissal should be triggered by tapping the back button. How does the object accomplish this notification?
You need to add id <ElectricalSystemEngineDelegate> delegate property to your ElectricalViewController.
Then you need to assign self (RootViewController) to that delegate after you created ElectricalViewController.
Then you call [delegate didRequestMainMenu] when you dispose ElectricalViewController.
Then you need to create a method didRequestMainMenu to your RootViewController.

How can I outsource view-change-operations from my view controller upon accelerations?

I have an view controller class. I want to do some things in my view when the accelerometer detects movement and calls the accelerometer:didAccelerate: method in the delegate object.
That delegate object is the problem here in my brain. Currently, my brain is freezed and I don't get it what would be better. Let me know what you think!
Solution 1)
In my view controller class I conform to the UIAccelerometerDelegate protocol, and implement that accelerometer:didAccelerate: method.
In the -applicationDidFinishLaunching: method of my AppDelegate class I set that view controller object up as the delegate for receiving method calls upon accelerations. I think that's not really good.
Solution 2)
I create a blank new object called AccelerationDelegate, conform to that UIAccelerometerDelegate protocol, implement that accelerometer:didAccelerate: method and in the -applicationDidFinishLaunching: method of my AppDelegate class I set that view controller object up as the delegate for receiving method calls upon accelerations.
But for solution 2 my brain got stuck a little bit! How would I access the view objects from my view controller inside that object?
The problem here is, that I have more than one view controller around. I use a tab bar controller to switch between them.
Any suggestions how I could get that right?
I agree that the second method is better. Are you looking to access just the currently selected tab view, or just a specific view in your app.
In any case, what I would do is to set up properties for your UITabViewController in your UIApplicationDelegate so that you can access it from the delegate (you can get the app delegate by calling [[UIApplication sharedApplication] delegate]). For example:
YourApplicationDelegate *appDelegate = (YourApplicationDelegate *)[[UIApplication sharedApplication] delegate];
FirstUIViewController *firstViewController = appDelegate.firstViewController;
[firstViewController doStuff];
where firstViewController is a property on your delegate.
If your acceleration is specific to one view controller, then it makes sense to have the view controller receive the information necessary to alter its own subviews. However, it might be better to set your view controller to be the delegate when the view appears, and set the delegate to null when it disappears. (Specifically, - (void) viewWillAppear: and - (void) viewWillDisappear:)