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.
Related
The normal way to open another screen from within a FirstVC screen, so one can close it again is like this:
SecondVC *secondVC = [[SecondVC alloc] initWithNibName:#"SecondVC" bundle:nil];
secondVC.delegate = self; //needed to dismiss
[self presentModalViewController: secondVC animated: YES];
while the SecondVC.m has to import a protocol that declares the method called to close the SecondVC
So I always have to create a protocol file SecondVCProtocol.h which basically looks like this:
#protocol SecondVCProtocol <NSObject>
-(void)secondVCDidFinish;
#end
Then in SecondVC.m I need to import this SecondVCProtocol.h file and now can finally call
[self.delegate secondVCDidFinish]
I have just completed another Android app and beeing back in the iOS world, I find this rather cumbersome. - needing to define such a protocol in a separate file & needing to use a delegate - all just to do the most normal task like closing a screen...
Isn't there an easier less complex way or is this just the way it has to be done?
for example like [self dismiss] in SecondVC - no delegate, no protocol - wouldn't his be really nice?
Many thanks!
You can just call
dismissViewControllerAnimated:completion:
on the presented viewcontroller, although it is not exactly best practice.
From Apple's documentation:
The presenting view controller is responsible for dismissing the view
controller it presented. If you call this method on the presented view
controller itself, it automatically forwards the message to the
presenting view controller.
Also from Apple's documentation though (http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/ModalViewControllers/ModalViewControllers.html)
When it comes time to dismiss a presented view controller, the
preferred approach is to let the presenting view controller dismiss
it. In other words, whenever possible, the same view controller that
presented the view controller should also take responsibility for
dismissing it. Although there are several techniques for notifying the
presenting view controller that its presented view controller should
be dismissed, the preferred technique is delegation.
What you describe is not the easiest pattern. Actually you should do something very similar to what you suggested would be nice. When SecondVC is ready to be dismissed it just calls, for example:
[self dismissViewControllerAnimated:YES completion:NULL];
From the UIViewController documentation:
The presenting view controller is responsible for dismissing the view controller it presented. If you call this method on the presented view controller itself, it automatically forwards the message to the presenting view controller.
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.
I have one single MainViewController which has of course it's one main UIView. I have this main view comprised of many different subviews.
Some of these subviews have their very own ViewController.
Lets say the MAIN view (whose delegate is primarily MainViewController) has a container which loads another UIView that uses a separate UIViewController- SecondaryViewController as the delegate for most it's actions.
This container view is of course loaded in MainViewController via
MyContainerViewController *myContainerController =
[[MyContainerViewController alloc] ...];
[self addSubView: myContainerController.view];
the controller for myContainerController.view though is MyContainerViewController. How inside this controller do I access MainViewController properties? Specifically I need to access MainViewController's - self.navigationController property to push a new ViewController? :)
Does this make any sense? I assume there's going to be casting of some sort involved since it seems I need to somehow retain a reference to MainViewController inside SecondaryViewController?
It doesn't make sense to push a new ViewController from the SecondaryViewController in the MainViewController.
This screws up the design of the code. A child object will access its parents method to call a method. By other words: bad code.
Make a delegate call from the SecondaryViewController to the MainViewController that it state has changed. Then the MainViewController can decide to do with the call and the SecondaryViewController will not know anything about the implementation of the MainViewController.
So:
Make a protocol in SecondaryViewController.
Let the MainViewController be SecondaryViewController's delegate.
Implement the delegate method in MainViewController which pushes the new ViewController.
Expose the desired sub-view controllers as properties of the view controller that contains them.
Expose your root view controller(s) as properties of your app delegate, and synthesize them also.
When you want to access a different UIViewController, first obtain a reference to your appDelegate:
MyAppDelegate* myAppDelegate = [[UIApplication sharedApplication] delegate];
Then just use your chain of properties to get access to your desired view controller.
SubSubViewController* ssvc = myAppDelegate.rootViewController.subViewController.subSubViewController;
Some folks frown upon the use of the sharedApplication class method to obtain the reference to a delegate, but I've used it in multiple apps and not suffered for it (yet). The alternative is to pipe your appDelegate through your hierarchy of viewControllers.
I'm working on a pretty simple multiview app for the iOS and I've been following a great tutorial in an Apress book. I've basically got my rootViewController instantiated and displayed with the app delegate, and I've got a number of content viewControllers (6) which I'd like to swap in and out based on user input. However, in the book they perform their switches with a button on a toolbar placed in the rootView using Interface Builder. It fires a method in rootView that loads up the new content ViewController and displays it.
My problem is that I'd like to perform the content view switch (that lies in my rootViewController instance), but I'd like to trigger the switch action with a button that's in my content view (and is therefore unavailable as my File Owner is my contentViewController, whose reference is held inside my rootViewController).
Hopefully I've explained it well enough, please let me know if I should elaborate more. I appreciate any help!
You need to pass down a reference to your root view controller (RootViewController *rootViewController) when you create your content view either in a custom init method or by just assigning it after you created it: self.contentView.rootViewController = self;.
Now inside your content view you can then call the appropriate method in the root view controller to do the switch: [self.rootViewController switchView]. This call then can be triggered inside the method that is called when you press the button (IBAction method).
So this is what you need to do:
1) Create a property inside the your content view controller of type RootViewController
#class RootViewController;
#interface MyContentViewController : NSObject {
#private
RootViewController *rootViewController;
}
#property (retain) RootViewController *rootViewController;
and make sure it retains the reference.
2) Synthesis the property and add the callback to the root view controller that switches the view:
#implementation MyContentViewController
#synthesize rootViewController;
- (IBAction) switchView:(id) sender {
[rootViewController switchToNextView];
}
-(void) dealloc {
[rootViewController release];
[super dealloc];
}
Also release your retain reference at the end.
3) Assign the root view controller to the content view inside your RootViewController:
self.contentViewController = [[[MyContentViewController alloc]
initWithNibName:#"ContentView"
bundle:nil] autorelease];
self.contentViewController.rootViewController = self;
That should be all. I hope that helps you.
Well, you could simply create an IBAction in each of your child controllers that calls:
[self.parentViewController switchToDifferentController:(int) viewNumber]
and then implement the switchToDifferentController method in your root. Other than ignore the compiler warning that parentView might not implement that method, it might work.
However, that is a bit brittle, as you'd have to assume that it was the parent calling you and that nobody will forget to implement that method.
In general, you use the "delegate" concept for a child controller to ask its parent to do something. The general idea is that you declare a group of methods as a "protocol". Think of it as a contract between objects. One object can say "I promise to implement these methods," and another can then choose to send those messages to it. The contract allows the compiler/system to check for conformance. You'll see this in UITableView, where the OS provides a standard table, but it calls back to your code to provide the individual cells as needed.
To implement a protocol, you mustdo the following: (See code segments below
Declares a protocol for the conversation
Specify that the parent will follows that protocol
Create a delegate property in your child
When the parent is about to launch the child, it assigns itself as the delegate for that child.
When the child wants to switch, it calls the parent using that protocol
#protocol myVCDelegate
- (void)switchToDifferentController:(int) viewNumber ;
#end
#interface ParentViewController : UIViewController <VCDelegate>
#property(nonatomic, assign) id <VCDelegate> delegate
childController.delegate = self;
[self.delegate switchToDifferentController:kController5];
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:)