iphone app - detect which tab bar item was pressed - objective-c

i have a tab bar based application, with more than 5 tab bar items - so i get 4 of them directly showing in the view and the rest available by selecting the "More" tab. When a tab bar item is pressed, i want to detect which one was it.
So, in the
- (void)tabBarController:(UITabBarController *)tabBarCtrl didSelectViewController:(UIViewController *)viewController method, i use tabBarCtrl.selectedViewController.title to get the item's title.
This works for the tabs visible in the view -that is the 4 first and the "More" tab- but does not work for the rest of my tab bar items which are shown in the list after pressing the "More" tab.
I can see that the didSelectViewController method is not even called when selecting a tab from the "More" list.
How can i detect any of them when pressed?
Thank you in advance.

How to get title of UITabBarItem in the More section?
- (void)tabBarController:(UITabBarController *)tabBarController
didSelectViewController:(UIViewController *)viewController
{
NSLog(#"controller class: %#", NSStringFromClass([viewController class]));
NSLog(#"controller title: %#", viewController.title);
if (viewController == tabBarController.moreNavigationController)
{
tabBarController.moreNavigationController.delegate = self;
}
}

You can access the index of selected item by using following code in your UIViewController. It will always return yout tab's index.
self.tabBarController.selectedIndex;
So if you have e.g. 6 items you can go to the "More..." tab, select your "5th" item and selectedIndex will be 4. If you go to the More tab and select 6th item, it'll return 5.
EDIT: If you want to check current position of some UITabBarItem you can do this:
Firstly, in your XIB file you should edit the tag property of each tab, so that 1st tab will have tag = 100, 2nd - 200, 3rd - 300, etc.
Then in ViewController add this code:
UIViewController *selectedVC = [self.tabBarController.viewControllers objectAtIndex:self.tabBarController.selectedIndex];
int selectedItemTag = selectedVC.tabItem.tag;
Then you can determine what viewController is it by using selectedItemTag variable. In this case, you can determine selectedIndex by doint this: selectedIndex = (selectedItemTag-100)/100.
The tag properties are not changed when customizing your UITabBar, so you can trust them :)

You can detect when a tab has been pressed using the UITabBarDelegate methods: http://developer.apple.com/library/ios/#documentation/uikit/reference/UITabBarDelegate_Protocol/Reference/Reference.html#//apple_ref/occ/intf/UITabBarDelegate
You can make your UITabBarController class be the delegate and add the method in the implementation:
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
{
NSLog(#"tab selected: %#", item.title);
}

1. So if you are using a UITabBarController you can make the class implement the UITabBarControllerDelegate and set your UITabBarController delegate to the class that needs to be notified when the TabBar selected item changes, then add the delegate method to your class:
-(void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
Inside this method you can use the UITabBarController selectedIndex property to know which is the current index selected:
-(void)tabBarController:(UITabBarController *)tabBarController didSelectViewController: (UIViewController *)viewController
{
NSLog(#"Selected index: %d", tabBarController.selectedIndex);
}
2. If you are not using just the UITabBar you can follow the answer here by Ken Pespisa and iCoder in this post Ken Pespisa and iCoder in this post.

Since you add tags to your EVERY UITabBarItem (even those with index 5 and more).
You can track what tab was selected using following code:
//MARK: - UITabBarControllerDelegate
func tabBarController(tabBarController: UITabBarController, didSelectViewController viewController: UIViewController) {
if viewController == tabBarController.moreNavigationController {
tabBarController.moreNavigationController.delegate = self
} else {
setSelectedTabBarOption()
}
}
//MARK: - UINavigationControllerDelegate
func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
setSelectedTabBarOption()
}
private func setSelectedTabBarOption() {
if let viewControllers = viewControllers {
let selectedController: UIViewController? = viewControllers.count > selectedIndex ? viewControllers[selectedIndex] : nil
if let tag = selectedController?.tabBarItem.tag {
//do whatever with your tag
}
}
}

- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
NSLog(#"Selected index: %d", tabBarController.selectedIndex);
if (viewController == tabBarController.moreNavigationController)
{
tabBarController.moreNavigationController.delegate = self;
}
NSUInteger selectedIndex = tabBarController.selectedIndex;
switch (selectedIndex) {
case 0:
NSLog(#"click tabitem %u",self.tabBarController.selectedIndex);
break;
case 1:
NSLog(#"click me again!! %u",self.tabBarController.selectedIndex);
break;
default:
break;
}
}

If you're using a tab bar controller, you should avoid knowing about the mapping between tab items and view controllers -- that's the job of the tab bar controller. If you're trying to use a tab bar for something else, then you should use UITabBar directly and not use UITabBarController. If you use UITabBar, you can set your own object as the tab bar's delegate, and the delegate will then get messages whenever the selected item changes.

Related

Trying to understand TabBarDelegate

In one of my ViewControllers I have the following code:
- (void)viewDidLoad
{
UITabBarController *tabBarController = (UITabBarController*)[UIApplication sharedApplication].keyWindow.rootViewController ;
[tabBarController setDelegate:self];
}
and :
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController: (UIViewController*)viewController {
NSLog(#"Yup!");
}
Whenever I switch tabs in my multi-tab setup, the console spits out
Yup
just as expected.
However, when I add
UITabBarController *tabController = (UITabBarController*)self.window.rootViewController;
tabController.selectedIndex = 1;
to my AppDelegate.m's
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:
the 'Yup' doesn't show anymore.
How come?
didSelectViewController will call when you select/change tabs from the app itself, it will not call when you set the selectedIndex programmatically
tabController.selectedIndex = 1; , will mostly useful when you want to set default tab or want to change the selectedIndex programmatically
From apple doc:
- (void)tabBarController:(UITabBarController *)tabBarController
didSelectViewController:(UIViewController *)viewController
it is called only in response to user taps in the tab bar and is not
called when your code changes the tab bar contents programmatically.
You can try calling that method manually like this:
- (void) selectedItemWithIndex:(int)value {
tabbar.selectedIndex = value;
[self tabBarController:tabbar didSelectViewController:tabbar.viewControllers.firstObject];//place you vc here by array or manually
}
Ref: https://stackoverflow.com/a/30700712/4557505

Contextual menu on only certain items in a "Source List"

I have a window with a Source List (NSOutlineView). My source list has just two levels. Level one is header and level two is data. I want to have a contextual menu on some of the data cells. Not all.
First, I try to attach a menu on the table cell view who represents the data cell -> nothing happens.
Second, I attach a menu on the Outline View in IB -> the contextual menu opens on each cells (header and data). I search for stopping the opening of the menu, but I don't find anything.
Do you have some ideas ?
Thank you
OS X 10.8.2 Lion, Xcode 4.5.2, SDK 10.8
If you subclass NSOutlineView, you can override menuForEvent: to return a menu only if the user clicked on the correct row. Here's an example:
- (NSMenu *)menuForEvent:(NSEvent *)event;
{
//The event has the mouse location in window space; convert it to our (the outline view's) space so we can find which row the user clicked on.
NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
NSInteger row = [self rowAtPoint:point];
//If the user did not click on a row, or is not exactly one level down from the top level of hierarchy, return nil—that is, no menu.
if ( row == -1 || [self levelForRow:row] != 1 )
return nil;
//Create and populate a menu.
NSMenu *menu = [[NSMenu alloc] init];
NSMenuItem *delete = [menu addItemWithTitle:NSLocalizedString( #"Delete", #"" ) action:#selector(delete:) keyEquivalent:#""];
[self selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
//Set the Delete menu item's represented object to the clicked-on item. If the user chooses this item, we'll retrieve its represented object so we know what to delete.
[delete setRepresentedObject:[self itemAtRow:row]];
return menu;
}
This assumes we're compiling with ARC, so you don't need to autorelease the menu object being created.
This extension + subclass (both NSOutlineView and NSTableView) does the sensible thing of seeing whether a menu is attached to a cell view or row view. Just a general, reusable subclass!
Set the menu on the cell view in outlineView:viewForTableColumn:item: – menu is a NSResponder property.
(Below is in Swift)
// An extension lets us both subclass NSTableView and NSOutlineView with the same functionality
extension NSTableView {
// Find a cell view, or a row view, that has a menu. (e.g. NSResponder’s menu: NSMenu?)
func burnt_menuForEventFromCellOrRowViews(event: NSEvent) -> NSMenu? {
let point = convertPoint(event.locationInWindow, fromView: nil)
let row = rowAtPoint(point)
if row != -1 {
if let rowView = rowViewAtRow(row, makeIfNecessary: true) as? NSTableRowView {
let column = columnAtPoint(point)
if column != -1 {
if let cellView = rowView.viewAtColumn(column) as? NSTableCellView {
if let cellMenu = cellView.menuForEvent(event) {
return cellMenu
}
}
}
if let rowMenu = rowView.menuForEvent(event) {
return rowMenu
}
}
}
return nil
}
}
class OutlineView: NSOutlineView {
override func menuForEvent(event: NSEvent) -> NSMenu? {
// Because of weird NSTableView/NSOutlineView behaviour, must set receiver’s menu otherwise the target cannot be found
self.menu = burnt_menuForEventFromCellOrRowViews(event)
return super.menuForEvent(event)
}
}
class TableView: NSTableView {
override func menuForEvent(event: NSEvent) -> NSMenu? {
// Because of weird NSTableView/NSOutlineView behaviour, must set receiver’s menu otherwise the target cannot be found
self.menu = burnt_menuForEventFromCellOrRowViews(event)
return super.menuForEvent(event)
}
}
It's not clear from your question whether your outline is view based or cell based. That's important.
If you're view based, then your view instances can implement
- (NSMenu *)menuForEvent:(NSEvent *)theEvent
and return the menu appropriate to that item -- or nil f you don't want a menu at all.
If you're cell based, or if you don't want to handle this in the view class for some reason, you'll need to subclass NSOutlineView and implement - (NSMenu *)menuForEvent:(NSEvent *)theEvent there. Again, you'll figure out which cell is hit or active, and decide from that what menu you want.
- (void)rightMouseDown:(NSEvent *)event
An NSView will not pass this to the next view, This method looks to see that the current class has a menuForEvent:, if it does then it is called. If it does not then it is finished and nothing else will happen. This is why you will not see an NSTableCellView respond to a menuForEvent: because the table view swallows the rightMouseDown:.
You may subclass the tableview and handle the rightMouseDown: event and call the NSTableCellView's rightMouseDown: and handle displaying your menu that you have constructed in your storyboard and hooked up to your NSTableViewCell.
Here is my solution in a subclassed NSTableView:
- (void)rightMouseDown:(NSEvent *)event
{
for (NSTableRowView *rowView in self.subviews) {
for (NSView *tableCellView in [rowView subviews]) {
if (tableCellView) {
NSPoint eventPoint = [event locationInWindow];
// NSLog(#"Window Point: %#", NSStringFromPoint(eventPoint));
eventPoint = [self convertPoint:eventPoint toView:nil];
eventPoint = [self convertPoint:eventPoint toView:self];
// NSLog(#"Table View Point: %#", NSStringFromPoint(eventPoint));
NSRect newRect = [tableCellView convertRect:[tableCellView bounds] toView:self];
// NSLog(#"Rect: %#", NSStringFromRect(newRect));
BOOL rightMouseDownInTableCellView = [tableCellView mouse:eventPoint inRect:newRect];
// NSLog(#"Mouse in view: %hhd", mouseInView);
if (rightMouseDownInTableCellView) {
if (tableCellView) {
// Lets be safe and make sure that the object is going to respond.
if ([tableCellView respondsToSelector:#selector(rightMouseDown:)]) {
[tableCellView rightMouseDown:event];
}
}
}
}
}
}
}
This will find where the right mouse event occurred, check to see if we have the correct view and pass the rightMouseDown: to that view.
Please let me know if this solution works for you.

Reload ViewController by clicking on TabBarItem

I'm kinda desperate right now :/
I have a Tab Bar Controller with 4 Items. In the 4. Tab I included a webView which shows a list of pdf's. If I open a PDF in the webView there is no way to go back to the main webView with the links. Is there a way by re-clicking the 4. TabBar to reload the View? If I change from the 3. to the 4. tabbar it works (viewWillAppear).
Someone told me, that the following method should work:
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController{
if ([viewController isKindOfClass:[UIColor class]]) {
//Try this if you're pushing the webView in another ViewController
[viewController.navigationController popToRootViewControllerAnimated:YES];
//or access to your webView and call goBack();
}
}
but actually I have no idea in which file I should insert that method. (See print Screen)
Thanks a LOT in advance for your help guys!
Subclass UITabBarController
1.1. Cmd+N and create a new instance of NSObject class, and name it TabBarController
1.2. in TabBarController.h replace NSObject so that it reads #interface TabBarController : UITabBarController <UITabBarControllerDelegate>
1.3. in TabBarController.m add this:
- (id) init
{
self = [super init];
if (self)
{
self.delegate = self;
}
return self;
}
1.4. and this
- (void)tabBarController:(UITabBarController *)tabBarController
didSelectViewController:(UIViewController *)viewController
{
// Is this the view controller type you are interested in?
if ([viewController isKindOfClass:[MehrViewController class]])
{
// call appropriate method on the class, e.g. updateView or reloadView
[(MehrViewController *) viewController updateView];
}
}
1.5. In IB, Inspection, change the class of Tab Bar Controller to your TabBarController (instead of UITabBarController)
1.6. You also need to include MehrViewController.h in TabBarController.m
Edit
in MehrViewController.m (as you posted in your question, assuming it has a webView)
// An example of implementing reloadView
- (void)reloadView {
[self.webView reload];
}

Changing navigationController.delegate causes to bad access

This may sound a newbie question, however I'm new t iOS dev.
I've a view pushed in navigationController, let say it is the 3rd pushed view.
In that view I set self.navigationController.delegate = self;. I've changed delegate because I need to handle case when user goes to previous view i.e. pops from current view.
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if ([[viewController class] isEqual:[MainViewController class]]) {
...
}
}
It works OK, but when I pop the current view and press navigation back button again (i.e. switching to first pushed view) I'm getting bad access error.
So what I'm missing ?
What is the correct way to handle navigation back button press ?
It's because navigation controller sends a message to popped and deallocated view controller, you have to set the delegate each time you do the popping and pushing. Also add self.navigationController.delegate = nil; to dealloc method of your viewController.
Place below in the Viewcontroller in which you assigning self.navigationController.delegate = self
-(void) viewWillDisappear:(BOOL) animated
{
[super viewWillDisappear:animated];
if ([self isMovingFromParentViewController])
{
if (self.navigationController.delegate == self)
{
self.navigationController.delegate = nil;
}
}
}

Objective C: How to disable user interaction to all of tab bars except one?

As what the title suggests, I would like to be able to lock all my tab bars except for one. And only after the user completes an action will I enable all the rest of the tab bars. How can I do that?
I haven't tried it, but according to the docs, you can return NO from the tabBarController:shouldSelectViewController: delegate.
[UPDATE] I just tried that out of curiosity - it seems to work fine. Create a new project from the "Tab bar application" template and then go to the -viewDidLoad of your FirstViewController. Add this line:
[self.tabBarController setDelegate:self];
and then implement the delegate method:
-(BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {
if (userHasCompletedAction) {
return YES;
}
return NO;
}
Don't forget to conform to <UITabBarControllerDelegate> in your .h file!
Hope that helps.
You have to implement this method
- (void)tabBarController:(UITabBarController *)tabBarController1 didSelectViewController:(UIViewController *)viewController {
if ([tabBarController1 selectedIndex]==0) {
UITabBarItem *tabBarItem = [[[[self tabBarController]tabBar]items] objectAtIndex:1];
[tabBarItem setEnabled:FALSE];
}
}
You have to do something like this for disabling your required tabbar items.
The method tabBar:didSelectItem: in UITabBarDelegate could help.