Custom view controller presentation causes the navigation bar to bounce - ios

I'm presenting a view controller using the transitioning delegate modally from my root view controller.
UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
UIViewController *rootVC = [window rootViewController];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:authVC];
navController.modalPresentationStyle = UIModalPresentationCustom;
navController.navigationBar.translucent = NO;
navController.transitioningDelegate = self;
[rootVC presentViewController:navController animated:YES completion:nil];
My transitioning delegate adds the view as follows, where authorizationVC is the login view pictured in the screenshots.
UIView *containerView = [transitionContext containerView];
[containerView addSubview:blurredView];
[containerView insertSubview:_authorizationVC.view aboveSubview:blurredView];
_authorizationVC.view.frame = CGRectMake(10, 30, 300, 450);
At first, the view animates in, and the navigation bar is full height, what I believe to be 64 pixels (44 for the nav bar and 20 for the status bar).
As soon as my animation completes, the nav bar shrinks to 44 pixels. The transition is jarring. The content inside my view controller is unaffected.
How do I avoid this jittering navigation bar? The second image is what I'd like to achieve.

Set all the properties of the view before adding it to its superview.
UIView *containerView = [transitionContext containerView];
_authorizationVC.view.frame = CGRectMake(10, 30, 300, 450); /// Change the frame FIRST!
[containerView addSubview:blurredView];
[containerView insertSubview:_authorizationVC.view aboveSubview:blurredView];
Voila! The nav bar acts as expected.

I would avoid using a UINavigationController if you don't really need it.
The UINavigationController takes into account the topLayoutGuide in the way it sizes the navigation bar. If you just want the coloured bar and close button, I'd simplify it and use your own views for that.
If you must use a UINavigationController you could try playing with the status bar's appearance and see how it affects the navigation controller's presentation.

Related

iOS: How to keep showing a subView after pushing to a viewController

I have viewController No.1, which has a subView showing. And after I push to another viewController, No.2, this subView does not shown on the screen anymore, which makes sense, since I have pushed to another VC.
However, I hope to keep this subView on the screen even though I am in a pushed viewController now. So I wonder how I can achieve this?
This will work:
//Create a view in the center of the screen
UIView *view = [[UIView alloc]initWithFrame:CGRectMake(([UIScreen mainScreen].bounds.size.width/2)-50, ([UIScreen mainScreen].bounds.size.height/2)-50, 100, 100)];
view.backgroundColor = [UIColor redColor];
//Create a window class
UIWindow* window = [[UIApplication sharedApplication].windows objectAtIndex:0];
//Pick a level
window.windowLevel = UIWindowLevelStatusBar;
//Add the view
[window addSubview:view];

iOS scale UIView with UINavigationBar and UIStatusBar

I need to implement scrollview, which contains few scaled subviews with UINavigationBar and UIStatusBar.
How to get UIStatusBar for display it on scaled view? Because now I implemented this (shown on the left — scaled view without status bar) but I need this (shown on the right):
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"Storyboard" bundle:nil];
//get view controller and navigation controller from storyboard
UINavigationController *mainNavigControll = [sb instantiateViewControllerWithIdentifier:#"mainNavigControll"];
UIViewController *myViewObject = [sb instantiateViewControllerWithIdentifier:#"myViewController"];
//scaled view and all sub view
CGAffineTransform transform = CGAffineTransformMakeScale(scale, scale);
for( UIView* v in myViewObject.view.subviews )
v.transform = transform;
[myViewObject.view addSubview:myViewObject.navigationController.view];
myViewObject.view.transform = transform;
//get navigation bar and set navigation item
UINavigationBar * navig_bar = mainNavigControll.navigationBar;
[navig_bar pushNavigationItem:myViewObject.navigationItem animated:NO];
//add navigation bar to view
[myViewObject.view addSubview:navig_bar];
//TODO: need added UIStatusBar ????
...
//add to scroll view
[_scrollView addSubview:myViewObject.view ];
This scrollview must looks like multitasking on iOS 7, but only with some internal views.
Use [[UIApplication sharedApplication] setStatusBarHidden:NO]; in custom view controller you have.

Why isn't the rootViewController of my modally presented (form sheet) navController aware of it's smaller size when presented modally?

I am working on an iPad app with a few different modal views, and this code is pretty common:
UIViewController *v1 = [[UIViewController alloc] init];
UINavigationController *nav1 = [[UINavigationController alloc] initWithRootViewController:v1];
nav1.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentViewController:nav1 animated:YES completion:nil];
It could be that I am doing this wrong, but this is how I am presenting a navController-nested vc modally.
The problem is that within the v1 class, any reference to self.frame/bounds results in full screen dimensions:768x1024. Even though the navController clearly isn't being displayed with that size.
What should I be doing to make it so that the v1 vc knows how big it actually is? So that if I wanted to add, say, a tableView, it would know how big it should be?
Thanks!
EDIT:
I have tried a few more things, and still don't have a solution to this problem. I have made a simple sample project to illustrate the problem I am having. I just have one view and this is the core of the code:
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"Frame: %#", NSStringFromCGRect(self.view.frame));
NSLog(#"Bounds: %#", NSStringFromCGRect(self.view.bounds));
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(self.view.frame.size.width - 400, 0, 400, 400);
button.backgroundColor = [UIColor redColor];
[button addTarget:self action:#selector(presentModal) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
- (void)presentModal {
SSViewController *view = [[SSViewController alloc] init];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:view];
nav.modalPresentationStyle = UIModalPresentationFormSheet;
[self.navigationController presentViewController:nav animated:YES completion:nil];
}
When this view loads, I have a big red button that is up against the top right corner of my view. When I press the button, it loads the same VC in a modal view embedded in a navController. The button shows up nearly off screen because the frame hasn't changed. It still shows as full screen. Here is a link to the project.
Not sure why you're having the issue you're having. I'm using the following code:
- (void)presentNewView {
NewViewController *newVC = [[NewViewController alloc] initWithNibName:nil bundle:nil];
newVC.view.backgroundColor = [UIColor redColor];
UINavigationController *newNC = [[UINavigationController alloc] initWithRootViewController:newVC];
newNC.modalPresentationStyle = UIModalPresentationFormSheet;
[self.navigationController presentViewController:newNC animated:YES completion:NULL];
}
.. it results in the following in the simulator:
.. and when I print out the first ViewController's frame and bounds (I thought it might be an issue with the two) I get the following:
frame height: 1024.000000
frame width: 768.000000
bounds height: 1024.000000
bounds width: 768.000000
.. and when I print out the presented ViewController's frame/bounds I get the following:
frame height: 620.000000
frame width: 540.000000
bounds height: 620.000000
bounds width: 540.000000
How are you determining the size of the frame exactly? Any reference within the v1 class that was presented modally SHOULD know its actual size, like I showed above.
EDIT
The major difference I found with my code and yours, is that in my code I created a subclass of my view controller "NewViewController" and was printing out the frame from within that class. The class itself seems to be aware of its correct bounds, but the class the presented it seems not to be. This is demonstrated by printing the view property from the ViewController class that presented it:
NewViewController's View From Presenting Class: frame = (0 0; 768 1024)
..compared to printing out the self.view from within the ViewDidAppear method of NewViewController itself:
NewViewController's View Did Appear: frame = (0 0; 540 576)
Moral of the story, if you are going to be presenting a UIViewController in the way you've shown, you're likely going to want to subclass UIViewController anyway so you can customize it however you want, so within that file if you reference self.view or self.bounds you will be getting the ACTUAL view/bounds.
EDIT #2
Based on the project you provided, the reason why you are having that issue is because you are printing out the frame/bounds of the view in viewDidLoad as opposed to viewDid/viewWillAppear. Adding those NSLog statements to VWA or VDA provides you the correct frame, so as I said in my initial edit, you should be fine accessing the view of the modal correctly at that point.
It's a new feature of iOS7. If you embed a UIViewController in navigation bar, it won't get smaller, because by default navigation bar is translucent.
You will see it if you change the background color of a view controller, that the top part of it is actually behind the navigation bar.
To lay out the v1 view controller underneath the navigation bar, you can use following code:
if ([v1 respondsToSelector:#selector(edgesForExtendedLayout)]) {
v1.edgesForExtendedLayout = UIRectEdgeNone;
}
It will behave just as in iOS6.
when presenting view controllers modally from a child view controller (one that has lass than the full screen and is a child of another view controller..) it is important to do this so that the modal controller knows the size of the canvas its appearing in
childViewController.definesPresentationContext = YES;
modalViewControllerWhichIsAboutToBePushed.modalPresentationStyle = UIModalPresentationCurrentContext

iOS - Semi-transparent modal view controller

I want to present a view controller with a slightly transparent background modally over the current view, such that the first view is slightly visible under the modal view.
I set the alpha value of the modal view controller and set the modalPresentationStyle to UIModalPresentationCurrentContext, as suggested in another post.
The result is that the view background is transparent when animating up, but when view controller is in place it changes to opaque black. It goes back to being transparent while animating the dismissal.
How can I get it to be transparent when active ?
I have tested in iOS 6 and 7. The code I am using follows:
MyModalViewController *viewController = [[MyModalViewController alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController];
[navController setNavigationBarHidden:YES];
self.navigationController.modalPresentationStyle = UIModalPresentationCurrentContext;
[self.navigationController presentViewController:navController animated:YES completion:NULL];
iOS 8 added a new modal presentation style specifically for this purpose:
presentedViewController.modalPresentationStyle = UIModalPresentationOverFullScreen
From the spec:
UIModalPresentationOverFullScreen
A view presentation style in which the presented view covers the screen. The views beneath the presented content are not removed from the view hierarchy when the presentation finishes. So if the presented view controller does not fill the screen with opaque content, the underlying content shows through.
If you are targeting ios 8 and above you can set the modal presentation style to "over current context" and you are done.
If ios 7 and below, you would have to create a custom transition style so that the presenting screen doesn't go blank after transition. That is rather complicated.
The solution I present offers a lot of flexibility: make a screenshot before showing the modal dialog and set that as the background image for the application window. By default, that background is black (that is what you see when the back view controller dissapears). Change the background to the screenshot of the app. Make the screenshot in the viewWillAppear or viewDidLoad method of your transparent view. This works even with push segues, not only modal dialogs, but you should avoid animations. In general, avoid animations which affect the position of the background view because those will make it seem like it snaps back into place when transition finishes. It is a good idea to reset the background to its previous black image on viewDidDissapear to avoid unwanted effects.
You can maintain a stack of such background images and you can do multiple "transparent" push seques. Or have some complex/deep menu which appears on top of some main screen. For these many reasons I think this solution is better than rolling your own transitioning code. It is more flexible and easier to implement, and you don't have to deal with the animations yourself.
The reason that the BG view controllers disappear after a modal is shown is that the default transition in iOS 7 removes the BG view after animation completed. If you define your own transition and you set your BG view not to be removed (just changing its alpha) then you will have the transparent modal view.
Same problem occured to me. I have solved it by looking at the following url about a custom alert controller. I managed to get it working even with a UINavigationController.
Swift
let viewController = UIViewController()
viewController.providesPresentationContextTransitionStyle = true
viewController.definesPresentationContext = true
viewController.modalPresentationStyle = .overCurrentContext
viewController.modalTransitionStyle = .crossDissolve
DispatchQueue.main.async {
self.navigationController?.present(viewController, animated: true, completion: nil)
}
Objective C
UIViewController *viewController = [UIViewController new];
viewController.providesPresentationContextTransitionStyle = true;
viewController.definesPresentationContext = true;
viewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
viewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
dispatch_async(dispatch_get_main_queue(), ^{
[self.navigationController presentViewController:viewController animated:true completion:nil];
});
Here is a solution.
Create your presenting view controller. Add a backView to this view controller's main view. Name this as backView.
In SecondViewController.m
-(void)viewDidLoad
{
// Make the main view's background clear, the second view's background transparent.
self.view.backgroundColor = [UIColor clearColor];
UIView* backView = [[UIView alloc] initWithFrame:self.view.frame];
backView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.6];
[self.view addSubview:backView];
}
Now you have a view controller with half transparent background. You can add anything you want to the self.view , the rest will be half transparent.
After that, in FirstViewController.m
self.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentViewController:secondViewController animated:YES completion:nil];
My solution is this:
Create a custom transparent overlay UIView that comes over any view, navigationbar and tabbbar.
-In the navigation controller (or tabbar controller) that your view controller is embedded in I create a custom view with it's frame equal to the frame of the navigation controller's view.
-Then I set it offscreen by setting it's origin.y to navigationController.view.height
-Then I create 2 functions -(void)showOverlay and -(void)hideOverlay that animate the overlay view on and off screen:
- (void)hideOverlay{
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.3];
CGRect frm = self.helpView.frame;//helpView is my overlay
frm.origin.y = self.offscreenOffset; //this is an Y offscreen usually self.view.height
self.helpView.frame = frm;
[UIView commitAnimations];
}
- (void)showOverlay{
[self.view bringSubviewToFront:self.helpView];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.3];
CGRect frm = self.helpView.frame;
frm.origin.y = self.onscreenOffset;
self.helpView.frame = frm;
[UIView commitAnimations];
}
-In my view controller I can just call
[(MyCustomNavCtrl *)self.navigationController showOverlay];
[(MyCustomNavCtrl *)self.navigationController hideOverlay];
And that's about it.
FYI: The syntax is now:
childVC.modalPresentationStyle = UIModalPresentationStyle.OverFullScreen
Why don't you try setting this in AppDelegate
self.window.rootViewController.modalPresentationStyle = UIModalPresentationCurrentContext;
then changing the alpha on the view being presented

UINavigationController's view and 20 points offset

This is an already posted question but I need to understand what is going on with the following scenario. I'm developing an iPad application and I created a UINavigationController like the following (test purposes).
UINavigationController* navigationController = [[UINavigationController alloc] init];
navigationController.view.backgroundColor = [UIColor redColor];
Once created, I added the UINavigationController's view as a subview of a UIViewController.
[self.view addSubview:navigationController.view];
The result is displayed in the following image:
As you can see, there is a gap between the status bar and the navigation bar of the UINavigationController. Since the view of the UINavigationController is red, it seems that the frame of the navigation bar has that gap.
I've found an hack to fix the problem. By setting the frame for the UINavigationController, the navigation bar goes in the right position.
CGRect statusBarFrame = [[UIApplication sharedApplication] statusBarFrame];
CGRect frameRect = CGRectMake(navigationController.view.frame.origin.x, navigationController.view.frame.origin.y - statusBarFrame.size.height, navigationController.view.frame.size.width, navigationController.view.frame.size.height);
navigationController.view.frame = frameRect;
Now, since I don't like very much that hack and I would understand what is going on, do you have any suggestions to find an elegant solution to resolve the problem?
Thank you in advance.
That 20px offset is for status bar, navigation controller is designed to be full-screen, but you are adding it to the subview of the main view.
viewController setWantsFullScreenLayout:YES
It may help you.
This is the fix that really solved all the problem. So thought of posting it as it might really.
How this works
This gonna set my navigation bar origin to 0, and in turn the view of navigation is also set to 0 which takes the reference of Navigation Bar, which solves all the mess :)
And 44 is the heigh of Navigation Bar in the code. :)
Put the code in ViewDidAppear,
CGRect windowFrame = [UIScreen mainScreen].applicationFrame;
//Setting the Origin to 0 of both Navigation Bar and Navigation View for Both Landscape and Portrait
if (UIDeviceOrientationIsPortrait([UIDevice currentDevice].orientation)){
[self.navigationController.navigationBar setFrame:CGRectMake(0, 0, windowFrame.size.width, 44)];
[self.navigationController.view setFrame:CGRectMake(0, 0, windowFrame.size.width, windowFrame.size.height)];
}
else{
[self.navigationController.navigationBar setFrame:CGRectMake(0, 0, windowFrame.size.height, 44)];
[self.navigationController.view setFrame:CGRectMake(0, 0, windowFrame.size.height, windowFrame.size.width)];
}
[self setWantsFullScreenLayout:<#(BOOL)#>];
is deprecated. If you want to add subviews to your ViewController without worrying about NevigationBar use:
self.edgesForExtendedLayout = UIRectEdgeNone;

Resources