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

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

Related

Frame of main View of UIViewController inside a UINavigationViewController

I am confused by the frame of self.view within a ViewController I have made the root ViewController of a UINavigationViewController. Lets say I add a red square UIView to self.view (where self.view is the embedded ViewController's view property). This red view is mostly occluded by the navigation bar. In other words, why doesn't self.view's frame's origin.Y = HeightOfNav bar? Instead it is 0.
I have read documentation that states a view added to the navigation controller resizes to take the nav bar's height into account, however, all my testing has shown this isn't the case. Not at any point in the view lifecycle does my view correct its origin to be visible. Perhaps I am setting up this viewcontroller/navigationController incorrectly such that the appropriate view-resizing does not take place but you can read the subsequent code to see if that is the case.
Here is the simple code where I generate these view controllers in appdelegate:
UIViewController * a = [[ViewController alloc] init];
UINavigationController * nav = [[UINavigationController alloc] initWithRootViewController:a];
self.window.rootViewController = nav;
Logging out self.view's frame in a's viewDidLoad, viewWillAppear, viewDidAppear, etc.. shows { 0, 0, screenWidth, screenHeight } (where screen* is the current simulator). So, 0,0 is behind a navBar and this is not the behavior I would expect.
Again, my expectation was that self.view would now have a frame whose origin starts after the NavigationBar at the top of any view inside a UINavigationController.

iOS7 slideout drawer draggable bug

I am trying to implement a slideout drawer similar to a the one found in this guide: http://www.raywenderlich.com/32054/how-to-create-a-slide-out-navigation-like-facebook-and-path
I have two subviews that are added to the navigation controller, The drawer is initialized as follows in viewDidLoad:
self.drawerViewController = [[DrawerViewController alloc] initWithNibName:#"drawer" bundle:nil];
self.drawerViewController.view.autoresizingMask = UIViewAutoresizingFlexibleHeight;
[self.view addSubview:self.drawerViewController.view];
[self addChildViewController: self.drawerViewController];
[self.drawerViewController didMoveToParentViewController:self];
self.drawerViewController.view.frame = CGRectMake(0, 0, 150, self.view.frame.size.height);
then the contentview is initialized using a viewcontroller from my storyboard (and i call send subviewtoback to move the drawer behind it):
UIStoryboard* sb = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
self.contentViewController = [sb instantiateViewControllerWithIdentifier:#"HomeViewController"];
[self.view sendSubviewToBack:self.drawerViewController.view];
[self pushViewController:self.contentViewController animated:YES];
I move the main content view by animating the frame of the contentview which uncovers the drawer underneath:
[UIView animateWithDuration:SLIDE_TIME delay:0 options:UIViewAnimationOptionBeginFromCurrentState
animations:^{self.contentViewController.view.frame = CGRectMake(150, 0, self.view.frame.size.width, self.view.frame.size.height);
}completion:nil];
Which works but if I do something like click the margin of the drawer and drag, the drawer can end up covering the entire screen in the iphone simulator or disappearing showing the black background (which is more easily reproduced when rotating the device while showing drawer). My question is why is the drawerview draggable and how do you prevent this?
Edit:
I have found the source of the dragging is from this addChildViewController line:
[self addChildViewController: self.drawerViewController];
However removing this doesnt allow users to click the table cells anymore.
The problem was using a navigation controller as a container of subviews. I don't think it's meant to be used this way so I ended up scrapping this and rewriting it with a uiviewcontroller container instead.

Solve the viewController sizing mystery! Why do elements (UIImageView and Nav bar) change size when switching viewControllers?

So this is the code for my mapView:
self.mapView = [[MKMapView alloc] init];
self.mapView.frame = CGRectMake(0, 70 , self.view.bounds.size.width,
self.view.bounds.size.height);
When I push to this view controller from one viewcontroller it looks just fine. The navigation bar and map view are all in the correct spot; however, when I tried to create a button that go directly to the map Viewcontroller from ANOTHER viewController everything changed.
The map view has shrunk and the navigation bar is missing now..? Here's the new button from the other ViewController:
UIImage* image4 = [UIImage imageNamed:#"Near_me_carousel.png"];
_mapButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 18, 26, 26)];
[_mapButton setBackgroundImage:image4 forState:UIControlStateNormal];
[_mapButton addTarget:self action:#selector(MapButton)
forControlEvents:UIControlEventTouchUpInside];
[_mapButton setShowsTouchWhenHighlighted:YES];
[self.view addSubview:_mapButton];
Here's the method:
-(void) MapButton {
MapViewController *mapView = [[MapViewController alloc] init];
[self.navigationController presentViewController:mapView animated:YES completion:NULL];
}
I'm so confused as to why this is happening! Any ideas?
I don't know if this is really your problem, but here's a shot:
Inside your button method, you are calling your MapViewController modally. If the viewController itself does not have a UINavigationBar, of course it will not be displayed.
When you have a push transition, through a UINavigationController, a navigationBar is automatically added above of your view.
And here's the tricky part: when presented as a push transition, the 0 y value of the view's frame is the point just below the navigationBar. I. e., the navigationBar does not belong to your view.
And when presented through a modal transition (presentViewController:), the 0 y value is the top/left point of the window, even if you add a NavigationBar yourself.
Ilustrating:
The origin of it will be here if presented as a modal:
And here if presented if a push in a navigation stack:
Conclusion:
So, in your case, the y-value 70 in this code
self.mapView.frame = CGRectMake(0, 70 , self.view.bounds.size.width,
self.view.bounds.size.height);
will be different according to the transition style. In a modal, it will look like it's displaced 44 points to the top (size of the navigation bar)

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

iPad UIModalPresentationFormSheet with UITabBarController's moreNavigationController edit mode issue

This is seemingly a bug, but i'm wondering if anyone can think of a workaround.
On iPad, you present a view controller as a UIModalPresentationFormSheet. This view controller is extending UITabBarController and has enough controllers to automatically display the "more" tab bar button. Once you tap on the more button it will display the list correctly, but as soon as you tap on 'edit' it presents the edit view larger then the actual form sheet (cropped inside the form sheet), causing the content to be out of view, including the toolbar with the "done" button. The only way to dismiss is to force quit the app.
To verify that it's not something specific to my app I started a single view project, and presented a simple modal view. This modal view controller extends UITabBarController and has the following init method:
- (id)init {
self = [super init];
if (self) {
self.modalPresentationStyle = UIModalPresentationFormSheet;
NSMutableArray *controllers = [NSMutableArray array];
for (int i = 0; i< 15; i++) {
UIViewController *vc = [[UIViewController alloc] init];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
vc.title = [NSString stringWithFormat:#"view %i", i];
[controllers addObject:nav];
}
self.viewControllers = controllers;
}
return self;
}
I also tried adding the modalPresentationStyle to moreNavigationController with no change.
Good day, dizy.
A nice challenge you've made. Here is a solution, maybe it's a bit hardcore, but it works.
I've done as you wrote – subclassed UITabBarController and presented it as a modal view controller. And run into the same problem. When tapping "edit" button in "More" screen UITabBarCustomizeView appears and it's frame is inadequate.
So I've done the following. I've made MyModalTabBarVC a delegate of itself and implemented tabBarController:willBeginCustomizingViewControllers: method:
- (void)tabBarController:(UITabBarController *)tabBarController
willBeginCustomizingViewControllers:(NSArray *)viewControllers
{
UIView *modalView = self.view;
CGRect bounds = modalView.bounds;
UIView *customizationView = [[modalView subviews] objectAtIndex:1];
UIView *customizationNavBar = [[customizationView subviews] objectAtIndex:0];
CGRect navBarFrame = [customizationNavBar frame];
navBarFrame.size.width = bounds.size.width;
customizationNavBar.frame = navBarFrame;
customizationView.frame = bounds;
}
So when this method is called UITabBarCustomizeView is already created. And a wrong frame can be changed manually. If you log po [self.view subviews] at the start you'll get:
(id) $1 = 0x06c6a940 <__NSArrayM 0x6c6a940>(
<UITransitionView: 0xd744ab0; frame = (0 0; 540 571); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0xd744b50>>,
<UITabBarCustomizeView: 0x6c5e570; frame = (0 -384; 768 1004); animations = { position=<CABasicAnimation: 0x6c569a0>; }; layer = <CALayer: 0x6c618d0>>,
<UITabBar: 0xd744110; frame = (0 571; 540 49); autoresize = W+TM; layer = <CALayer: 0xd742b80>>,
)
PS. This solution doesn't fix animation. As you can see from log, corrupted animation is already created and charged. I hope that canceling it and adding a new one, appropriate, will not be a problem.
The modal view's viewController must be causing the glitch.
You could try to:
hide the tab bar while editing and un-hiding it when the done button
is pressed.
create a custom toolbar for the view controller, this could be done
with a UIView, so that it's set always be on top of the view.
resize your individual tabs. Best way to do this is to create your
own custom tab bar with a UIViewController and IBActions connected
to UIButtons with IBOutlets.
Why would you have so many tabs in the modalPresentationStyle? I personally would use push segue instead.
Try pushing to a new set of view controllers that are under their own navigation controller as well. There would be more room for the tab bar. To get back, put a back button in the toolbar that pops the push, or pushes back to the original.

Resources