Why is root view set to zero size after dismissing a modal VC, when Auto Layout constraints are installed on UIWindow? - ios

I try to use Auto Layout for my application's root view, i.e. I install Auto Layout constraints in the application's UIWindow and enable Auto Layout on the root VC's view.
The problem: When I dismiss a modally presented VC, the view hierarchy "collapses" and only the UIWindow remains visible. I assume that the root VC's view is resized to zero.
If I do not use Auto Layout on the application's root view everything seems to work fine.
My question: Is it forbidden to use Auto Layout for an application's root view? If not, what am I doing wrong? If yes, is this restriction documented somewhere in the official Apple docs, or is it merely "common knowledge"?
The following code is a minimal sample application that demonstrates the problem. You can simply copy&paste the code into a new Xcode project (use the "empty application" template).
#pragma mark Interface declarations
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow* window;
#end
#interface PresentingViewController : UIViewController
#end
#interface PresentedViewController : UIViewController
#end
#pragma mark AppDelegate implementation
#implementation AppDelegate
- (BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Set this to false and the problem goes away
bool useAutoLayout = true;
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
PresentingViewController* pvc = [[PresentingViewController alloc] init];
UINavigationController* nc = [[UINavigationController alloc] initWithRootViewController:pvc];
self.window.rootViewController = nc;
[self.window addSubview:nc.view];
if (useAutoLayout)
{
nc.view.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary* viewsDict = [NSDictionary dictionaryWithObjectsAndKeys:
nc.view, #"ncView",
nil];
NSArray* hConstraintsWindow = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[ncView]-0-|"
options:0
metrics:nil
views:viewsDict];
[self.window addConstraints:hConstraintsWindow];
NSArray* vConstraintsWindow = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[ncView]-0-|"
options:0
metrics:nil
views:viewsDict];
[self.window addConstraints:vConstraintsWindow];
}
nc.view.backgroundColor = [UIColor redColor];
[self.window makeKeyAndVisible];
return YES;
}
#end
#pragma mark PresentingViewController implementation
#implementation PresentingViewController
- (void) loadView
{
self.view = [[UIView alloc] initWithFrame:CGRectZero];
self.view.backgroundColor = [UIColor greenColor];
self.title = #"presenting vc";
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(add:)];
}
- (void) add:(id)sender
{
PresentedViewController* pvc = [[PresentedViewController alloc] init];
UINavigationController* navigationController = [[UINavigationController alloc]
initWithRootViewController:pvc];
navigationController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentViewController:navigationController animated:YES completion:nil];
}
#end
#pragma mark PresentedViewController implementation
#implementation PresentedViewController
- (void) loadView
{
self.view = [[UIView alloc] initWithFrame:CGRectZero];
self.view.backgroundColor = [UIColor yellowColor];
self.title = #"modal vc";
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(done:)];
}
- (void) done:(id)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
}
#end
EDIT: In response to trojanfoe's answer, here are my thoughts why I believe I should be allowed to use Auto Layout for the root view:
UIWindow is derived from UIView, so I see no reason why I should not be allowed to install constraints in it.
It's true that there is no UIViewController instance that manages a UIWindow - but if we talk about the roles of the MVC design pattern, from my point of view the application delegate clearly takes the controller role, and therefore should be allowed to set up constraints.
To make this clear: If it's not possible to use Auto Layout on the root view for some technical reason, I will, of course, accept that. In fact, I want to be convinced not to use Auto Layout, but I prefer rational argument to blind coding.

I believe you are over-complicating the set-up of the views:
Constraints should be set on views only, not on the window.
The view controller is responsible for managing the view hierarchy and you don't need to be involved at all.
I think removing your constraints/view manipulation code will solve your issues:
PresentingViewController* pvc = [[PresentingViewController alloc] init];
UINavigationController* nc = [[UINavigationController alloc] initWithRootViewController:pvc];
self.window.rootViewController = nc;
nc.view.backgroundColor = [UIColor redColor];
[self.window makeKeyAndVisible];
Also shouldn't that be PresentedViewController given the navigation view controller will do the presenting?

Related

UINavigationBar with UISegmentedControl partially covers childViews

I have read many other threads on this and the Apple docs, but haven't found a solution yet for my particular problem.
My app uses a UITabBarController as the rootViewController, and in one of the tabs I have a UISegmentedControl in the navigationBar to switch between three child UITableViewControllers.
(In the real app two of the childVCs are a custom UIViewController, I'm just using three UITableViewControllers for the sample app).
The segmentedControl setup and the switching all works fine. The thing that goes wrong is that only the first UITableViewController is shown correctly. For the second and third one, part of the first cell is hidden under the navigationBar. When I click through all three, the first one is still ok.
I have made a little sample app to show what's going on, using very bright colors for demonstration purposes: https://www.dropbox.com/s/7pfutvn5jba6rva/SegmentedControlVC.zip?dl=0
Here is also some code (I'm not using storyboards):
// AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
FirstViewController *fvc = [[FirstViewController alloc] init];
UINavigationController *firstNavigationController = [[UINavigationController alloc] initWithRootViewController: fvc];
SecondViewController *svc = [[SecondViewController alloc] init];
UINavigationController *secondNavigationController = [[UINavigationController alloc] initWithRootViewController: svc];
// Initialize tab bar controller, add tabs controllers
UITabBarController *tabBarController = [[UITabBarController alloc] init];
tabBarController.viewControllers = #[firstNavigationController, secondNavigationController];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = tabBarController;
[self.window makeKeyAndVisible];
return YES;
}
// FirstViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.title = #"One";
self.view.backgroundColor = [UIColor orangeColor];
UITableViewController *vc1 = [[UITableViewController alloc] init];
UITableViewController *vc2 = [[UITableViewController alloc] init];
UITableViewController *vc3 = [[UITableViewController alloc] init];
vc1.view.backgroundColor = [UIColor redColor];
vc2.view.backgroundColor = [UIColor blueColor];
vc3.view.backgroundColor = [UIColor greenColor];
self.viewControllers = #[vc1, vc2, vc3];
self.segmentTitles = #[#"Red", #"Blue", #"Green"];
self.segmentedControl = [[UISegmentedControl alloc] initWithItems: self.segmentTitles];
[self.segmentedControl addTarget: self
action: #selector(segmentClicked:)
forControlEvents: UIControlEventValueChanged];
self.navigationItem.titleView = self.segmentedControl;
self.segmentedControl.selectedSegmentIndex = 0;
// set the first child vc:
UIViewController *vc = self.viewControllers[0];
[self addChildViewController: vc];
vc.view.frame = self.view.bounds;
[self.view addSubview: vc.view];
self.currentVC = vc;
}
- (void)segmentClicked:(id)sender
{
if (sender == self.segmentedControl)
{
NSUInteger index = self.segmentedControl.selectedSegmentIndex;
[self loadViewController: self.viewControllers[index]];
}
}
- (void)loadViewController:(UIViewController *)vc
{
[self addChildViewController: vc];
[self transitionFromViewController: self.currentVC
toViewController: vc
duration: 1.0
options: UIViewAnimationOptionTransitionFlipFromBottom
animations: ^{
[self.currentVC.view removeFromSuperview];
vc.view.frame = self.view.bounds;
[self.view addSubview: vc.view];
} completion: ^(BOOL finished) {
[vc didMoveToParentViewController: self];
[self.currentVC removeFromParentViewController];
self.currentVC = vc;
}
];
}
So obviously my question is, why does this happen, and what can I do to fix it?
Edit: adding screenshots.
EDIT: Based on the answer below I changed the code in the animation block to:
[self.currentVC.view removeFromSuperview];
if ([vc.view isKindOfClass: [UIScrollView class]])
{
UIEdgeInsets edgeInsets = UIEdgeInsetsMake(self.topLayoutGuide.length, 0, self.bottomLayoutGuide.length, 0);
[UIView performWithoutAnimation: ^{
vc.view.frame = self.view.bounds;
((UIScrollView *)vc.view).contentInset = edgeInsets;
((UIScrollView *)vc.view).scrollIndicatorInsets = edgeInsets;
}];
}
else
{
vc.view.frame = self.view.bounds;
}
[self.view addSubview: vc.view];
Now it works. I'm going to try this with a custom UIViewController as well.
The issue is that you do not set the correct content inset to each table view. The system attempts to do it for you, but I guess your setup is too complex for it, and it only does it for the first tableview that is loaded in viewDidLoad. In your loadViewController: method, when replacing the currently displayed view, make sure to set both the contentInset and scrollIndicatorInsets to the values of the previous view. I think the system will manage to set the correct insets later, in case you rotate to landscape. Try it. If it doesn't, you will need to do it on your own in viewDidLayoutSubviews.

Can't pop iOS viewController. Not sure, but I think it's something with the Navigation Controller

I'm having trouble trying to pop a view
App Delegate
#implementation MAAppDelegate
#synthesize navController;
#synthesize detailViewController;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Init the navController for the Master Detail View of the grade cells
UINavigationController *navController = [[UINavigationController alloc] init];
detailViewController = [[UIViewController alloc] init]; //step6
navController = [[UINavigationController alloc] initWithRootViewController:[[MAController alloc] init]]; //step7
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
self.window.rootViewController = navController; //step8
[self.window makeKeyAndVisible];
// Set MAController as rootViewController
//self.window.rootViewController = [[MAController alloc] init];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
// Use the insanely cool TSMessages to show network alerts
[TSMessage setDefaultViewController: self.window.rootViewController];
return YES;
}
First part of viewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self.navigationController setNavigationBarHidden:YES];
UIBarButtonItem *newBackButton = [[UIBarButtonItem alloc] initWithTitle:#"Home" style:UIBarButtonItemStyleBordered target:self action:#selector(home:)];
self.navigationItem.leftBarButtonItem=newBackButton;
Later, when I change the viewController
NSLog(#"Opened progress report");
UIViewController *detailViewControl = [[UIViewController alloc] init];
// Set progress report as the view controller
[self.navigationController pushViewController:detailViewControl animated:YES];
UIImage *background = [UIImage imageNamed:#"bg"];
// Add static image bg
self.backgroundImageView = [[UIImageView alloc] initWithImage:background];
self.backgroundImageView.contentMode = UIViewContentModeScaleAspectFill;
[self.view addSubview:self.backgroundImageView];
// Add blurred layer to image when tableView goes in front of it
self.blurredImageView = [[UIImageView alloc] init];
self.blurredImageView.contentMode = UIViewContentModeScaleAspectFill;
self.blurredImageView.alpha = 0;
[self.blurredImageView setImageToBlur:background blurRadius:10 completionBlock:nil];
[self.view addSubview:self.blurredImageView];
[self.navigationController setNavigationBarHidden:NO];
So I don't understand why that when I do this, a selector from the button (that I know fires, because I get Righthtere in my log):
-(void)home:(UIBarButtonItem *)sender {
NSLog(#"Righthtere");
// Set progress report as the view controller
[self.navigationController popToViewController:self animated:YES];
}
It doesn't go back to the initial view controller.
You seem to be confusing popToViewController and popViewControllerAnimated. popViewControllerAnimated removes the current view from the stack and brings the new stack top the active view controller. popToViewController pops the stack until the listed view controller is on top of the stack.
Since you are calling popToViewController with self, it will look and see that the requested view controller is already on top of the stack and do nothing. If you wish to go back one view controller then your call should be.
[self.navigationController popViewControllerAnimated:YES];
I use the below code to pop the previous viewcontroller in iOS 8.
[self presentModalViewController:viewcontroller animated:YES];

Black empty area below table in iOS7 with nested UIViewControllers

I get some black empty area below a UITableViewController when nested in a certain way (iOS7). Would anybody know why that happens?
(obviously the code is a 100% stripped down version of the actual app's code)
- (BOOL) application:(UIApplication *) application didFinishLaunchingWithOptions:(NSDictionary *) launchOptions {
UITabBarController *tabBarController = [UITabBarController new];
UITableViewController *demoViewController = [UITableViewController new];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:demoViewController];
tabBarController.tabBar.translucent = NO;
navigationController.navigationBar.translucent = NO;
// THESE LINES INTRODUCE A BLACK AREA BELOW THE TABLE
InBetweenViewController *inBetweenViewController = [InBetweenViewController new];
[inBetweenViewController addChildViewController:navigationController];
[inBetweenViewController.view addSubview:navigationController.view];
tabBarController.viewControllers = #[ inBetweenViewController ];
// INSTEAD, THIS LINE WORKS CORRECTLY
/* tabBarController.viewControllers = #[ navigationController ]; */
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = tabBarController;
[self.window makeKeyAndVisible];
return YES;
}
#implementation InBetweenViewController
- (void) viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
for (UIView *subview in self.view.subviews) {
subview.frame = self.view.bounds;
}
}
#end
It's because your inBetweenViewController doesn't know how to render its childViewController. You just add the the view of that controller without any further instructions. It should be possible to solve this by using a simple autoresizingMask. You then also need to make sure that the subview's size is the same as the superview's size when you add it. If you need more details on how to do that, let me know.

Issue with adding buttons to UINavigationController

So this is how I make the navbar:
- (void)viewDidLoad
{
[super viewDidLoad];
UINavigationController *navBar = [[UINavigationController alloc] init];
[navBar willMoveToParentViewController:self];
navBar.view.frame = CGRectMake(0, 0, 320, 44);
[self.view addSubview:navBar.view];
[self addChildViewController:navBar];
[navBar didMoveToParentViewController:self];
...
And everywhere I have read says that this is how you add buttons:
UIBarButtonItem *button = [[UIBarButtonItem alloc]initWithTitle:#"test" style:UIBarButtonItemStyleBordered target:self action:#selector(print_message:)];
self.navigationItem.rightBarButtonItem = button;
[button release];
But the button does not show on the navbar. What is wrong with this code?
Unless you're building a custom container view controller (which is a relatively rare thing to do), you should not be building a UINavigationController inside your content controller's -viewDidLoad. While it will provide you a navigation bar, your view controller parent-child relationship will be backwards: your content controller will contain the navigation controller, rather than the other way around.
Instead, you need to create the navigation controller earlier in your app's startup process - maybe in your application delegate, or in your main storyboard if you're using one. Make sure that the new navigation controller has your content controller as its root controller (usually by way of -initWithRootViewController:). Then your self.navigationItem configuration will work properly.
You should create your navigationbar probably differently:
In your xxxAppDelegate.m edit this method:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
//This is the ViewController of the view you want to be the root
xxxViewController *tvc = [[xxxViewController alloc]init];
//Now you have to initialize a UINavigationController and set its RootViewController
UINavigationController *nvc = [[UINavigationController alloc]initWithRootViewController:tvc];
//Now set the RootViewController to the NavigationViewController
[[self window]setRootViewController:nvc];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
So now you have a proper NavigationController. If you do this in the viewDidLoad method, the NavigationController will be made each time you reload your view.
Now in your xxxViewController.m edit your init method:
- (id)init
{
...
if (self) {
//Create a UINavigationItem
UINavigationItem *n = [self navigationItem];
//Create a new bar button item
UIBarButtonItem *button = [[UIBarButtonItem alloc]initWithTitle:#"test" style:UIBarButtonItemStyleBordered target:self action:#selector(print_message:)];
[[self navigationItem]setRightBarButtonItem:button];
}
return self;
}
This should now display a proper NavigationBar with a UIBarButtonItem.

subview added to navigationbar ios

I have problems with displaying view as subview on navigationController.view on Ipad . I need to display view with transparent background on my viewController(with navBar),but while i change orientation my navBar become visible on foreGround of my view;
I created simple view based app.
Here code i added to project:
AppDelegate.h:
UINavigationController *_navController;
#property (strong, nonatomic) UINavigationController *navController;
AppDelegate.m:
_navController = [[[UINavigationController alloc] initWithRootViewController:self.viewController] autorelease];
self.window.rootViewController = _navController;
ViewController.m:
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *view = [[[UIView alloc] initWithFrame:self.view.frame] autorelease];
[view setBackgroundColor:[UIColor redColor]];
[self.navigationController.view addSubview:view];
}
try pushing view to Navigation Controller
YourAppDelegate *del = (YourAppDelegate *)[UIApplication sharedApplication].delegate;
[del.navigationController pushViewController:nextViewController animated:YES];
or
UINavigationController* navigation = [[UINavigationController alloc] init];
iVkViewController *overviewViewController = [[iVkViewController alloc] init];
overviewViewController.title = #"First";
[navigation pushViewController:overviewViewController animated:NO];

Resources