Black empty area below table in iOS7 with nested UIViewControllers - ios

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.

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.

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

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?

Adding UIImageView to rootViewController

All I am simply trying to do is display an image on the screen as I am just starting out iOS
development. I figured since UIImageView is a subclass of UIView, I would add it in a similar way but I am not having any luck. I understand this is an easy question but any help would be appreciated.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions: (NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.window.rootViewController = [UIViewController new];
UIImage* stallion = [UIImage imageNamed:#"stallion1.png"];
UIImageView* iv = [UIImageView alloc];
iv.image = stallion;
[self.window.rootViewController.view addSubview:iv];
[self.window makeKeyAndVisible];
return YES;
}
Try something like this:
App delegate:
#import "RootViewController.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
//allocate and initialize the root view controller
RootViewController *rootViewController = [[RootViewController alloc] init];
//set the root view controller
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
return YES;
}
Create a subclass of UIViewController called RootViewController
.h
#import <UIKit/UIKit.h>
//set the class RootViewController as a subcalss of UIViewController
#interface RootViewController : UIViewController
#end
.m
//this is one of the life cycle methods of a UIViewController and should already be in the code when the class is created
- (void)viewDidLoad
{
//execute the viewDidLoad method of the superclass (UIViewController)
[super viewDidLoad];
//allocate and initialize the image view
//assign the image view a frame
//x offset from the left = 0.0
//y offset from the top = 0.0
//width = the view controller's view's width (should be the whole screen)
//height = the view controller's view's height(should be the whole screen)
UIImageView *iv = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, self.view.frame.size.width, self.view.frame.size.height)];
[iv setImage:[UIImage imageNamed:#"myImageName.png"]];
//the background will be red if everything is setup correctly, but the image isn't found
[iv setBackgroundColor:[UIColor redColor]];
//add the image view to the view controller's view
[self.view addSubview:iv];
}
//EDIT
It would be very helpful for you if u will read book in which there are information about View Controllers. This is very essential information. I recommend you to read "The Core IOS 6 Cookbook" by Erica Sadum. It really helped me.
You can also just look at the apple documentation http://developer.apple.com/library/ios/#documentation/WindowsViews/Conceptual/ViewControllerCatalog/
Write something like this in AppDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
MyViewController *viewController = [[MyViewController alloc] init];
self.window.rootViewController = viewController;
[self.window makeKeyAndVisible];
return YES;
}
Next you must add the UIImageView but only when the view of the created view controller is loaded. The best method for this is viewDidLoad. You can also use viewDidAppear: or viewWillAppear:.
You must override this methods in MyViewController class (which should inherit from the UIViewController class).
- (void)viewDidLoad
{
[super viewDidLoad];
UIImageView *imageView = [UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, self.frame.size.width, self.frame.size.heigth);
imageView.image = [UIImage imageNamed#"stallion1.png"];
[self.view addSubview:imageView];
}

initWithFrame is given empty frame

I'm attempting to use a UINavigationController that utilizes a custom subclass of UINavigationBar. However, I'm running into a problem with initWithFrame. When initWithFrame is called, I print out the dimensions and it shows a 0.0 width and 0.0 height. But, when I print out the same frame in -(void) layoutSubviews inside of the UINavigationBar subclass, the frame is correctly set. Why is initWithFrame receiving a zero-sized frame, and what can I do to fix this? It doesn't sound correct to be manually calling initWithFrame or anything like that, since UINavigationController should take care of that. Here is the relevant code:
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
UINavigationController *navController = [[UINavigationController alloc] initWithNavigationBarClass:[CustomNavigationBar class] toolbarClass:nil];
CoolViewController *vc = [[CoolViewController alloc] init];
[navController pushViewController:vc animated:YES];
self.window.rootViewController = navController;
[self.window makeKeyAndVisible];
return YES;
}
CustomNavigationBar.m
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
NSLog(#"initWithFrame with frame: %f, %f", frame.size.width, frame.size.height); // This prints "initWithFrame with frame: 0, 0"
}
return self;
}
Thank you!
I believe the navigation controller doesn't actually set the frame size to a specified value. Instead, it asks the navigation bar for its sizeThatFits: and uses that. By definition, that can't be done and passed to initWithFrame:. So, you want to use a combination of sizeThatFits:, setFrame: and layoutSubviews to add, size and reset your navigation bar content.

Hiding custom UITabBar

I have successfully created and implemented a custom UITabBarController with a custom UITabBar following this tutorial. It works fine until I have to hide it.
I'm not using Storyboards or IB and I have to get a reference to my existing UITabBarController which is on screen to hide a custom UIView in it. I'm trying to do it this way but it's only creating a new instance of that UITabBarController and not pointing me to the original instance I see onscreen:
SGTabBarController *tabBarController = [[SGTabBarController alloc] init];
[tabBarController hideCustomTabBar];
SGTabBarController.h
#interface SGTabBarController : UITabBarController
#property (nonatomic) int tabBarHeight;
-(void)hideCustomTabBar;
-(void)showCustomTabBar;
#end
SGTabBarController.m
-(void)hideCustomTabBar{
customTabBarView.hidden = YES;
NSLog(#"HIDDEN!!!");
}
-(void)showCustomTabBar{
customTabBarView.hidden = NO;
}
Any ideas on how to get to it? Thanks in advance!
How I am able to access a custom UITabBarController anywhere in the app.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Set up the Dashboard
//
_window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[_window makeKeyAndVisible];
UITabBarController *tabBarController = [[UITabBarController alloc] init];
NSMutableArray *tabBarItems = [#[] mutableCopy];
// Repeat this for any amount of ViewControllers
UITableViewController *tableViewController = [UITableViewController alloc] initWithStyle:UITableViewStylePlain];
UINavigationController *navController = [UINavigationController alloc] initWithRootViewController:tableViewController];
[tabBarItems addObject:navController];
tabBarController.viewControllers = tabBarItems;
self.window.rootViewController = tabBarController;
return YES;
}

Resources