Im writing a custom view controller container as per the iOS documentation on view controllers and i'm struggling on finding an elegant way to present the first view controller that also forwards the relevant display messages like viewWillAppear: automatically.
When i try transitionFromViewController:toViewController:duration:options:animations:completion: with the fromViewController: as nil i get an error. I have resorted to animating the view into the view hierarchy with a UIView animation block. This seems to break the automatic forwarding of the appearance methods and means its my responsibility to call viewWillAppear: and ViewDidAppear: at the appropriate times. Is there a more efficient way to transition the first view on to the screen that takes care of the appearance and rotation methods?
My code looks a little like this for animating in the first view controller.
self.visibleViewController = [[UIViewController alloc] init];
[self addChildViewController:self.visibleViewController];
[self.visibleViewController viewWillAppear:YES];
[self.visibleViewController.view setAlpha:0];
[self.view addSubview:self.visibleViewController.view];
[UIView animateWithDuration:0.5
delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
self.visibleViewController.view.alpha = 1;
}
completion:^(BOOL finished){
[self.visibleViewController viewDidAppear];
[self.visibleViewController didMoveToParentViewController:self];
}];
The answer was right there hidden in the documentation all along.
The documentation for UIViewController is
- (void)beginAppearanceTransition:(BOOL)isAppearing animated:(BOOL)animated
and the companion
- (void)endAppearanceTransition
Their documentation says never to call viewWillAppear and such from your own code. You can kick the chain off properly with these methods.
Are you sure the system doesn't send your child view controller the viewWillAppear/viewDidAppear/DidMoveToParentViewController messages? This bit of the Apple docs implies that it does:
In order for iOS to route events properly to child view controllers
and the views those controllers manage, your container view controller
must associate a child view controller with itself before adding the
child’s root view to the view hierarchy.
The way I read that, if you add a view controller as a child, then add the child's root view to your view, the system should send the appropriate messages as you add the view controller.
Try taking out your manual calls to those methods and set breakpoints in your child view controller to see if they still get called.
Related
I have added following piece of code in a view.
- (IBAction)accept_clicked:(id)sender {
[self.view removeFromSuperview];
self.view = nil;
}
Once accept is clicked, I have removed the own view like this. It worked fine, anyhow is it fine to do like this or It should be removed from another view(parent)?
Don't do this (with self.view) - it doesn't looks good and you might face difficult to find problems. self.view is the main view associated with an UIViewController. So this view to be visible on the screen, you must have shown it somehow: either pushing it to a UINavigationController or presenting it modally with -presentViewController:animated:completion: (IOS5+) or - presentModalViewController:animated:. Showing a view by instantiating a view controller and adding its view to the current view controller's view is not a good practice also:
//Not good
MyViewController *mvc = [[MyViewController alloc] init];
[self.view addSubView:mvc.view];
In your particular case I suppose you are showing some terms and conditions (or something similar) and have an accept and deny button, your best solution would be to present your view controller from somewhere, implement a delegate method, so the presenting view controller can have the result and then in your -accept_clicked: method to use either [self dismissModalViewControllerAnimated:YES] or [self dismissViewControllerAnimated:completion:] (IOS5+),
I'm trying to create a container view controller using iOS5 and new methods like addChildViewController.
Do I have to call addSubview after calling addChildViewController?
Do I have to call removeFromSuperview before calling removeChildViewController?
I don't see anything about this in Apple docs.
What do you think?
1) Do I have to call addSubview after calling addChildViewController?
Yes
2) Do I have to call removeFromSuperview before calling removeChildViewController?
Not quite
You should call removeFromParentViewController: instead of removeChildViewController:
You should also call willMoveToParentViewController:
For adding / removing, you can refer to this great category :
UIViewController + Container
- (void)containerAddChildViewController:(UIViewController *)childViewController {
[self addChildViewController:childViewController];
[self.view addSubview:childViewController.view];
[childViewController didMoveToParentViewController:self];
}
- (void)containerRemoveChildViewController:(UIViewController *)childViewController {
[childViewController willMoveToParentViewController:nil];
[childViewController.view removeFromSuperview];
[childViewController removeFromParentViewController];
}
Official resource at developer.apple.com
Short answer: "Yes, and yes." The view hierarchy and the view controller hierarchy are still independent. The containment API simply allows views from other controllers to present themselves within a parent controller's view in a clean and consistent way.
You can find a bit in Apple's docs here... this is a relevant passage from the "Container View Controllers Arrange Content of Other View Controllers" section:
A container manages a view hierarchy just as other view controllers do. A container can also add the views of any of its children into its view hierarchy. The container decides when such a view is added and how it should be sized to fit the container’s view hierarchy, but otherwise the child view controller remains responsible for the view and its subviews.
If you have access, I would highly recommend checking out the WWDC 2011 video entitled "Implementing UIViewController Containment" (download it from Apple Developer Video Archive).
Adding to Peter's answer:
one reason I found for calling addChildViewController before addSubview was that when addSubview is called then the viewDidLoad of the child get's called, and in some cases you will want to have the parent-child hierarchy properly set up at that point. If that isn't done, during child's the viewDidLoad the parentViewController property will be nil.
The below is an example provided by Apple documentation.
- (void) displayContentController: (UIViewController*) content {
[self addChildViewController:content];
content.view.frame = [self frameForContentController];
[self.view addSubview:self.currentClientView];
[content didMoveToParentViewController:self];
}
You can also go through the detailed explanation given here -
https://developer.apple.com/library/archive/featuredarticles/ViewControllerPGforiPhoneOS/ImplementingaContainerViewController.html
This will give you idea about child and parent view controller relations and how to work with them.
Im new to iPhone development and I have really taken this to me. I love it, but there is one thing thats naggin' me. How do I keep switching view? I know how to come from first view that is given to me when I create a new project, to a view that I make, but how do I get passed this two windows? How do I get from to views that I created?
I have this app which have a main window with a NavigationController whih is feed with a UITableViewController. This is my main menu. I have a in the upper right corner, a "+"-button which gives me a new view, but how do I get a new view from here? How do I push a new view when the user pick something to add?
Hope someone understand my question. A link to some documentation would be fine. I have looked everywhere.
You can do this many diffrent way, you can do what the Sebastian said, you can also have a common RootViewController that manages your other view controllers view. This is what I like to do, I actually define a protocol on the RootViewController something like ToggleView:UIViewController newController UIViewController:oldController. I make any UIViewController that i want to be able to switch from that view to another implement this protocol. This makes since because generally when you click on a button, you know what View you want to go to next. So when a user clicks the button, in the UIViewController that owns the button i create the new ViewController whose view i want pushed into the screen, this is nice because it also allows me to set up data in the view controller and not have to delegate it to some other object or use a singleton to get the data in the new view, then i call my toggleView methods and the root view controller does the switching. I find this works pretty well and theres berely any code involved. I dont always u se this though, if I know a new view will always come out of another particular view, (for example a home page where one views events and creation of those events), in this case I will loosly couple the view controllers by using protocols.
For that particular situation, people usually use the presentModalViewController:animated: method of UIViewController. UINavigationController is a subclass of UIViewController, so your code would look something like this:
UIViewController *addingViewController = [[UIViewController alloc] initWithNibName:#"AddingView" bundle:nil];
[[self navigationController] presentModalViewController:addingViewController animated:YES];
[addingViewController release];
Here is the rootviewcontrollerdelegate definition
#protocol RootViewControllerViewDelegate
-(void)toggleView:(UIViewController )newController viewController:(UIViewController)oldController;
#end
a possible implementation to toggleView
-(void)toggleView:(UIViewController *)newController viewController:(UIViewController*)oldController {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:1];
[UIView setAnimationTransition:([oldController.view superview] ? UIViewAnimationTransitionFlipFromLeft : UIViewAnimationTransitionFlipFromLeft) forView:self.view cache:YES];
[newController viewWillAppear:YES];
[oldController viewWillDisappear:YES];
[oldController.view removeFromSuperview];
[self.view addSubview:newController.view];
[oldController viewDidDisappear:YES];
[newController viewDidAppear:YES];
[UIView commitAnimations];
[oldController release];
}
This will swipe the view controllers by flipping the view
Obviously you must make a new RootViewController somewhere and start with a view there, (could be the app delegate)
Now if you want a ViewController to be able to use the RootViewController it must conform to the protocol, you declare it in that classes interface like so
#interface MyViewController : UIViewController <RootViewControllerDelegate> {
id delegate;
}#property(assign) id <RootViewControllerViewDelegate> delegate;
Now you can use the delegates method to swap a view for another given that everything has been initialized right. the code to swap two controllers view could look like this
NewViewController *viewController=...
//you can set up your viewControllers data here if you need to
//Since its probable that this view has that data it can just set it instead of
//delegating
viewController.delegate=delegate; //setting up the RootViewController reference
[delegate toggleView:viewController viewController:self];
remember on the toggleView call back to release the old ViewController, if you dont youll get a leak since you lose all reference to that controller.
i have a root view controller that inserts a subview at index 0 at its viewDidLoad method.
i am trying to get the subview to become firstResponder, but can only do this - from my understanding - in the subview's viewDidAppear method.
here's the line of code i added to the root view controller's viewDidLoad method:
[self.view insertSubview: subViewController.view atIndex: 0];
the subviewcontroller has a xib, subViewController.xib, that is shown correctly at runtime. nevertheless, the subViewController's viewDidAppear does not get triggered.
any idea why this happens? any idea how to remedy this - apart from calling viewDidAppear manually (doing so results in failure to become firstResponder)?
thanks,
mbotta
You have to push the view controller on to a navigation stack in order for it's delegate methods to get called. Adding your view controller's view to the subview array won't call them. The first thing you should do is read the View Controller Programming Guide from Apple as this will save you from some headaches you're creating by doing this in a non-standard way.
Instead of adding the view to your root view controller subviews, do this:
SubviewController *controller = [[SubviewController alloc] init];
[[self navigationController] pushViewController:controller animated:YES];
[controller release], controller = nil;
Now your delegate methods will get called. If you don't have a navigation controller as your root view controller, though, this won't work.
If I recall correctly (sorry can't find the place in docs now) -viewDidAppear does not get called for subviews. You must call it manually in the -viewDidAppear method of parent view controller.
I have a viewController (Planner) that loads two view controllers (InfoEditor and MonthlyPlan) when the application starts. MonthlyPlan is hidden behind InfoEditor (on load).
So my question is when I exchange InfoEditor for MonthlyPlan (MonthlyPlan gets brought to the top) how can I have data on the MonthlyPlan view be updated. An NSLog in viewDidLoad is being called when the application starts (which makes sense.) NSLogs in viewDidAppear and viewWillAppear aren't doing anything.
Any ideas?
Thanks!
-- Adding more details --
I'm creating the view hierarchy myself. A simple viewController that is just loading two other viewControllers. The two child viewControllers are loaded at the same time (on launch of application.) To exchange the two views I'm using this code:
[self.view exchangeSubviewAtIndex:1 withSubviewAtIndex:0];
The exchanging of the views is fine. The part that is missing is just some way of telling the subview, you're in front, update some properties.
There's a lack of details here. How are you "exchanging" the two views?
If you were using a UINavigationController as the container then viewWillAppear/viewDidAppear would be called whenever you push/pop a new viewController. These calls are made by the UINavigationController itself. If you ARE using a UINavigationController then make sure you have the prototypes correct for these functions.
- (void)viewWillAppear:(BOOL)animated
If you are trying to implement a view hierarchy yourself then you may need to make these calls yourself as part of activating/deactivating the views. From the SDK page of viewWillAppear;
If the view belonging to a view
controller is added to a view
hierarchy directly, the view
controller will not receive this
message. If you insert or add a view
to the view hierarchy, and it has a
view controller, you should send the
associated view controller this
message directly.
Update:
With the new details the problem is clear: This is a situation where you must send the disappear/appear messages yourself as suggested by the SDK. These functions are not called automagically when views are directly inserted/removed/changed, they are used by higher-level code (such as UINavigationController) that provides hierarchy support.
If you think about your example of using exchangeSubView then nothing is disappearing, one view just happens to cover the other wholly or partially depending on their regions and opacity.
I would suggest that if you wish to swap views then you really do remove/add as needed, and manually send the viewWillAppear / viewWillDisappear notifications to their controllers.
E.g.
// your top level view controller
-(void) switchActiveView:(UIViewController*)controller animated:(BOOL)animated
{
UIController* removedController = nil;
// tell the current controller it'll disappear and remove it
if (currentController)
{
[currentController viewWillDisapear:animated];
[currentController.view removeFromSuperView];
removedController = currentController;
}
// tell the new controller it'll appear and add its view
if (controller)
{
[controller viewWillAppear:animated];
[self.view addSubView:controller.view];
currentController = [controller retain];
}
// now tell them they did disappear/appear
[removedController viewDidDisappear: animated];
[currentController viewDidAppear: animated];
[removedController release];
}
I would just add an updataData method to each subview and call it at the same time you bring it to the front. You would need to add a variable to your root view controller to track the active subView:
[self.view exchangeSubviewAtIndex:1 withSubviewAtIndex:0];
if (subView1IsActive) [subView1Controller updateData];
else [subView2Controller updateData];