Is there way to share a UIView between a parent and child UIViewController without any noticeable visual glitches when it's added to the child's view as a subview?
I have a UIViewController and its corresponding UIView, considered as a 'masthead', that will be shared between a parent and child UIViewController. (image something like a stock ticker that will be present somewhere on the screen, across all screens in the app)
When the child UIViewController is created and pushed on to view hierarchy (I'm using them with 'UINavigationController'), what I see is its placeholder background area peaking through before the masthead view is added as a subview.
I thought about creating unique masthead view for each screen but most of the app's views will share this masthead. Thus managing content changes across all of them seemed complicated and I'm trying to take the simpler route by having 1 instance.
AppDelegate work:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Other prep work, including setup of self.window
UIViewController *vc = [[ParentViewController alloc] initWithNibName:nil
bundle:[NSBundle mainBundle]];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:vc];
self.window.rootViewController = navController;
}
The Parent UIViewController implementation:
#interface ParentViewController : UIViewController {}
#implementation ParentViewController()
- (void)viewDidLoad
{
// The shared controller and its view has already been created and initialized.
// Adding the masthead to my view
[self.view addSubview:self.mastheadController.view];
[super viewDidLoad];
}
- (void)showChildController
{
DetailViewController *detailController = [[DetailViewController alloc] initWithNibName:nil
bundle:[NSBundle mainBundle]
withMastheadController:self.mastheadController];
[self.navigationController pushViewController:detailController animated:YES];
detailController = nil;
}
Here's Child UIViewController implementation:
#interface DetailViewController : UIViewController {}
#implementation DetailViewController()
- (void)willMoveToParentViewController:(UIViewController *)parent
{
// Since this method is invoked before our viewDidLoad and the
// parent's viewWillDisappear, remove shared view from parent view
// stack.
[self.mastheadController.view removeFromSuperview];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Adding the shared masthead controller view to our view
[self.view addSubview:self.mastheadController.view];
}
The reason I'm using willMoveToParentViewController is because I thought that if I wait until 'viewWillDisappear' gets called for the ParentViewController, it was not enough time for the mastheadView to get inserted into the child view hierarchy without any noticeable effects.
This is my understanding about the order in which the view appear/disappear events happen:
child:willMoveToParentViewController (parent will be null upon dismiss)
child:viewDidLoad
parent:viewWillDisappear
child:viewWillAppear
Any help would be greatly appreciated.
I would approach this differently. Why not add the view to navController's view, and then remove it when you don't want to see it any more?
Related
I'm trying to load a specific UITableViewController as the Detail view of a UISplitViewController when the user selects a cell within the Master view; but having some problems. When i select the cell, it just shows a blank window with out a table view controller.
However if i change the App Delegate to just load the UITableView controller without using the UISplitViewController it works fine, so i know it's a problem with the way i've coded the UISplitViewController.
The view hierachy:
UISplitViewController
-->
UINavigationController
--> UITableViewController (DetailViewController)
UINavigationController
--> UIViewController (ColorViewController)
The user selects a cell in ColorViewController and that should change the DetailViewController.
In AppDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.viewController = [[ViewController alloc] init];
self.window.rootViewController = [self.viewController splitViewController];
[self.window makeKeyAndVisible];
return YES;
}
In ViewController.m:
- (UIViewController *) splitViewController {
// Create the navigation-run root view
ColorViewController *rootVC = [ColorViewController controller];
UINavigationController *rootNav = [[UINavigationController alloc] initWithRootViewController:rootVC];
// Create the navigation-run detail view
DetailViewController *detailVC = [DetailViewController controller];
UINavigationController *detailNav = [[UINavigationController alloc] initWithRootViewController:detailVC];
// Add both to the split view controller
svc = [[UISplitViewController alloc] init];
svc.viewControllers = [NSArray arrayWithObjects: rootNav, detailNav, nil];
svc.delegate = detailVC;
return svc;
}
In ColorViewController.m:
#interface ColorViewController : UITableViewController
...
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UIViewController *controller = (UIViewController *)self.splitViewController.delegate;
TableViewController *tvc = [[TableViewController alloc] init];
[controller.view addSubview:tvc.view];
}
What's the best way to change the Detail view within a UISplitViewController? Should i just add the replacement view to UIViewController like above or is there a better way? Maybe that's the cause of my problem?
Since you have a navigation controller handling the detail side of your split view, your strategy should be to use it to push your new table view controller. When I've done this, I've kept references to both navigation controllers as properties in the app delegate so that I can use one or the other for push operations.
I'm not sure what the real purpose of your ViewController class is but if you move its splitViewController method into the app delegate it would be easier to change the navigation controllers from local variables to properties in a place where they can be easily accessed.
I'm going to answer my own question..
For some reason, in the Master view (ColorViewController), the detail object TableViewController *tvc needs to be declared as an instance variable, not as a variable within a method.
After doing that, it works and displays the table properly.
I don't really understand why though, if anyone would like to try to explain.
Cheers,
I have page-based app. On each page I have 3 uibuttons at the top, uiscrollview with alphabet (uibuttons to sort data in uitable) at the right and uitableview at the center. How to show cell's detail view? If it is necessary to add uinavigationcontroller I can't do this. If I adds it, it disables interaction with my table, buttons and scrollview.
And another question is how to show new data in tableview and scrollview when goes to next page??
I have rootViewController class and DataViewController class.
rootViewController listing:
#interface RootViewController ()
#property (readonly, strong, nonatomic) ModelController *modelController;
#end
#implementation RootViewController
#synthesize pageViewController = _pageViewController;
#synthesize modelController = _modelController;
#synthesize navContr = _navContr;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// Configure the page view controller and add it as a child view controller.
//[self presentModalViewController:navContr animated:YES];
self.pageViewController = [[[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil] autorelease];
self.pageViewController.delegate = self;
DataViewController *startingViewController = [self.modelController viewControllerAtIndex:0 storyboard:self.storyboard];
NSArray *viewControllers = [NSArray arrayWithObject:startingViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:NULL];
self.pageViewController.dataSource = self.modelController;
[self addChildViewController:self.pageViewController];
[self.view addSubview:self.pageViewController.view];
self.navContr = [[UINavigationController alloc] initWithRootViewController:self.pageViewController];
[self.view addSubview:self.navContr.view];
// Set the page view controller's bounds using an inset rect so that self's view is visible around the edges of the pages.
CGRect pageViewRect = self.view.bounds;
self.pageViewController.view.frame = pageViewRect;
[self.pageViewController didMoveToParentViewController:self];
// Add the page view controller's gesture recognizers to the book view controller's view so that the gestures are started more easily.
self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
for (UIGestureRecognizer *recognizer in self.pageViewController.gestureRecognizers){
if ([recognizer isKindOfClass:[UITapGestureRecognizer class]]){
[recognizer setEnabled:NO];
}
}
}
After few manipulations it works but I need help to get it work fine!
So now it looks like this
Next question: how do I remove the brown space at the top???
::UPDATE::
Problem solved. It needs just to set y-axis position of UINavigationController to -20 ;)
i'm not sure if this link about creating navigation-based project may help you.. (http://iosmadesimple.blogspot.com/2012/08/navigation-based-project-doing-it.html)
From that tutorial, there's a class called SampleViewController, subclass of UIViewController. You might want to put a tableView in SampleViewController.xib file. Then in your SampleViewController.h file, add an IBOutlet UITableView* yourTable property and synthesize it. Connect it to your tableView in your .xib file. //Or you may do it programmatically
in your SampleViewController.h, make your interface header look like this.. I think you already know this...
#interface SampleViewController:UIViewController < UITableviewDelegate, UITableViewDatasource >
in your SampleViewcontroller.m, under viewDidLoad method, set the table delegate and datasource to self:
yourTableView.delegate = self;
yourTableView.datasource = self;
Afterwhich, you implement tableView delegate and datasource methods... //you already know those since you were already able to show a tableview ;)
one of these methods is the "tableview:didSelectAtIndexpath:" --> this is the part wherein you can put your code when you click one of the cells.
Let's assume you have the DetailsViewController Class, this is the class you would like to show after clicking a cell and show its details.
DetailsViewController Class must have a variable that will accept the data you would like to show. Let's say, an NSString *detailsMessage; //do the #property and #synthesize thing...
Let's go back to SampleViewController.m file, under tableview:didSelectAtIndexpath: Method:
inside that Method.. put these codes.
DetailsViewController *detailsVC = [[DetailsViewController alloc] init];
detailsVC.detailsMessage = #"The Data you want to pass.";
[self.navigationController pushViewController:detailsVC animated:YES];
I hope this helps. :(
There may be other ways but by far the easiest way is to use a navigation controller. In fact, it is built to do exactly this.
If you don't want the navigationBar then you can hide it in the viewWillAppear function.
[self.navigationController setNavigationBarHidden:YES animated:YES];
Then you can add an other UIViewController to push when the user selects a cell.
Having read your OP again I'm not sure how you are adding your navigationController.
To use a navigationController you create it and load it at start time. You then create your current viewController (the one with the buttons and table etc...) and set this as the rootViewController of the navigationController.
Then you display the navigationController.
Could you explain how you are adding your navigationController as it may help understand what is going wrong.
Thanks
::EDIT::
OK, my assumption was correct.
The way you are using the navigation controller is not how it was intended.
OK, so at the moment your AppDelegate file will have a method Application didFinishLaunching...
It will look something like this...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.viewController = [[OJFViewController alloc] initWithNibName:#"MainViewController" bundle:nil];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}
You should change it so that it is like this...
first add a property to your appDelegate...
#property (nonatomic, strong) UINavigationController *navigationController;
Then change the didFinishLaunchingMethod to this...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.viewController = [[OJFViewController alloc] initWithNibName:#"MainViewController" bundle:nil];
self.navigationController = [[UINavigationController alloc] initWithRootViewController:self.viewController];
self.window.rootViewController = self.navigationController;
[self.window makeKeyAndVisible];
return YES;
}
This will still show you MainViewController but it will now be contained within a navigationController.
Next in your MainViewController function viewWillAppearAnimated add the line...
[self.navigationController setNavigationBarHidden:YES animated:animated];
This will then hide the navigationBar at the top of the view so you still have access to your buttons.
The you need a new ViewController and xib file (for example DetailViewController).
When the user selects a table row you need to then do something like...
DetailViewController *detailView = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:nil];
//pass in details of which row was selected.
[self.navigationController pushViewController:detailView animated:YES];
This will then display your new view and new viewController. You will also need to write a way of passing the data in (set up a property on the DetailViewController).
Hope this helps.
I have a UIViewController called LaunchController that is launched in my iPhone app when the app first opens:
#interface LaunchController : UIViewController<UINavigationControllerDelegate, UIImagePickerControllerDelegate>
Then, when a button is clicked, I push another view controller:
MainController *c = [[MainController alloc] initWithImage:image];
[self presentModalViewController:c animated:NO];
MainController has the following constructor, which I use:
- (id)initWithImage:(UIImage *)img
{
self = [super init];
if (self) {
image = img;
NSLog(#"inited the image");
}
return self;
}
and then it has a viewDidLoad method as follows:
- (void)viewDidLoad
{
NSLog(#"calling view did load");
[super viewDidLoad];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
[self.view addSubview:imageView];
NSLog(#"displaying main controller");
}
When the program runs, I see that the constructor for MainController is called (due to the output of NSLog), however viewDidLoad never gets called, even though I am calling presentModalViewController. Why is this? Why isn't viewDidLoad being called?
I think it is something as followings. When you need the property of view inside UIViewController, it will be loaded with lazy manner.
- (UIView *)view
{
if (_view == nil) {
[self loadView]; //< or, the view is loaded from xib, or something else.
[self viewDidLoad];
}
return _view;
}
After the view initialized, it will call viewDidLoad to inform the UIViewController.
You aren't loading your view controller from a xib file, and from comments you don't have anything in loadView (which is where you would create your view controller's view if you were not using a xib file).
Therefore, your view isn't being loaded, so viewDidLoad is never called.
Typically you would use initWithNibName: to initialise a new view controller, and then set the image after it (so expose the image as a property).
viewDidLoad will be called as soon as your controller's view property is accessed, that is when you display it for the first time or request it (e.g. have some code that calls c.view.
The reason viewDidLoad is not being called is because you aren't loading a view.
In your init method:
self = [super init];
means that you are just creating a naked view from scratch. not loading one from a nib.
try this instead:
self = [super initWithNibName:nil bundle:nil];
If you have a xib or nib file with the same name as the view controller class it should find if. Otherwise, you can just give a nibName that works.
UPDATE:
If you are not using nib files, then the appropriate method is NOT viewDidLoad. You have to implement loadView instead of viewDidLoad.
In your specific case, just put everything that is currently in viewDidLoad into loadView.
I'm trying to use a UINavigationController but I'm uncertain how. Up till now (for about a year), I've been using presentModalViewController and dismissModalViewController to present/dismiss view controllers.
So, this is what I did. My main view controller (the first one that shows on launch) is called MainViewController, and it extends UIViewController.
So I made this launch function in my app delegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
MainViewController *controller = [[MainViewController alloc] init];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:controller];
[self.window addSubview:navigationController.view];
[self.window makeKeyAndVisible];
return YES;
}
And in my MainViewController's viewDidLoad method:
- (void)viewDidLoad {
[super viewDidLoad];
self.title = #"Title";
self.navigationController.navigationBar.tintColor = [Constants barColor];
....more code...
}
But, in my MainViewController, I'd like to present another view controller called SecondViewController, which needs a UINavigationBar with a back arrow button. So do I make SecondViewController extend UIViewController and do the same thing by setting the title and backButton in the viewDidLoad method? And how do I present it? What should I do to accomplish this?
You'll need to set a root view controller up, it's easiest starting from the apple template.
Here's where the magic happens:
UIViewController *controller = [[UIViewController alloc] initWithNibName:#"MyNib" bundle:nil];
[self.navigationController pushViewController:controller animated:YES];
[controller release];
The nav controller does all the work for you (back buttons, titles, animations) - it keeps track!
My workflow is this:
Setup MutableArray in the viewDidLoad, add controllers to it, e.g:
NSMutableArray *array = [[NSMutableArray alloc] init];
MyCustomViewController *customView = [[MyCustomViewController alloc] initWithNibName:#"nib" bundle:#"nil"];
customView.title = #"Second Level";
[array addObject:customView];
self.controllers = array;
Then in your delegate:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSUInteger row = [indexPath row];
UIViewController *childControllerToBe = [controllers objectAtIndex:row];
[self.navigationController pushViewController:childControllerToBe animated:YES];
}
This, along with a lot more can be learnt by reading a decent beginner book such as Beginning iPhone Development
Also, apple docs are always good :)
UINavigationController is a subclass of UIViewController, but unlike UIViewController it’s not usually meant for you to subclass. This is because navigation controller itself is rarely customized beyond the visuals of the nav bar. An instance of UINavigationController can be created either in code or in an XIB file with relative ease.
Please visit "How to add UINavigationController Programmatically"
You should Push it onto the navigation stack.
This Lecture by Stanford's iPhone Course will teach you a lot about Navigation Bars. (It's a quick read)
Basically at the heart of it you need this code:
[self.navigationController pushViewController:SecondView];
You can use PopViewController to go back programmatically, but the Back Button is automatically created.
Here's some source code from the Lecture. It covers exactly what you are having issues with.
Hey everyone, I am new to iPhone development and I'm not understanding the whole UINavigationController and UITabBarController idea. Is one a substitute for the other - how do apps such as Tweetie combine both?
I'd like to have my app have a persistent Tab Bar # the bottom (which seems to be working), but also a Navigation bar at the top which can push/pop views onto the screen without removing the tab bar.
How can I accomplish this?
What should the hierarchy look like in IB as far as my MainWindow.xib with regards to all of these controllers?
What is best practice here?
Thanks very much,
Just wrap the view controller inside the UINavigationController and Place the UINavigationController inside the UITabBar.
This will work fine for you…
Example:
NSMutableArray *tabBarViewControllers = [[NSMutableArray alloc] initWithCapacity:2];
tabBarController = [[UITabBarController alloc] init];
[tabBarController setDelegate:self];
UINavigationController *navigationController = nil;
navigationController = [[UINavigationController alloc] initWithRootViewController:<Your View controller1>];
[tabBarViewControllers addObject:navigationController];
[navigationController release];
navigationController = nil;
navigationController = [[UINavigationController alloc] initWithRootViewController:<Your View controller2>];
[tabBarViewControllers addObject:navigationController];
[navigationController release];
navigationController = nil;
tabBarController = tabBarViewControllers;
[tabBarViewControllers release];
tabBarViewControllers = nil;
Use the wizard for a Tab Bar Application, and set it up as normal. In any tab where you want to add a navigation controller, create it in the XIB using the library. My XIB has:
- File's Owner DescriptiveNameNavViewController
- First Responder
- View UIVIew
- Navigation Controller UINavigationController
- Navigation Bar UINavigationBar
Note that there isn't anything in the view. See viewDidLoad below for where the UINavigationController gets attached to the UIView.
In the header file for the Tab's ViewController (which I've here called DescriptiveNameNavViewController -- there isn't a particular standard for this, but I use [Something]NavViewController to remind me that this ViewController contains a navigation controller with the navigation stack. This is the controller name that I set in the MainWindow.xib that the wizard generates) Set up a UINavigationController * IBOutlet that has the navigation controller in the XIB attached to it:
#interface DescriptiveNameNavViewController : UIViewController {
UINavigationController *navigationController;
}
#property (nonatomic, retain) IBOutlet UINavigationController *navigationController;
#end
In the controller for the DescriptiveNameNavViewController , do something like this:
- (void)viewDidLoad {
[super viewDidLoad];
[[self view] addSubview:[navigationController view]];
DescriptiveNameController *aController = [[[DescriptiveNameController alloc ] initWithNibName:#"DescriptiveNameController" bundle:nil ] autorelease];
aController.title = #"Descriptive Title";
//
// app stuff goes here.
//
[self.navigationController pushViewController:aController animated:YES];
[self.navigationController setDelegate:self];
}
Setting the delegate in the DescriptiveNameNavViewController is super-important, because otherwise you won't get the methods called that you expect in DescriptiveNameViewController instances and anything else you push into the navigation controller's stack.
In DescriptiveNameNavViewController, implement the UINavigationControllerDelegate methods like this:
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if ([viewController respondsToSelector:#selector(viewDidAppear:)]) {
[viewController viewDidAppear:animated];
}
}
And that will cause messages to get propagated to controllers inside the UINavigationController like you expect. It seems like many problems that people encounter are because the viewDidAppear: or other methods aren't getting called on the ViewControllers pushed into the NavigationController.
Anyway, let me know if more detail would help.