Add child view controller to UINavigationController
Asked Answered
D

3

20

I'm trying to add a child view controller to a UIViewController contained in a UINavigationController with this code:

- (void)buttonTapped:(id)sender
{
    MyChildController *viewController = [self.storyboard instantiateViewControllerWithIdentifier:@"MyChild"];
    [self addChildViewController:viewController];
    [self.view addSubview:viewController.view];
    [viewController didMoveToParentViewController:self];


    viewController.view.alpha = 0.0f;
    [UIView animateWithDuration:0.4 animations:^{
        viewController.view.alpha = 1.0f;
    }];
}

But this is the result:

Image Result

As you can see the UINavigatioBar and the UIToolbar are still on top of the child view controller. How can I put the child view controller on top of all? I've already tried to replace the code with:

[self.navigationController addChildViewController:viewController];
    [self.navigationController.view addSubview:viewController.view];
    [viewController didMoveToParentViewController:self.navigationController];

But in this way the viewDidAppear:animated of the viewController doesn't get called. I don't know, why.

Diuresis answered 8/10, 2013 at 20:44 Comment(0)
P
8

In your first view controller, do something like this:

- (IBAction)buttonClick:(id)sender
{
    SecondViewController *secondView = [self.storyboard instantiateViewControllerWithIdentifier:@"SecondViewController"];
    UIImage *blurryImage = [UIImage imageNamed:@"foo.jpeg"];
    secondView.imageView.image = blurryImage;
    [self.navigationController addChildViewController:secondView];
    secondView.view.frame = self.navigationController.view.frame;
    [self.navigationController.view addSubview:secondView.view];
}

Then in your second view controller, add the getter for your imageview:

-(UIImageView *)imageView
{
    if( _imageView == nil )
    {
        _imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 320, 548)];
        [self.view addSubview:_imageView];
    }
    return _imageView;
}
Phantasy answered 8/10, 2013 at 23:5 Comment(8)
I'm trying to use the viewcontroller-container approach because I need to blur the background view of the modal view controller that is also semi-transparent. The effect is the same as notification view that you pull down from the top of the screen. The background is semi-transparent and it's also blur/frosted glass. Why if I add a child view controller to self.navigationController the viewDidAppear method is not called?Diuresis
Hmm...not quite sure...have you tried calling [viewController viewWillAppear:NO]?Phantasy
Yes and it does nothing. I need to call [viewController viewDidAppear:NO] just after the line [viewController didMoveToParentViewController:self.navigationController]. Why the hell the methods are not called? Otherwise if I add the child view controller to self and not self.navigationController it works but as showed in the above image the view is 'inside' the navigationController.Diuresis
So I just coded up a simple case of your problem and it looks like viewDidLoad is getting called. Why not put your logic in there? I am imagining you are deallocating your view when it gets hidden right?Phantasy
Thanks for the time Jonah. Yea the viewDidLoad is getting called. I'm using viewDidAppear because I need to do stuff about making the view blurred (actually it doesn't work now). By the way thanks for the code! ;) Do you know how to make the view of the second view controller look like blurred/frosted glass?Diuresis
let us continue this discussion in chatPhantasy
In order for viewWillAppear: and viewDidAppear: to be called, you need to send the message -beginAppearanceTransition:animated: to the view controller responsible for the view you added. Then you do the animation, if any. And in the animation completion block, send -endAppearanceTransition to the view controller that appeared. NOTE: Do not call [viewController viewDidAppear:NO] - you should never invoke these methods yourself.Eben
this is the perfection to meFasciculus
F
34

@Sam's comment is correct. You need to call beginApperanceTransition:animated: and endAppearanceTransition for viewDidAppear to be triggered. The reason why UINavigationController does not call viewDidAppear when you add a child view controller is because it has overridden its container composition methods to prevent the programmer from adding a child view controller in strange places. In your case, it doesn't want your child view to cover up the navigation bar. The correct usage of a navigation controller is to have children appear under the navigation bar. Nonetheless, you can still force upon this non-standard UI by manually telling the child when it is appearing and when it has finished appearing.

Add a child to UINavigationController

MyChildViewController* child = [[MyChildViewController alloc] init];
[self.navigationController addChildViewController:child];
child.view.frame = self.navigationController.view.bounds;
[self.navigationController.view addSubview:child.view];
child.view.alpha = 0.0;
[child beginAppearanceTransition:YES animated:YES];
[UIView
    animateWithDuration:0.3
    delay:0.0
    options:UIViewAnimationOptionCurveEaseOut
    animations:^(void){
        child.view.alpha = 1.0;
    }
    completion:^(BOOL finished) {
        [child endAppearanceTransition];
        [child didMoveToParentViewController:self.navigationController];
    }
];

Remove a child from UINavigationController

[child willMoveToParentViewController:nil];
[child beginAppearanceTransition:NO animated:YES];
[UIView
    animateWithDuration:0.3
    delay:0.0
     options:UIViewAnimationOptionCurveEaseOut
    animations:^(void){
        child.view.alpha = 0.0;
    }
    completion:^(BOOL finished) {
        [child endAppearanceTransition];
        [child.view removeFromSuperview];
        [child removeFromParentViewController];
    }
];
Forbade answered 25/3, 2014 at 21:19 Comment(2)
I think child be calling didMoveToParentViewController: when its being removed.. Something like, [child didMoveToParentViewController:nil]Teasley
beginAppearanceTransition should be called before addSubview, and endAppearanceTransition should be called after removeFromSuperview, otherwise 'unbalanced calls' errors might occur.Fescennine
P
8

In your first view controller, do something like this:

- (IBAction)buttonClick:(id)sender
{
    SecondViewController *secondView = [self.storyboard instantiateViewControllerWithIdentifier:@"SecondViewController"];
    UIImage *blurryImage = [UIImage imageNamed:@"foo.jpeg"];
    secondView.imageView.image = blurryImage;
    [self.navigationController addChildViewController:secondView];
    secondView.view.frame = self.navigationController.view.frame;
    [self.navigationController.view addSubview:secondView.view];
}

Then in your second view controller, add the getter for your imageview:

-(UIImageView *)imageView
{
    if( _imageView == nil )
    {
        _imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 320, 548)];
        [self.view addSubview:_imageView];
    }
    return _imageView;
}
Phantasy answered 8/10, 2013 at 23:5 Comment(8)
I'm trying to use the viewcontroller-container approach because I need to blur the background view of the modal view controller that is also semi-transparent. The effect is the same as notification view that you pull down from the top of the screen. The background is semi-transparent and it's also blur/frosted glass. Why if I add a child view controller to self.navigationController the viewDidAppear method is not called?Diuresis
Hmm...not quite sure...have you tried calling [viewController viewWillAppear:NO]?Phantasy
Yes and it does nothing. I need to call [viewController viewDidAppear:NO] just after the line [viewController didMoveToParentViewController:self.navigationController]. Why the hell the methods are not called? Otherwise if I add the child view controller to self and not self.navigationController it works but as showed in the above image the view is 'inside' the navigationController.Diuresis
So I just coded up a simple case of your problem and it looks like viewDidLoad is getting called. Why not put your logic in there? I am imagining you are deallocating your view when it gets hidden right?Phantasy
Thanks for the time Jonah. Yea the viewDidLoad is getting called. I'm using viewDidAppear because I need to do stuff about making the view blurred (actually it doesn't work now). By the way thanks for the code! ;) Do you know how to make the view of the second view controller look like blurred/frosted glass?Diuresis
let us continue this discussion in chatPhantasy
In order for viewWillAppear: and viewDidAppear: to be called, you need to send the message -beginAppearanceTransition:animated: to the view controller responsible for the view you added. Then you do the animation, if any. And in the animation completion block, send -endAppearanceTransition to the view controller that appeared. NOTE: Do not call [viewController viewDidAppear:NO] - you should never invoke these methods yourself.Eben
this is the perfection to meFasciculus
T
3

@Pwner's answer Swift version:

Add child to UINavigaitonController

let child = MyChildViewController()
self.navigationController?.addChildViewController(child)
guard let navigationController = navigationController else {
    return
}
child.view.frame = navigationController.view.bounds
child.beginAppearanceTransition(true, animated: true)
self.navigationController?.view.addSubview(child.view)
self.view.alpha = 0
UIView.animate(withDuration: 0.3, animations: {
    child.view.alpha = 1.0
}, completion: { _ in
    guard let navigationController = self.navigationController else {
        return
    }
    child.endAppearanceTransition()
    child.didMove(toParentViewController: navigationController)
})

Remove a child from UINavigationController

child.willMove(toParentViewController: nil)
child.beginAppearanceTransition(false, animated: true)
UIView.animate(withDuration: 0.3, animations: {
    child.view.alpha = 0.0
}, completion: { _ in
    guard let navigationController = self.navigationController else {
        return
    }
    child.view.removeFromSuperview()
    child.endAppearanceTransition()
    child.removeFromParentViewController()
})
Twinge answered 11/4, 2018 at 10:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.