How to add 2nd UITableView programmatically from within a UINavigationController - iphone

I realize this is a popular topic, and I've searched through many posts here but haven't found anything that has helped my issue. I'm a beginner, for what it's worth (as you'll see by my question :-)
My app has a tab bar with 3 items. The first loads a UINavigationController that is intended to have 3 "screens" to drill-down through (first: UITableView, second: filtered UITableView, third: UIView). I cannot, for the life of me, figure out how to show the UITableView on the 2nd screen, programmatically.
I'm overriding - (void)loadView since I'm not using IB. At different times, I've tried things like:
- (void)loadView
{
[super loadView];
// first option (thought for sure this would work)
[[self view] addSubview:secondTableView];
// another...
[self tableView:secondTableView];
// another...
[[[[self navigationController] topLevelController] view] addSubview:secondTableController];
}
I do have the table view setup properly with it's delegate and datasource, I just can't figure out how to show the damn thing. The 2nd controller is also inheriting from UITableViewController. Additionally, I don't know how you can say "fit this table view within the navigation title and the tab bar menu". I'm using CGRectMake() currently to guess the sizes, but it seems like there should be a better way (maybe that's why you use IB :-). Either way, that's secondary to even getting something to show up in the first place.
Thanks in advance for any suggestions.

If you're inheriting from UITableViewController your view should be a UITableView not a plain UIView, then when you want to create a view programmatically you should allocate it, initialize it, set its properties and assign it as your controller's view, I haven't tried [super loadView] before but it's creating the tableView for you and assigning dataSource and delegate to self, so you don't have to do that. So it should go like this
- (void)loadView {
UITableView *tempPointer = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 320, 480) andStyle:UITableViewStylePlain];
self.view = tempPointer; // controller retains view property so we should release it
[tempPointer release];
// If you want your view to be resized automatically (to fit)
self.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
self.tableView.dataSource = self;
self.tableView.delegate = self;
//...
}
or
- (void)loadView {
[super loadView];
self.tableView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
secondTableView = self.tableView; // or change your code to cope with self.tableView instead of secondTableView...
//...
}
Clear out some uncertainties about view hierarchy creation, retaining etc in the documentation about both UIViewController and UITableViewController if you haven't already.

have you tried:
[Self.navigationController pushViewController:secondTableView];
Hope this helps, if not could you give a little more describing the situation.

Related

How can I create one UIViewController with two views that display one or the other depending on button clicked

I'm quite new to iOS development and I am stuck. Currently I am using one tab controller to switch between two view controllers (list and map view). This made it easier to use storyboard to configure the look of the two views.
Now the requirements have changed and the app needs to have one view controller with a segmented control that on click, displays either the list or the map view. In order to do this, I would need to make one view controller that can display list/map view.
I understand how the segmented controller part works, but I'm just stuck on how I can go about having two views with one or the other displayed in the same area.
How can I go about having two views in one view controller (if possible, utilizing storyboard)?
Thanks in advance!
You should not have two main views in a single view controller, instead you need to create one view controller per view that you want to show. However you can certainly have multiple subviews in a single view controller, which may be what works for you.
There are a number of approaches to solve this the problem, the correct approach would be to create a container UIViewController, and add as its childs the 2 viewcontrollers you want to show, them simply set the view to the view controller you want to display, but that would probably be overly complicated since you mention you are new to iOS development.
Therefore an easy solution (not sure if you can implement this in storyboard - since I don't like it), would be to have a single view controller, with the tabs, and 2 subviews of the main view, then you can simply switch between views by doing something like this:
[self.view addSubview:view1];
//to switch
[view1 removeFromSuperview];
[self.view addSubView:view2];
alternatively, you do not really need to remove it from superview but just hide it, and then use bringSubViewToFront to show the view that you need.
If you want to use the other approach I would recommend looking for this video the WWDC 2011 video titled "Implementing UIViewController Containment". This other question should be useful to: UISegmented control with 2 views
Hope that helps.
Using storyboard api you can switch between 2 viewControllers
- (void)viewDidLoad {
[super viewDidLoad];
UIViewController *viewController = [self viewControllerForSegmentIndex:self.typeSegmentedControl.selectedSegmentIndex];
[self addChildViewController:viewController];
viewController.view.frame = self.contentView.bounds;
[self.contentView addSubview:viewController.view];
self.currentViewController = viewController;
}
- (UIViewController *)viewControllerForSegmentIndex:(NSInteger)index {
UIViewController *viewController;
switch (index) {
case 0:
viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"FirstViewController"];
break;
case 1:
viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"SecondViewController"];
break;
}
return viewController;
}
- (IBAction)segmentChanged:(UISegmentedControl *)sender {
UIViewController *viewController = [self viewControllerForSegmentIndex:sender.selectedSegmentIndex];
[self addChildViewController:viewController];
[self transitionFromViewController:self.currentViewController toViewController:viewController duration:0.0 options:UIViewAnimationOptionTransitionNone animations:^{
[self.currentViewController.view removeFromSuperview];
viewController.view.frame = self.contentView.bounds;
[self.contentView addSubview:viewController.view];
} completion:^(BOOL finished) {
[viewController didMoveToParentViewController:self];
[self.currentViewController removeFromParentViewController];
self.currentViewController = viewController ;
}];
self.navigationItem.title = viewController.title;
}
This is in reference to iOS tutorial by Raywenderlich. Hope this helps
With Storyboard it is possible in this way.
Create UIViewController with UISegmentControl and UITableView+UITableViewCell added to it.
Now you want to add MKMapView as well, hoverer, if you simply try to place the MapView on the ViewController, it will be added as new TableView cell, which is not what we want.
That's why you should not do it so. Instead, MapView has to be added to Storyboard's List of ViewControllers
Adjust the size and origin of MapView to be the same as TableView ones.
Now, setHidden to YES for either TableView of MapView, create and synthesize outlets for them. Then in Segment control Value Changed method implement switching:
- (IBAction)switchView:(id)sender {
self.theTableView.hidden = !self.theTableView.hidden;
self.theMapView.hidden = !self.theMapView.hidden;
if (!self.theTableView.hidden) {
[self.theTableView reloadData];
}
}

global ADBannerView in iPhone app

Is it possible with a standard UINavigationController-rooted app, to have a single ADBannerView visible at the bottom of the screen, below the view hierarchy? That is, without modifying each view-controller/view that can be pushed to the root UINavigationController, can I have a global ADBannerView be visible?
I'm not sure how to set this up, either in IB or in code. Help?
I see similar questions with vague answers. I'm looking for a concrete example.
EDIT: The better way to do this in iOS5+ is likely to use view controller containment. That is, make a root controller that contains your ad and application controller (nav, tab, etc.).
I figured out a way to do this. Here is what I did:
For my first attempt I created a new view controller called AdBannerController. For its view I created a full-screen view and two subviews. The first subview (contentView) is for regular content, the second is the AdBannerView. I used an instance of this view controller as the view controller associated with the app window ( [window addSubview: adBannerController.view] ). Then I added my UINavigationController.view as a subview of adBannerController.view: [adBannerController.contentView addSubview: navigationController.view].
This mostly worked except that viewcontrollers pushed to the UINavigationController never got their will/did-load/unload methods called. Shucks. I read in a few places that this is a symptom of the UINavigationController view not being a direct descendant of the app window.
For my second attempt I took the same AdBannerController and derived it from UINavigationController. This time, I did the following in loadView:
- (void)loadView
{
[super loadView];
_contentView = [self.view retain];
self.view = [[[UIView alloc] initWithFrame: _contentView.frame] autorelease];
[self.view addSubview: _contentView];
_adView = [[ADBannerView alloc] initWithFrame: CGRectMake(0, _contentView.bounds.size.height, 320, 50)];
_adView.currentContentSizeIdentifier = ADBannerContentSizeIdentifier320x50;
_adView.delegate = self;
[self.view addSubview: _adView];
/* for visual debugging of view layout
[[_mainView layer] setCornerRadius: 6.0];
[[_mainView layer] setMasksToBounds: YES];
[[_mainView layer] setBorderWidth: 1.5];
[[_mainView layer] setBorderColor: [[UIColor grayColor] CGColor]];
*/
}
Notice what happens - I let the superclass UINavigationController construct its regular "content" view, but I swap it out and replace it with my own view which is a container for both the content and ad views.
This works pretty well. I'm also using three20 and there were a few things required to make this work with that setup, but not too bad.
I hope this helps someone!
In Apple's dev sample code the iAdSuite project contents projects that have this done for you. Highly recommended.
In my root view controller (w/ ADBannerViewDelegate) I setup my banner by adding it to the nav controller view, which keeps it on top at all times:
banner = [[ADBannerView alloc] init];
banner.delegate = self;
banner.frame = CGRectMake(0.0, 430.0, banner.frame.size.width, banner.frame.size.height);
[self.navigationController.view addSubview:banner];
Note you will have to comment out layoutAnimated in delegate method bannerViewDidLoadAd as it will try to move the ad view up:
- (void)bannerViewDidLoadAd:(ADBannerView *)banner
{
//[self layoutAnimated:YES];
}
I adapted the approach suggested in the iAdSuite given here
http://developer.apple.com/library/ios/#samplecode/iAdSuite/Introduction/Intro.html
I downloaded the code and focused on the 'tab' example. I copied over the BannerViewController.h/.m as is into my project.
I created all my views in the usual way with the storyboard approach. However, in my AppDelegate class I then accessed the already built tab bar - containing all the storyboard built viewControllers.
The AppDelegate class implements the TabBarControllerDelegate protocol:
#interfaceAppDelegate : UIResponder <UITabBarControllerDelegate, UIApplicationDelegate>
The AppDelegate implementation didFinishLaunchingWithOptions method grabs the pre-built tabBar, setting its delegate to self (e.g. the AppDelegate class).
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:
// ----------------------------------------------------------
// Set the TabBarController delegate to be 'self'
// ----------------------------------------------------------
_tabBarController = (UITabBarController*)self.window.rootViewController;
// tabController.selectedIndex = [defaults integerForKey:kOptionLastTabSelectedKey];
_tabBarController.delegate = self;
// update tab bar per iAdSuite approach
[self updateiAd];
I then built a new set of controllers per the iAdSuite approach and reset the tab bar with these new tab bar items.
-(void)updateiAd {
NSArray* viewControllers = [_tabBarController viewControllers];
NSMutableArray*newViewControllers = [[NSMutableArray alloc] init];
BannerViewController*bvc=NULL;
for(UIViewController * vc in viewControllers) {
bvc = [[BannerViewController alloc] initWithContentViewController:vc];
[newViewControllers addObject:bvc];
}
// set the new view controllers, replacing the original set
[_tabBarController setViewControllers:newViewControllers];
}
This approach puts the same 'ad' at the bottom of each view, exactly as needed. I also had to set the view title in the viewDidLoad method of each custom viewController (somehow, setting it on the bar item didn't seem to work not did setting the image; the later may reflect an issue with my images however).
My original configuration was
TabViewController
NavController1 NavController2 NavController3 ...
| | |
CustomViewController1 CustomViewController2 CustomViewController3
My final configuration is now
TabViewController
NavController1 NavController2 NavController3 ...
| | |
iAdView1 iAdView2 iAdView3
| | |
CustomViewController1 CustomViewController2 CustomViewController3
In terms of view lifecycle, I should add that only the NavControllers are in existence at the time the updateiAd method is called.
The individual CustomViewControllers1/2/3/etc get created after the call completes.

UITextField subview of UITableViewCell to become first responder?

I have a core data application which uses a navigation controller to drill down to a detail view and then if you edit one of the rows of data in the detail view you get taken to an Edit View for the that single line, like in Apples CoreDataBooks example (except CoreDataBooks only uses a UITextField on its own, not one which is a subview of UITableViewCell like mine)!
The edit view is a UITableviewController which creates its table with a single section single row and a UITextfield in the cell, programatically.
What I want to happen is when you select a row to edit and the edit view is pushed onto the nav stack and the edit view is animated moving across the screen, I want the textfield to be selected as firstResponder so that the keyboard is already showing as the view moves across the screen to take position. Like in the Contacts app or in the CoreDataBooks App.
I currently have the following code in my app which causes the view to load and then you see the keyboard appear (which isn't what I want, I want the keyboard to already be there)
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[theTextField becomeFirstResponder];
}
You can't put this in -viewWillAppear as the textfield hasn't been created yet so theTextField is nil. In the CoreDataBooks App where they achieve what i want they load their view from a nib so they use the same code but in -viewWillAppear as the textfield has already been created!
Is there anyway of getting around this without creating a nib, I want to keep the implementation programatic to enable greater flexibility.
Many Thanks
After speaking with the Apple Dev Support Team, I have an answer!
What you need to do is to create an offscreen UITextField in -(void)loadView; and then set it as first responder then on the viewDidLoad method you can set the UITextField in the UITableViewCell to be first responder. Heres some example code (remember I'm doing this in a UITableViewController so I am creating the tableview as well!
- (void)loadView
{
[super loadView];
//Set the view up.
UIView *theView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.view = theView;
[theView release];
//Create an negatively sized or offscreen textfield
UITextField *hiddenField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, -10, -10)];
hiddenTextField = hiddenField;
[self.view addSubview:hiddenTextField];
[hiddenField release];
//Create the tableview
UITableView *theTableView = [[UITableView alloc] initWithFrame:[[UIScreen mainScreen] bounds] style:UITableViewStyleGrouped];
theTableView.delegate = self;
theTableView.dataSource = self;
[self.view addSubview:theTableView];
[theTableView release];
//Set the hiddenTextField to become first responder
[hiddenTextField becomeFirstResponder];
//Background for a grouped tableview
self.view.backgroundColor = [UIColor groupTableViewBackgroundColor];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
//Now the the UITableViewCells UITextField has loaded you can set that as first responder
[theTextField becomeFirstResponder];
}
I hope this helps anyone stuck in the same position as me!
If anyone else can see a better way to do this please say.
Try do it in viewDidAppear method, works for me.
I think the obvious solution is to create the textfield in the init method of the view controller. That is usually where you configure the view because a view controller does require a populated view property.
Then you can set the textfield as first responder in viewWillAppear and the keyboard should be visible as the view slides in.
have you tried using the uinavigationcontroller delegate methods?:
navigationController:willShowViewController:animated:

UIViewController: set self.view to my view or add my view as subview?

I have a question about UIViewController's subview, I created a UIView subclass MainView, which has the exact size of the screen, I wonder which is a better way of adding MainView, consider the following factors:
1 As MainView has same size as the whole screen, the MainView itself may have subviews, but there is no views at the save level as MainView(ie I don't need to add other subviews to self.view).
2 If I use self.view = mainView, do I put the code in loadView(as the viewDidLoad method means the view(self.view) is already loaded)? I see the loadView method is commented out by default, if I add the code to this method, what other code do I need to put together(e.g. initialize other aspects of the application)?
3 If I add mainView via [self addSubview:mainView], are there actually two off screen buffer? One for self.view, one for mainView, both has same size as the screen and one is layered on top of the other(so it wastes memory)?
Thanks a lot!
I'm not sure I completely understand what you're asking, but I'll try to answer a few of the questions you have.
First of all, if you have multiple UIViews on the screen they are all loaded into memory. You have to do -removeFromSuperview and release them to get the memory back.
You can assign your UIView as the UIViewController's view. For example:
MainView *mainView = [[MainView alloc] initWithFrame:CGRectMake(320.0, 480.0)];
self.view = mainView;
[mainView release]; //since the .view property is a retained property
in that case, you have have the view's initialization code in the -init method. Just redefine it like:
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
//initializations
}
return self;
}
You must implement loadView if you did initialize your view controller with a NIB.
UIViewController takes care of sizing its "main" view appropriately. This is all you need to do:
- (void)loadView
{
UIView* mainView = [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
self.view = mainView;
}
I'd solve all of this by doing it in a xib! If you create a UIView in your xib, you can then change it's class (when you select the UIView there should be a text field in the Class Identity section of the Identity inspector* - type 'MainView' here!)
Then, create your view controller by calling
myViewController = [[MainViewController alloc] initWithNibName:#"MyNibName" bundle:nil];
That should solve your problems; it's the main subview of your view controller (directly accessable from self.view) and you don't need to worry about memory usage, there's only one view :)
Sam
NB * Click tools -> Identity Inspector. I didn't know it was called this until I had to write this answer!
Yes, the first code-snippet shown above is the "standard" approach, AFAIK, when not using (evil!) NIB files -- i.e. when alloc'ing your view in-code, via loadView.
Note it seems one can also get away with the following, instead of hard-coding the screen-rect size:
UIView *myView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
self.view = myView;
[myView release];
Note you definitely want to do the [myView release] call since, indeed, as pointed out above, self.view (for UIView) is a retained property.
Cheers, -dk
Perhaps the most important thing to do is make sure you have the following:
self.view.userInteractionEnabled = YES;
While it might not be required all of the time, it fixes the issue where self.view is unresponsive. This issue pops up occasionally.

iPhone Dev - Lazy loading a Tab Bar app

How can I make it so when a tab is selected, the current one is unloaded, and the next one is loaded so only one loaded at a time? Or should I not even do this? I know how to do it with a normal UIViewController as the root VC, but not sure with a UITabBarController. Also, is there a way to animate the transition from one tab to the next? Any help? Thanks!!
EDIT: ... If I unload the view controllers, then their icons on the tab bar are gone... maybe I'll just unload their views..
I can answer both questions in one...
You just need a class that acts as the UITabBarController delegate, then implement a method like so:
// Animate tab selections so they fade in and fade out
-(void)tabBarController:(UITabBarController*)tbc didSelectViewController:(UIViewController*)newSelection
{
[UIView beginAnimations:#"TabFadeIn" context:nil];
[UIView setAnimationDuration:0.6];
for( UIViewController* vc in tbc.viewControllers )
vc.view.alpha = (vc==newSelection) ? 1 : 0;
[UIView commitAnimations];
}
Now my code simply makes the tab bars fade in and out, but you could also do work here to unload non-used tabs. Sometimes that is a good idea if some of the tabs will be using a ton of memory.
You cant really manage the UITabBarController unfortunaly so you cant do lazy loading. You can by managining your own TabBar but you said u knew that already,
to manage your own tab bar though all you gotta do is setup a UITabBar with its TabBarItems in a ViewController, then implement the TabBar Delegate protocol, mainly the – tabBar:didSelectItem: method which is called whenever the tabbarItem selection is changed, then based on the item id you can load your new ViewController and release any others
so: Edit: this code goes in your UIViewController
-(void)addTabBar{
NSMutableArray* items=[[NSMutableArray alloc] init];
UITabBarItem *eventsItem= [[UITabBarItem alloc] initWithTitle:#"Events" image:nil tag:0];
UITabBarItem *albumItems=[[UITabBarItem alloc] initWithTitle:#"Album" image:nil tag:1]; //the tag is how you tell what was clicked
[items addObject:homeItem];
[items addObject:albumItems];
//MyTabBar is of type UITabBar
myTabBar=[[UITabBar alloc] initWithFrame:CGRectMake(0,411,320,49)];
[myTabBar setItems:items];
myTabBar.delegate=self; //you gotta implement the UITabBar delegate protocol
[myTabBar setSelectedItem:eventItem]; //set the selected item
[homeItem release];
[eventsItem release];
[albumItems release];
[items release];
[self.view addSubview:myTabBar]
}
then the protocol method would look something like below
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
{
if(item.tag == 0 )
{
//load the ViewController that pertains to this item and release others
}
...etc
}
Lazy loading is not an UITabBarController task. Instead, it is responsability of your viewControllers associated with your Tab.
To release the UIView, associated with each UIViewControllers, every time you change the TabBarItem, you must implement the following method in each UIViewController subclass, associated with your UITabBarController.viewControllers property:
-(void)viewDidDisappear {
[self.view removeFromSuperview];
self.view = nil;
}
Obviously, this will remove the self.view associated with your UIViewController. However, if your code is smart enough, this will remove all the related objects.
For example, suppose that your loadView method is as follow:
-(void)loadView {
UIView *contentVew = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.view = contentView;
…
...
UILabel *aLabel = [[UILabel alloc] initWithFrame:CGRectMake(0,0,320,50)];
…
…
[contentView addSubview:aLabel];
[aLabel release];
…
[contentView release];
}
This means that every object inside the contentView and their memory responsabilities are demanded to the contentView, that is released and attached to the self.view property.
In this scenario, removing the self.view (that's the reference to the contentView) resulting in a domino-style releasing of every object, that's your goal.
Best regards
Not sure why you'd want to do this, the current tab will get unloaded anyway if there's a memory issue involved. That's what -viewWillAppear, -viewDidUnload, etc. are for.
UITabBarController does lazy load all of its view controllers. When a tab is switched out, then it's view is subject to being deallocated in a memory tight situation. It is then recreated when it is chosen the second time. Furthermore, most of your memory hits are in your views and not the view controllers. Hence, don't worry about the memory hit from the view controller. The view is the proze.
If you are running on v3 of the OS, then you can use the -viewDidUnload method to ensure the maximal amount of memory reduction.
Andrew
I'm currently using this to unload inactive view controllers in the tab bar (based on Kendall's answer)
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController: (UIViewController *)viewController {
// reload all inactive view controllers in the tab bar
for (UIViewController *vc in tabBarController.viewControllers) {
if(vc != viewController)
[vc didReceiveMemoryWarning];
}
}