Unexpected UIView offset in UIViewController hierarchy with container controllers and UINavigationController - ios5

I have a UINavigationController with a number of child view controllers, some of which are container controllers. The UIView frame layout of the last added child UIViewController (green background) has its parent background (yellow) through when it should not.
How can I layout everything so all of the views fit within the bounds of the screen below the Navigation bar without the weird offset?
UIViewController hierarchy, started in my AppDelegate:
UINavigationController rootViewController:FrontFacadeViewController < UIViewController
view (0,0) dim:(320x460)
=> StandardCoverViewController < UIViewController
added as addSubview of FrontFacadeViewController's view]
view: (0,0) dim:(320x460)
=> ConfigurationViewController < UIViewController
added by pushing onto self.navigationController
view: (0,20) (320x460)
=> GearViewController < UIViewController
added as subview of ConfigurationViewController's view
view: (0,0) (320x416)
Here's how all of the view controllers are created:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
FrontFacadeViewController *frontController = [[FrontFacadeViewController alloc]
initWithBundle:[NSBundle mainBundle]];
_appNavigationController = [[UINavigationController alloc]
initWithRootViewController:frontController];
}
FrontFacadeViewController:
// FrontFacadeViewController determines a particular view controller to present
// based on some business logic.
// Because of this, it adds child UIViewControllers using container controllers.
//
- (void)viewDidLoad {
StandardCoverViewController *controller = [StandardCoverViewController alloc] initWithNibName:nil bundle:self.nibBundle];
[self addChildViewController:controller];
[controller didMoveToParentViewController:self];
[self.view addSubview:controller.view];
[controller didMoveToParentViewController:self];
}
- (IBAction)onButtonPress {
ConfigurationViewController *controller = [[ConfigurationViewController alloc] initWithNibName:nil bundle:self.nibBundle];
[self.navigationController pushViewController:controller animated:YES];
}
ConfigurationViewController: (yellow background)
- (void)viewDidLoad {
[self.view setBackgroundColor:[UIColor yellowColor]];
GearViewController *controller = [[GearViewController alloc] initWithNibName:nil bundle:self.nibBundle];
[self addChildViewController:controller];
[self.view addSubview:controller.view];
[controller didMoveToParentViewController:self];
}
GearViewController: (green background)
- (void)viewDidLoad {
[self.view setBackgroundColor:[UIColor greenColor]];
// nothing special
}
Am I doing this all wrong?

Related

How to handle UITapGestureRecognizer in a subview?

I have a viewcontroller (vc1) in which I added another viewcontroller (vc2) as a subview. An UITapGestureRecognizer is triggered when you tap on a view (tappingView) contained in the subview (vc2). I would like to handle this tap inside the subview controller (vc2), and not from the first viewcontroller (vc1)... but it keeps crashing when I click on my view.
Here is the hierarchy sum up :
-vc1
--vc2
---tappingView
And here is a sample of code :
ViewController1 (viewDidLoad):
- (void)viewDidLoad
{
ViewController2 *vc2 = [[ViewController2 alloc] init];
[[self view] addSubview:[vc2 view]];
}
ViewController2 (viewDidLoad):
- (void)viewDidLoad
{
UIView *tappingView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 60.0, 80.0)];
[[self view] addSubview:tappingView];
UITapGestureRecognizer *tapGestureRecognize = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(singleTapGestureRecognizer:)];
[tappingView addGestureRecognizer:tapGestureRecognize];
}
ViewController2 (singleTapeGestureRecognizer:) :
- (void)singleTapGestureRecognizer:(id)sender {
NSLog(#"Tap gesture");
}
When I click in my view (tappingView), I keep getting a crash (BAD_ACCESS).
Anyone have an idea why ?
You shouldn't just add another view controller's view to your view -- you should make that controller a childViewController of vc1. Change the viewDidLoad in vc1 to this:
- (void)viewDidLoad {
ViewController2 *vc2 = [[ViewController2 alloc] init];
[self addChildViewController:vc2];
[vc2 didMoveToParentViewController:self];
[[self view] addSubview:[vc2 view]];
}
It's not clear why you're doing it this way in the first place. Why not present vc2 as a modal view controller instead?
In this block
- (void)viewDidLoad
{
ViewController2 *vc2 = [[ViewController2 alloc] init];
[[self view] addSubview:[vc2 view]];
}
your ViewController2 is not retained (addSubview only retains the view, not the view controller).
To fix it, declare vc2 as a strong property, instead of a variable.
self.vc2 = [[ViewController2 alloc] init];
Or better, as someone suggested, add vc2 as a child view controller.
Targets of UITapGestureRecognizer take the UIGestureRecognizer argument, not id. I.e.:
- (void)singleTapGestureRecognizer:(UIGestureRecognizer *)sender {
NSLog(#"Tap gesture");
}
Further, you're setting frames in viewDidLoad, which you should do in viewWillAppear: instead.
use below code
- (void)singleTapGestureRecognizer:(UIGestureRecognizer *)sender
instead of
- (void)singleTapGestureRecognizer:(id)sender.
As it must have UIGestureREcognizer instead of id.`

Nav Bar button loading random view before loading appropriate view

I have a back button on my answer view controller (a view controller that displays answers) if the user hits the back button I created it switches to a view that has the title of "Back" and just an empty tableview, before switching back to my main view of where all the questions to be answered are displayed. Why is this happening? Its a very brief thing, but definitely noticeable!
UINavigationBar *navBar = [[UINavigationBar alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 48)];
navBar.delegate = self;
UINavigationItem *backItem = [[UINavigationItem alloc] initWithTitle:#"Back"];
[navBar pushNavigationItem:backItem animated:NO];
UINavigationItem *topItem = [[UINavigationItem alloc] initWithTitle:#"Question"];
[navBar pushNavigationItem:topItem animated:NO];
topItem.leftBarButtonItem = nil;
[self.view addSubview:navBar];
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
ViewController *controller = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
controller.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentViewController:controller animated:YES completion:nil];
return true;
}
- (void)navigationBar:(UINavigationBar *)navigationBar didPopItem:(UINavigationItem *)item
{
ViewController *controller = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
controller.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentViewController:controller animated:YES completion:nil];
}
You're creating your own UINavigationBar and UINavigationItem instances and you probably shouldn't be. The situation you describe is exactly what a UINavigationController is for. When using a UINavigationController it creates the UINavigationBar and each UIViewController that you show on screen (push into the navigation controller) has its own UINavigationItem (with the title taken from the title of the view controller).
The reason you get an empty 'view' titled "Back" is that you're creating it:
UINavigationItem *backItem = [[UINavigationItem alloc] initWithTitle:#"Back"];
[navBar pushNavigationItem:backItem animated:NO];
Dispense with all of this, create a UINavigationController and make your question view controller it's root view controller, then add the navigation controller to the screen. Then when a question is answered, push the answer view controller:
[self.navigationController pushViewController:answerViewController animated:YES];

Accessing TabBarController on Button Press

I have programatically created a TabBarController with views etc. Now i want to show this TabBarController on Button Press. How do i do that? Currently i am presenting it modally but it doesn't work - throws sigtrap errors.
This is my code for the TabBarController
#implementation TabBarViewController
- (void) loadView
{
HomeViewController * homeViewController = [[HomeViewController alloc]initWithNibName:#"HomeViewController" bundle:nil];
UITabBarController *tabBarController = [[UITabBarController alloc] init];
tabBarController.view.frame = CGRectMake(0, 0, 320, 460);
// Set each tab to show an appropriate view controller
[tabBarController setViewControllers:[NSArray arrayWithObjects:homeViewController, homeViewController, nil]];
[self.view addSubview:tabBarController.view];
[homeViewController release];
[tabBarController release];
}
This is my code for accessing this tabBarController from a Button Press event from my mainViewController -
- (IBAction)quickBrowse:(UIButton *)sender
{
TabBarViewController * tabBarController = [[TabBarViewController alloc]init];
[self presentModalViewController:tabBarController animated:YES];
[tabBarController release];
}
You should only override the method loadView if you are not using IB and if you want to create yours view manually. And when you do that you must assign your root view to the view property of UIViewController.
I believe in your case you don't need to override this method, you can use the viewDidLoad method to create your UITabBarController and store it in a variable, so when the event gets called all you need to do is pass the variable to the method presentModalViewController:animated:
Your final code would look like this:
- (void) viewDidLoad
{
[super viewDidLoad];
HomeViewController * homeViewController = [[HomeViewController alloc]initWithNibName:#"HomeViewController" bundle:nil];
// you can't pass the same view controller to more than one position in UITabBarController
HomeViewController * homeViewController2 = [[HomeViewController alloc]initWithNibName:#"HomeViewController" bundle:nil];
// local variable
self.modalTabBarController = [[UITabBarController alloc] init];
// Set each tab to show an appropriate view controller
[self.modalTabBarController setViewControllers:[NSArray arrayWithObjects:homeViewController, homeViewController2, nil]];
}
- (void)viewDidUnload
{
self.modalTabBarController = nil;
[super viewDidUnload];
}
- (IBAction)quickBrowse:(UIButton *)sender
{
[self presentModalViewController:self.modalTabBarController animated:YES];
}

Why I can't release the navigation controller of the view I added to the window

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[self setWindow:[[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease]];
// Override point for customization after application launch.
//self.window.backgroundColor = [UIColor whiteColor];
// Create First view
FirstViewController *firstViewCTL = [[FirstViewController alloc] init];
// Create UINavigationController
UINavigationController *navCTL = [[UINavigationController alloc] init];
[[navCTL navigationBar] setBarStyle:UIBarStyleBlack];
[navCTL pushViewController:firstViewCTL animated:NO];
[firstViewCTL release];
[[self window] addSubview:navCTL.view];
[navCTL release];
[[self window] makeKeyAndVisible];
return YES;
}
I understand that adding subview (addSubview:) will retain the view to be aded. But why I can't now release my navigation controller (navCTL) that own the views that has already been retained
-addSubview: retains the view, not the view controller.
You can use UIWindow's rootViewController property (iOS 4 and higher) to retain the view controller, it also saves you adding the view as a subview yourself.
...
[window setRootViewController:navCTL];
[navCTL release];
...

add two navigation controller to one Tab bar Item

I want 2 navigation Controller to be attached to one Tab bar item.
Basically the idea is to have 2 views on a single tab Item and there should be a navigation bar to push and pop the screens. (same like settings application in iPad).
edited======
It will look like on left hand side there is a View with its own navigation Controller and on right hand side there is another View with its own navigation controller(or some other UI) to achieve the Push Pop stuff.
I know how to attach 1 navigation Controller to one Tab bar Item.
Edited For ModalView Issue:-
by implementing conmulligan code everything works property. But if I try to display some ModalViewController in lansdscape mode and when I try to close this ModalView the FirstViewController navigationbar becomes portrait(along with its View) and SecondViewController NavigationBar remains landscape(at it should be). This happens only in Device not in simulator.
Below is my Code of presenting the ModalViewController.
ModalTableViewController *modalTableViewController = [[ModalTableViewController alloc]initWithNibName:#"ModalTableViewController" bundle:[NSBundle mainBundle]];
UINavigationController *localNavigationViewController = [[UINavigationController alloc] initWithRootViewController:modalTableViewController];
localNavigationViewController.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentModalViewController:localNavigationViewController animated:YES];
[modalTableViewController release];
[localNavigationViewController release];
For Dismising the ModalViewCntroller I do it as below:-
[self dismissModalViewControllerAnimated:YES];
Waiting for your replies.
One way would be to create a UIViewController subclass that contains two navigation controllers and add that to the UITabBarController. Here's how you'd create and layout the navigation controllers in the UIViewController's -viewDidLoad method:
FirstViewController *firstViewController = [[FirstViewController alloc] init];
UINavigationController *firstNavigationController = [[UINavigationController alloc] initWithRootViewController:firstViewController];
[firstViewController release];
SecondViewController *secondViewController = [[SecondViewController alloc] init];
UINavigationController *secondNavigationController = [[UINavigationController alloc] initWithRootViewController:secondViewController];
[secondViewController release];
firstNavigationController.view.frame = CGRectMake(0.f, 0.f, 320.f, self.view.frame.size.height);
firstNavigationController.view.autoresizingMask = UIViewAutoresizingFlexibleHeight;
secondNavigationController.view.frame = CGRectMake(321.f, 0.f, self.view.frame.size.width - 321.f, self.view.frame.size.height);
secondNavigationController.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleRightMargin;
[self.view addSubview:firstNavigationController.view];
[self.view addSubview:secondNavigationController.view];
This is more or less how the UISplitViewController works under the hood.
Edit: you might need to add the following code to make sure it lays out properly:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[firstNavigationController viewWillAppear:animated];
[secondNavigationController viewWillAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[firstNavigationController viewWillDisappear:animated];
[secondNavigationController viewWillDisappear:animated];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[firstNavigationController viewDidAppear:animated];
[secondNavigationController viewDidAppear:animated];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[firstNavigationController viewDidDisappear:animated];
[secondNavigationController viewDidDisappear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}