I want to stack a UIViewController Board on top of a UIViewController Menu in order to create a Facebook-like side menu. This menu should contain a UITextView.
So far I can drag the Board View side ways and the Menu appears underneath it. Great. But there's an issue with the UITextView inside the menu. When I click it the app crashes with a BAD_EXC... exception. It seems like an issue with the UITextView Delegate.
Here's how I currently set it.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
//Board
board = [[BoardViewController alloc] init];
[self.window setRootViewController:board];
//Menu
MenuViewController* menu = [[MenuViewController alloc]init];
menu.textView.delegate = menu;
[self.window addSubview:menu.view];
//Window
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
Note: when I set the textView delegate to board and implement the delegate methods there it works but that really seems to be the wrong place to implement the menu textview methods to me. The delegate of the menu should be in the menu class itself.
How to set the menu's textView delegate correctly to the menu class?
//Menu
MenuViewController* menu = [[MenuViewController alloc]init];
menu.textView.delegate = menu;
[self.window addSubview:menu.view];
And then, poof: the menu view controller isn't referenced again and is deallocated by ARC. The view is retained by the window so it will look like everything is ok until the text view tries to send its delegate message to an object which was long since deallocated. This is the cause of your EXC_BAD_ACCESS crash.
The simple, slack solution would be to define a property in your app delegate for the menu view controller.
#property (nonatomic, strong) MenuViewController * menu;
and then store the menu there
//MenuViewController* menu = [[MenuViewController alloc]init];
//becomes
self.menu = [[MenuViewController alloc]init];
The proper solution and the one I recommend is that you look up UIViewController containment and implement your own custom view controller container that looks after this special arrangement of view controllers.
Just to briefly outline: You would have a subclass of UIViewController with two properties, one for the board view controller and one for the menu view controller. It would have a scroll view and would be responsible for the sliding action and any communication that needed to be passed from the board to the menu and vis versa. This container would also be responsible for loading the board and the menu view controllers and inserting their views into the correct places in its own view. If the board view controller needs to be swapped out for another board then the container would be responsible for this also.
You would usually set that by dragging the delegate connection from the textField to the view controller in interface builder, or programmatically in viewDidLoad of the menu view controller. You're right in that your app delegate shouldn't have anything to do with this process.
Related
So I'm trying to convert a single view application into a tabbed application. My use case is this - in one of the view controllers I want to push a new view controller and still have the tab underneath.
I'm currently doing this -
[self.tabBarController setViewControllers:#[self.searchViewController, self.loginViewController]];
[self.searchViewController presentViewController:self.searchViewController.detailController animated:YES completion:nil];
However, this makes the tab across the bottom disappear.
What should I do?
presentViewController is a "modal" presentation - the presented view controller takes over the entire screen. If you want to remain within the tab but move between view controllers, the root view controller in the tab should be a UINavigationController. You can then push/pop view controllers onto that.
There are two primary methods for view navigation, the first is a presentation which displays a view from the bottom that slides up, and the second is a push which displays a view from the right that slides in from the side.
In most cases, the view I am going to display and what action kicked off the navigation determine which method I will use. For example, if I have a table view that has a list of music albums and I want to search for a song by a particular artist, to see the songs within that album I want to PUSH the view controller, i.e. slide to the right. This gives me the built-in (and intuitive) ability to go back via the automatically added back button on the navigation bar in case the song I was looking for wasn't in the album I selected.
If perhaps I wanted to present the user with the ability to edit the album details, such as renaming the album, this is a totally different type of action, and I would want to PRESENT such a view modally, i.e. from the bottom.
The major distinction between the two is where are you going and what are you doing. If the next view you are going to show is something that does one action and you are then returned back to the original view, presenting modally from the bottom is conventional. If you are doing to be potentially navigation further and further into subsections and will be coming back and forth between said subsections, like Artist->Album->Song etc., you are going to want to push the view from the side, just like the default music app in iOS does.
This is an example starter project I created that demonstrates an easy way to make this work the way you likely want. I create instances of the different view controllers I want to be contained in the tabBarController, which are associated with the tabs, and then "wrap" them each with their own navigation controller before adding them to the tabBar via the .items property. This way each view controller has its own navigation hierarchy and within each you'll be able to call [self.navigationController pushViewController:] or [self.navigationController presentViewController] to keep the navigation 'within' the views and separate from the tabBar itself.
#import "AppDelegate.h"
#import "TabBarViewController.h"
#import "InfoViewController.h"
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
InfoViewController *firstVC = [[InfoViewController alloc] init];
firstVC.title = #"First";
firstVC.view.backgroundColor = [UIColor redColor];
UINavigationController *firstNC = [[UINavigationController alloc] initWithRootViewController:firstVC];
InfoViewController *secondVC = [[InfoViewController alloc] init];
secondVC.title = #"Second";
secondVC.view.backgroundColor = [UIColor blueColor];
UINavigationController *secondNC = [[UINavigationController alloc] initWithRootViewController:secondVC];
TabBarViewController *tabBarVC = [[TabBarViewController alloc] init];
tabBarVC.viewControllers = #[firstNC, secondNC];
self.window.rootViewController = tabBarVC;
[self.window makeKeyAndVisible];
return YES;
}
That resulted in the following:
Hope that helps!
I created and loaded a UISplitViewController in an existing ViewController by writing the following code in the viewDidLoad method:
LeftPanelViewController *leftPanel = [[LeftPanelViewController alloc] initWithNibName:#"LeftPanelViewController" bundle:nil];
FirstViewController *firstView = [[FirstViewController alloc] initWithNibName:#"FirstViewController_iPad" bundle:nil];
self.navigationController = [[UINavigationController alloc] initWithRootViewController:firstView];
UISplitViewController *splitController = [[UISplitViewController alloc] init];
splitController.viewControllers = [NSArray arrayWithObjects:leftPanel, self.navigationController, nil];
[self.view addSubview:splitController.view];
[self addChildViewController:splitController];
[splitController didMoveToParentViewController:self];
Everything is fine except for the fact that the splitController is not being drawn in the borders of the iPad, there's a space between the top of the screen and the top of the view. Even when I rotate the iPad the left panel is also having the same problem.
SplitViewController doesn't have a xib file, and when I change the added view for another that has, everything is correctly displayed.
Any ideas of what may cause this problem?
Notes:
Compiling and running the project in the simulator multiple times causes the SplitViewController to be displayed sometimes without any problems and others with spaces between any of the edges of the screen and the view. Running in the iPad causes always the same problem.
First of all.. why do you implement a container view controller? I guess you just want to present the splitViewController on its own, right? Than don't add the view yourself.
Instead correctly set it as your rootViewController on your window (preferably in applicationDidFinishLaunching).
self.window.rootViewController = splitViewController;
Container View Controller are not needed in standard cases. So you should never need to use the following methods:
addChildViewController:
removeFromParentViewController
willMoveToParentViewController:
didMoveToParentViewController:
Check the documentation of UIViewController.
If you really wanted to implement a Container View Controller, than you need to take care of the layout yourself. So you need to position / size the view of the other controller yourself. Depending on if you use AutoLayout or autoresizing, you need to set correct constraints/flags.
I am using iOS 5.1 and I need to create a Custom TabBar for my app. When app will be launched, the control will be pushed to the Custom TabbBar Controller class. Here is what I am doing. I have created class derived from UITabBarController and I used XIB also. In the XIB, I can see the UIView Objetc containing the Tab Bar and when I am adding more Tabs in that tabBar, it is not reflecting when I run the App and I can see only One Black TabBar without any title and color. I remember earlier we use the MainWindow.xib to add the TabBarController and for each TabBar Item we add the Navigation Controller so that every tab has its own Navigation controller but I am not getting how it can be done if I have used the XIB derived from UiTabBarController Class.Please let me know if I am unclear at any point. I will give more clarification. All I want to know why the changes on the TabBar of the XIB are not reflecting in the app?
The lower image showing the XIB and App so you can see that I have added the Four Tabs in the UITabBar but in the app there is only one Black Bar.
From the Apple Documentation about UITabBarController:
This class is not intended for subclassing. Instead, you use instances of it as-is to present an interface that allows the user to choose between different modes of operation
You can't subclass it. If the original UITabBarController doesn't have the features you are looking for, your only choice is to create a new custom tab bar controller without subclassing the original one.
EDIT: not sure if you want to do this. Are you simply trying to load an UITabBarController from a nib? If so, subclassing is not necessary. It would be better to use storyboards ;-) but you can do it even with nib.
You have to simply do this:
1- add an empty xib to the project and call it TabBar (only xib. No source files)
2- put an UITabBarController inside that nib. No other components, only that UITabBarController
3- put this code in the AppDelegate application didFinishLaunchingWithOptions method:
// this code should be already in that method if you have used the empty application model
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// this is the new code to add. You are programmatically loading an instance of UITabBarController from the nib named TabBar.xib
UITabBarController *tb = (UITabBarController *)[[[NSBundle mainBundle]loadNibNamed:#"TabBar" owner:self options:nil] objectAtIndex:0];
// here you are assigning the tb as the root viewcontroller
self.window.rootViewController = tb;
// this code is standard in this method
// Override point for customization after application launch.
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
Then you can customize your UITabBarController and it should load correctly.
Take care: if you add other objects inside the nib, you must change the line
UITabBarController *tb = (UITabBarController *)[[[NSBundle mainBundle]loadNibNamed:#"TabBar" owner:self options:nil] objectAtIndex:0];
with a loop and some check to see if you are really loading the UITabBarController and not other components. Now I'm simply loading the first object in the nib, without taking care if it's a UITabBarController or an UIPotato ;-)
I have a split-view app that allows a user to select and display a thumbnail of a chosen image. I have placed a UIButton in the detailViewController using Interface Builder. When this button is pressed, I would like to have it change to a full screen view of the image. I have set up a new View Controller, called FullViewController and thought I had everything connected. The problem is that the navigation controller is null. I adjusted the AppDelegate.m to the following:
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after app launch.
// Set the split view controller as the window's root view controller and display.
self.window.rootViewController = self.splitViewController;
UINavigationController *nvcontrol =[[UINavigationController alloc] initWithRootViewController:fullViewController];
[window addSubview:nvcontrol.view];
[self.window makeKeyAndVisible];
return YES;
}
This is the function in the DetailViewController.m which is called when the button is pressed. The navigation controller comes up null in here.
//Function called when button is pressed - should bring up full screen view
- (IBAction) pressFullViewButtonFunction: (id) sender{
//viewLabel.text = #"Full View";
if (fullViewController == nil){
FullViewController *fullViewController = [[FullViewController alloc] initWithNibName:#"FullViewController" bundle:[NSBundle mainBundle]];
NSLog(#"fullViewController is %#", fullViewController);
self.fullViewController = fullViewController;
}
NSLog(#"self.navigationController is %#",self.navigationController);//this is null
[self.navigationController pushViewController:self.fullViewController animated:YES];
}
I'm not sure how to fix this. I've tried adding in the couple lines in the AppDelegate, but when it runs, the table in the root view doesn't show up and it no longer properly switches between portrait and landscape views.
I have the rest of the code readily available if that would help clarify. Just let me know!
Thanks.
From the code you post it is not possible to identify the problem, but two common reasons for self.navigationController to be nil are:
you did not push the object behind self on to the navigation controller in the first place; indeed it seems so, since the navigation controller is added as a subview of the split view controller; possibly you mean the opposite... not sure...
(sub-case of 1) you showed the object behind self using presentViewControllerModally.
When I say "the object behind self" I mean the instance of the class where pressFullViewButtonFunction is defined.
If you need more help, post the code where you push your controllers on to the navigation controller...
On a side note, if you do:
UINavigationController *nvcontrol =[[UINavigationController alloc] initWithRootViewController:fullViewController];
and nvcontrol is not an ivar, then you have a leak.
Hope this helps...
My application starts off with nothing but a UIWindow. I programmatically add a view controller to self.window in application:didFinishLaunchingWithOptions:.
myViewController = [[UIViewController alloc] init:...];
...
[self.window addSubview:myViewController.view];
[self.window makeKeyAndVisible];
At the same time i kick off a background process:
[NSThread detachNewThreadSelector:#selector(startupOperations) toTarget:self withObject:nil];
The startupOperations look something like this:
NSAutoreleasePool *threadPool = [[NSAutoreleasePool alloc] init];
// Load data
...
// When your done, call method on the main thread
[self performSelectorOnMainThread:#selector(showMainViewController) withObject:nil waitUntilDone:false];
// Release autorelease pool
[threadPool release];
showMainViewController removes myViewController, creates a UITabBarController and sets it as the window's main view:
[self.myViewController.view removeFromSuperview];
self.myViewController = nil;
tabBarController = [[UITabBarController alloc] init];
...
[self.window addSubview:tabBarController.view];
[self.window makeKeyAndVisible];
Questions:
All the view controllers are returning YES for shouldAutorotateToInterfaceOrientation:. Rotation works fine for myViewController but as soon as the tabBarController is made visible, rotation stops working and interface appears in Portrait. What's the reason behind this behavior?
Also, in iOS 4.x, UIWindow has rootViewController property. What's the role of this property? The new templates use rootViewController instead of [self.window addSubview:...]. Why is that?
Pretty strange. I tried and simulate your "view flow" in a simple tab bar based project and autorotation effectively works after removing the initial controller and adding the tab bar controller's view as a subview.
The only condition I found where it did not work is when self.window did contain a second subview that I did not remove. Could you check at the moment when you execute
[self.window addSubview:tabBarController.view];
what is self.window.subview content?
If that does not help, could you share in your question how you initialize the UITabBarController and UITabBar?
As to your second question, as you say rootViewController is the root controller for all the views that belong to the window:
The root view controller provides the content view of the window. Assigning a view controller to this property (either programmatically or using Interface Builder) installs the view controller’s view as the content view of the window. If the window has an existing view hierarchy, the old views are removed before the new ones are installed.
(Source)
You can also use that, but take care of assigning it already in applicationDidFinishLaunching, otherwise, if you "manually" add a subview and later change this property, it will not remove the subview you explicitly added.