Implementing my own navigation controller? - iphone

I have a tab bar app. Under one of the tabs i want a uisegmentedControl in the top navigation view, that controls what view is currently displayed. This is dead easy if i just exchange the view, but i want to do it in a more organized and generic way, by using one uiviewcontroller for each view and exchanging them in the most optimzed way.
i guess step one would be to know exactly what a tabbar controller sends to a navigation controller/view controller when a tab is changed, and work it out from there.
Can any one point me in the right direction?

Some time ago I stumbled upon SegmentsController which I found in this blog entry from red artisan.
I used it in conjunction with a UITabBarController, but without knowing I did it wrong. Not wrong as in "it crashs" or "it doesn't do what i want" but wrong in the sense that I have to forward each UIViewController call (like viewDidAppear, receivedMemoryWarning etc) to the child viewControllers. The app with the wrong code is still in the app store and I never received a complain about it.
But I played around a while and figured out how to use it right. It's a bit of a hassle but imho it's absolutely worth it.
I'll show you the correct version that I have right now, I'm creating the UITabBarController in Interface Builder so I have to change the tab in code. Which introduces another piece of mess, and maybe there is room for improvements. But right now I'm satisfied with this solution.
NSMutableArray *items = [self.tabBarController.viewControllers mutableCopy]; // tabs from tabbar configured in IB
// The two child vc that will appear in the segment control
SomeViewController_iPhone *tvcs = [[[SomeViewController_iPhone alloc] initWithNibName:#"SomeView_iPhone" bundle:nil] autorelease];
SomeOtherViewController_iPhone *tvct = [[[SomeOtherViewController_iPhone alloc] initWithNibName:#"SomeOtherView_iPhone" bundle:nil] autorelease];
NSArray *viewControllers1 = [NSArray arrayWithObjects:tvcs, tvct, nil];
// the nav controller acts as a wrapper around the child viewcontrollers
UINavigationController *navController1 = [[[UINavigationController alloc] init] autorelease];
navController1.tabBarItem.title = NSLocalizedString(#"FirstTab", nil);
navController1.tabBarItem.image = [UIImage imageNamed:#"tabImage1.png"];
navController1.navigationBar.tintColor = [UIColor navBarTintColor];
firstTabSegmentsController = [[SegmentsController alloc] initWithNavigationController:navController1 viewControllers:viewControllers1];
// uses a NSArray category that basically creates a NSArray that has the title properties of the vc in viewControllers1
firstTabSegmentedController = [[UISegmentedControl alloc] initWithItems:[viewControllers1 arrayByPerformingSelector:#selector(title)]];
firstTabSegmentedController.frame = CGRectMake(0, 0, 222, 30);
firstTabSegmentedController.segmentedControlStyle = UISegmentedControlStyleBar;
firstTabSegmentedController.selectedSegmentIndex = 0;
[firstTabSegmentsController indexDidChangeForSegmentedControl:firstTabSegmentedController];
[firstTabSegmentedController addTarget:firstTabSegmentsController action:#selector(indexDidChangeForSegmentedControl:) forControlEvents:UIControlEventValueChanged];
// replace first tab from interface builder with this
[items replaceObjectAtIndex:0 withObject:navController1];
as you see it needs a bit of setup, but in my opinion this solution is better than anything else I've tried throughout the time. I hope I de-NDAed the code correctly.
Edit: Uploaded a sample project: BeautifulColors.zip

Just exchanging the views and keeping up with the current view's viewController is the best way to implement a UISegmentedControl in this regard.
Note: by exchanging the views i mean adding a subview to the current view and removing the old one.
You might be interested in the method below, which is implemented by the UITabBarControllerDelegate
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController;

Related

In Three20, can I not use custom table cells in a searchViewController?

I'm implementing a TTTableViewController with a searchViewController part, and I'm running into issues when displaying the results of the search in the search's TableView.
If I add TTTableTextItems to the datasource items property, it works fine, but if I try to add a TTTableCaptionItem or a TTTableImageItem or any custom cell I've created, the rendering is messed up.
Can anyone confirm that it's possible to use custom table cells in a searchViewController and perhaps point me in the right direction for how to make it work?
I've attached 3 screen shot to show what's working and what's not working.
No. 1: Works with this code:
[self.items addObject:[TTTableTextItem itemWithText:[item objectForKey:#"title"]]];
No 2. Breaks the layout with this code:
[self.items addObject:[TTTableCaptionItem itemWithText:[item objectForKey:#"title"]
caption:[item objectForKey:#"excerpt"]]];
No. 3 Also breaks the layout with this code:
[self.items addObject:[TTTableImageItem itemWithText:[item objectForKey:#"title"]
imageURL:[item objectForKey:#"thumbnail"]]];
OK, I figured it out. The key is setting the the variableHeightRows property to YES in the TTTableViewController that you're using as the search controller. Below is the code I've used.
TTTableViewController* searchController = [[[TTTableViewController alloc] init] autorelease];
searchController.dataSource = [[[MagazineSearchDataSource alloc] init] autorelease];
searchController.variableHeightRows = YES;
self.searchViewController = searchController;
self.tableView.tableHeaderView = _searchController.searchBar;

updating value of modal view variable

I'm trying to make a modal view which displays the champion of my app.
there's a NSMutableString variable called champ in modal view,
which is supposed to be updated by returnChamp function in main view.
the champ string is correctly set in main view,
but in modal view, the champ value appears as (null).
In fact, it seems it doesn't even go into the returnChamp function.
so apparently something wrong with my calling or implementing returnChamp,
but I have another function that does the similar, and that works fine.
could anyone please help me?
-(void) mainView{
.....
champ = [[currentPlayers objectAtIndex:playerIndex] retain];
NSLog(#"%#",champ);
modalWinner = [[winner alloc] init];
modalWinner.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:modalWinner animated:YES];
}
- (NSMutableString *) returnChamp{
NSLog(#"returnChamp");
return champ;
}
//in modalWinner
-(void) modalView{
..............
champName = [[NSMutableString alloc] init];
NSLog(#"%#", [(MainViewController *)self.parentViewController returnChamp]);
champName = [(MainViewController *)self.parentViewController returnChamp];
UIImage *champImage = [UIImage imageNamed:champName];
}
self.parentViewController is probably not actually a reference to your object. For some reason, it seems that the framework always insists on setting a UINavigationController as self.parentViewController - even for modals, and to the extent that it will create one if there isn't already one. This is probably going unnoticed because you're casting it to your MainViewController type.
You'll need to find a different way of making your original object available to be communicated with, or perhaps pass the appropriate value to the newly-instantiated controller before you present it.
For example, if you add a champName property to the modal class, you can do:
modalWinner = [[ModalWinnerViewController alloc] init];
modalWinner.champName = myValue; /* Set value before presenting controller */
[self presentModalViewController:modalWinner animated:YES];
There will probably be some code needed to update the UI with this value. The viewWillAppear method of the modal view controller is a good place for this as it is called by the framework immediately before the view is presented.
Note that this property-based approach could be used to keep a reference to your intended parent object, as well. And see here for a different approach to solving a similar problem.

Reload / Refresh tab bar items in a ViewController ?

I am trying to change images of my tabbar in a ViewController, but to display the new images, I must click on each tab bar item.
for (CustomTabBarItem *myItem in self.tabBarController.tabBar.items){
myItem.enabled = YES;
myItem.badgeValue = #"1";
UIImage *myImage = [[UIImage alloc] initWithContentsOfFile:[[DesignManager sharedManager] getPathOfFile:#"test.png"]];
*myItem.imageSelect= *myImage; // change images of each item. don't appear if I dont click on the item
}
Anyone know How can I can display directly these images?
Thanks
You need to replace the old tab bar item with a new one. You can't update the image dynamically otherwise.
The easiest way to do this is to set the tabBarItem property of the view-controller represented by a given tab. If you wanted to do this from within that view controller, just write:
self.tabBarItem = [[UITabBarItem alloc] initWithTitle: #"title" image: myImage: tag: nil];
Or, you could do this from somewhere else, say your app delegate:
UIViewController* vc = [tabBarController.viewControllers objectAtIndex: 3];
vc.tabBarItem = [[UITabBarItem alloc] initWithTitle: #"title" image: myImage: tag: nil];
I know this is an old question. I ran into the same problem when I need to update the badge value from another active tab. Creating another UITabBarItem will solve your current problem but causes potential memory leak when this code is called many times. Plus, when other view controllers access the tab, they do not have reference to newly created UITabBarItem. My trick is
vc.tabBarItem = vc.tabBarItem;
It works for me.

Remove UITabBarItem

How can I remove a UITabBarItem from a UITabBar?
I haven't tried anything, because I haven't found anything from Google searches or the documentation for UITabBar, UITabBarController, or UITabBarItem.
Thanks in advance! :)
UITabBar has an NSArray collection of items. Since the items property is an NSArray and not an NSMutableArray, you'd have to construct a new NSArray from the existing one devoid of the object you want to remove, then set the items property to the new array.
/* suppose we have a UITabBar *myBar, and an int index idx */
NSMutableArray *modifyMe = [[myBar items] mutableCopy];
[modifyMe removeObjectAtIndex:idx];
NSArray *newItems = [[NSArray alloc] initWithArray:modifyMe];
[myBar setItems:newItems animated:true];
Mike Caron's advice will throw an exception if you intend to modify a tabBar that belongs to a controller.
In iOS 3.0 and later, you should not
attempt to use the methods and
properties of this class to modify the
tab bar when it is associated with a
tab bar controller object. Modifying
the tab bar in this way results in the
throwing of an exception. Instead, any
modifications to the tab bar or its
items should occur through the tab bar
controller interface. You may still
directly modify a tab bar object that
is not associated with a tab bar
controller.
In this case self.tabBarItem=nil will remove it.
NOTE: this appears to not work in iOS 11. It was still good in iOS 10.
This is a moderately horrible answer, in my opinion, in part because it's subverting the human interface guidelines, but all the same it seems to work cleanly:
UITabBar *oldbar = self.tabBarController.tabBar;
UITabBar *newbar = [[UITabBar alloc] initWithFrame:CGRectMake(0,0,oldbar.frame.size.width,oldbar.frame.size.height)];
NSMutableArray *olditems = [[oldbar items] mutableCopy];
[olditems removeObjectAtIndex:0];
NSArray *newitems = [[NSArray alloc] initWithArray:olditems];
[newbar setItems:newitems animated:false];
[oldbar addSubview:newbar];
That layers it cleanly on top of the old tabbar, and it maintains its functionality.

Act on click of a button on the Nav Bar for moreNavigationController -- Can't pushviewcontroller

Okay, here is my issue: My app has a display of categories in the tab bar at the bottom of the iPhoneOS screen. This only allows 5 categories before it presents the MORE button. I have over 25 (please do not answer this by saying: "Rethink your application...etc" -- that was rudely said before. They are food, drink, etc categories and cannot be changed). I want to allow the user to put their favorites on the home page. The Apple moreNavigationController editing system only allows 20 tab bar items to be rearranged due to space constraints on the editing page. This is not enough so i need to implement my own Editing screen. I set the rightBarButtonItem to nil and created my own. Using NSLog, i can see the "click" happens when clicking the EDIT button, but I cannot push using pushViewController. Nothing happens. I think it has something to do with the navigationController I am addressing...but i am not sure. ps: This all happens in my App Delegate which DOES act as both UITabBarControllerDelegate & UINavigationControllerDelegate.
I tried to do the following:
- ( void )navigationController:( UINavigationController * )navigationController_local willShowViewController:( UIViewController * )viewController_local animated:( BOOL )animated
{
UIViewController * currentController = navigationController_local.visibleViewController;
UIViewController * nextController = viewController_local;
// Do whatever here.
NSLog(#"Nav contoller willShowViewController fired\n'%#'\n'%#'\nThere are currently: %d views on the stack\n",currentController,nextController,[self.navigationController.viewControllers count]);
if ( [nextController isKindOfClass:NSClassFromString(#"UIMoreListController")])
{
UINavigationBar *morenavbar = navigationController_local.navigationBar;
UINavigationItem *morenavitem = morenavbar.topItem;
morenavitem.rightBarButtonItem = nil;
NSLog(#"Is a UIMoreListController\n");
UIBarButtonItem *editTabBarButton = [[UIBarButtonItem alloc]
initWithTitle:#"Edit"
style:UIBarButtonItemStylePlain
target:self
action:#selector(editTabBar:)];
morenavitem.rightBarButtonItem = editTabBarButton;
[editTabBarButton release];
}
}
This works to place an EDIT button at the top right of the screen -- mimicking Apple's look and feel... but when that button is clicked, you cannot exit the darn moreNavigationController.
I have tried many things. UIAlerts work, etc...but pushing (or popping -- even popping to root view) a view controller on the stack does not.
- (void) editTabBar:(id)sender {
NSLog(#"clicked edit tabbar\n");
NSLog(#"Total count of controllers: %d\n",[self.navigationController.viewControllers count]);
TabBarViewController *tabBarViewController2 = [[TabBarViewController alloc] initWithNibName:#"TabBarView" bundle:nil];
tabBarViewController2.navigationItem.title=#"Edit Tab Bar";
[self.navigationController pushViewController:tabBarViewController2 animated:YES];
[tabBarViewController2 release];
NSLog(#"finished edit tabbar\n");
}
If you click the edit button on the moreNavigationController's display page, you get the log entries like expected AND (this is strange) the views on the stack climbs -- but no page change occurs. I marked it down to not using the correct navigation controller...but I am lost on how to find which one TO use.
this is a weird one too. In the edit function if i just do this:
- (void) editTabBar:(id)sender {
self.tabBarController.selectedIndex = 0;
}
It DOES take me home (to tabbarcontroller 0)
BUT doing this:
- (void) editTabBar:(id)sender {
[self.navigationController popToRootViewControllerAnimated:YES];
}
does not work.
Does the moreNavigationController have some special quality that screws with the rest of the system?
I would try reimplementing the whole "More" functionality from scratch. In other words, store the four home tabs in your user defaults and add a dummy fifth tab that switches to your own complete reimplementation of the more view controller stack.
You could even write a lightweight subclass of UITabBarController that handled this for you.
UITabBarController is evil, so I wouldn't be at all surprised if MoreController had some special properties, too.
I have had success intercepting the More Controller in shouldSelectViewController to change the data source; you may be able to find some workaround there.
PS I am inclined to agree that you could consider redesigning your app so that you didn't need an unlimited number of viewControllers attached to the tab bar just to select categories; you might have better luck using a tool bar with a single, scrollable, custom view in it. If that's really the best way of picking categories for your app, of course.