Problem popping to root nav controller on tab bar switch - iphone

Trying to mimic/copy the built-in address book, specifically the behavior when editing a contact or viewing an existing contact's Info from inside the Phone app. When you navigate to another tab, the editing state is reset and the "New Contact" or "Info" view is popped so that when you come back to the Contacts tab, you are back at the root table view.
I have most of this working inside viewWillDisappear using setEditing: and popToViewController: however I get strange behavior when the user navigates from the Info view to the table view using the back button. Even if I pop to the root table view controller, it seems to be using the default UITableViewController class and not my subclass (e.g. standard selection behaviors instead of my overrides to push the detail view.)
Any hints? IPD
Here's some code to illustrate:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
// This is to clean up from the colored bar in detail view
self.navigationController.navigationBar.tintColor = nil;
// These are to match the behaviour of Contacts app
[self setEditing:NO animated:NO];
// This is the tricky part: works when switching tabs, but not when back button was going to pop anyway!!
[self.navigationController popToViewController:rootViewControllerForTab animated:NO];
}

The -viewWillDisappear: method is not the best place for modifying the view controller stack for your navigationController because it is triggered both when you switch tabs and when a view is pushed on top of it.
I played around with this a bit and found that the best place for this is in the -[UITabBarControllerDelegate tabBarController:didSelectViewController:] method. So, first you need to designate an object to be the delegate for your tab bar (I used the app delegate). Bind the delegate property of your UITabBarController to an object implementing the UITabBarControllerDelegate protocol in code or in Interface Builder.
Then, implement the -tabBarController:didSelectViewController: method. The trick now is how to tell when your "address book" tab is being switched to. I kept track of the view controller for the tab in question using a property of type UINavigationController (the root view controller for the tab). After binding the tab1NavController property to the actual instance using Interface Builder, it can be used to compare to the viewController parameter to see what tab was just selected.
#interface Pop2RootTabSwitchAppDelegate : NSObject
<UIApplicationDelegate, UITabBarControllerDelegate> {
UINavigationController *tab1NavController;
}
#property (nonatomic, retain) IBOutlet UINavigationController *tab1NavController;
#end
#implementation Pop2RootTabSwitchAppDelegate
- (void)tabBarController:(UITabBarController *)tabBarController
didSelectViewController:(UIViewController *)viewController {
NSLog(#"[%# tabBarController:%# didSelectViewController:%#]", [self class],
tabBarController, viewController);
if (viewController == tab1NavController) {
NSLog(#"viewController == tab1NavController");
[tab1NavController popToRootViewControllerAnimated:NO];
}
}

Related

UINavigationController transition animations triggered too fast

I'm using a custom-made container view controller in my dictionary app. Basically, the container view controller contains a custom navigation bar on top (NOT a UINavigationBar--just a UIView with back and forward UIButtons, a UISearchBar, and a bookmark UIButton at the right), and a tab bar controller at the bottom.
My problem is this: I use the back and forward buttons to push and pop view controllers in one of the tabs (a UINavigationController) so the user can navigate through the dictionary browsing history. However, if I press the back or forward buttons too fast, I get this message in the log pane and some of the screens don't appear at all:
Unbalanced calls to begin/end appearance transitions for
<DefinitionViewController: 0x8e5d230>.
Looking around StackOverflow, I understood that this is because clicking on the back or forward buttons too fast calls the push/pop methods of the UINavigatonController in the active tab, but it does not let the animation finish. https://stackoverflow.com/a/17440074/855680
Pushing or popping view controllers without the animations solves the problem, but I do want to keep the animations. How can I approach this problem? I looked at the UINavigationController class reference to see if there are any delegate methods or properties that indicate that it's in the middle of an animation, but there doesn't seem to be any.
Fixed it myself. The solution was to create a property in my container view controller which indicates whether the UINavigationController transition animations are still happening:
#property (nonatomic, getter = isStillAnimatingTransition) BOOL stillAnimatingTransition;
Now, for all the UIViewController classes that I push into the UINavigationController, I set this flag to YES or NO in each of the view controllers' viewWillDisappear and viewDidAppear methods, like this:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.containerViewController.stillAnimatingTransition = NO;
}
- (void)viewWillDisappear:(BOOL)animated
{
self.containerViewController.stillAnimatingTransition = YES;
[super viewWillDisappear:animated];
}
And my container view controller only ever allows the execution of the back and forward buttons if the animation flag is set to NO, like this:
- (void)backButtonClicked
{
if (!self.isStillAnimatingTransition) {
// Do whatever.
}
}
- (void)forwardButtonClicked
{
if (!self.isStillAnimatingTransition) {
// Do whatever.
}
}
Maybe you can take advantage of the UINavigationControllerDelegate class and handle the events there.
In your main class that holds the navigation controller, set the delegate to yourself and handle the interactions there.
i.e. in the .h file:
#interface yourClass : UIViewController <UINavigationControllerDelegate> {
UINavigationController *content;
}
and then in the .m file:
content = [[UINavigationController alloc] initWithRootViewController:yourRootViewController];
content.delegate = self;
After that you can listen to the transition events via the following functions, and set your animation flags accordingly.
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
stillAnimatingTransition = NO;
}
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
stillAnimatingTransition = YES;
}
You can find more references about the delegate protocol from apple
https://developer.apple.com/library/ios/documentation/uikit/reference/UINavigationControllerDelegate_Protocol/Reference/Reference.html#//apple_ref/occ/intfm/UINavigationControllerDelegate/navigationController:willShowViewController:animated:

Set UITabBarController created in Interface Builder as delegate

I created my iOS app with Tab Bar template, so here is UITabBarController with bar buttons. An issue is how to set it as delegate. I found at SO that it has to be set programmatically in AppDelegate, but I believe it's impossible, because I've got no access to Tab Bar Controller as outlet.
I added proper value to #interface (both ViewController and AppDelegate), but doesn't know what to do next.
#interface ViewController : UIViewController <UITabBarControllerDelegate>
I hope I don't have to get rid of whole app template and it's possible to set Tab Bar Controller created in IB to be delegate.
Exactly I want to make it delegate to create on tab select event like this:
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController;
Any idea?
You can quickly and easily create a new TabBarController Delegate class and connect it as the delegate in the storyboard.
Create a new class :
class TabBarControllerDelegate: NSObject, UITabBarControllerDelegate {
In IB, add an object from the object library to the list of View Controller on the left (note: search "object", it's a yellow cube).
Change the class of this object (your yellow cube in IB) to TabBarControllerDelegate
In IB navigate to your Tab Bar Controller Scene. From the Connection Inspector, drag the delegate circle to the new object you added in Step 3.
Implement your delegate methods in your TabBarControllerDelegate class. Done!
func tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController)->Bool {
println("Selected a new tab")
}
I don't remember exactly the Xcode's Tab Bar template set up, but in your AppDelegate you can access to your window's rootViewController, cast it to a UITabBarController, and then set its delegate to your AppDelegate or to any other view controller.
Something like this:
UITabBarController *tabBarController =
(UITabBarController *)[[self window] rootViewController];
[tabBarController setDelegate:self]; // In this example your app delegate would implement the UITabBarControllerDelegate protocol.
EDIT
If you want to set your ViewController instance as the delegate:
UITabBarController *tabBarController =
(UITabBarController *)[[self window] rootViewController];
// I assume you have your ViewController instance set as the first view controller of your tab bar controller
// No need for a cast here since objectAtIndex: returns id, but of course you must implement the UITabBarController protocol in your ViewController.
[tabBarController setDelegate:[[tabBarController viewControllers] objectAtIndex:0]]];
EDIT 2
From your ViewController itself you can set the tab bar controller's delegate as rdelmar comments.
Just keep in mind that this cannot be done in the init method because the view controller is not in the tab bar controller yet. The proper place would be viewDidLoad but therefore it will not be executed until the ViewController view loads...
self.tabBarController.delegate = self;
0 lines of code
Drag an Object and subclass it
Xcode > Show File Inspector > Custom Class.
Class: TabBarControllerDelegate.
Set delegate to that Object
Put your existing code in that Object
This is the code you already have in your current UITabBarControllerDelegate.
class TabBarControllerDelegate: NSObject, UITabBarControllerDelegate {
// Delegate code goes here
}
What about create a viewController lets say MyTabController subclass UITabBarController
#interface MyTabController : UITabBarController<UITabBarControllerDelegate>
and set the tab Controller's class in you storyboard to MyTabController instead of UITabBarController, then put self.delegate = self; in your viewDidLoad
implement:
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController;
and here you are.
Edit:
If you find self.delegate = self; is odd, which it is, you can create an outlet in your MyTabController
IBOutlet UITabBarController *tabBarController; and connect it to the tab controller in your storyboard.
Then you can put tabBarController.delegate = self;

how to reach tabBar

i have a navigation application with a tab bar and there is a web view in first view which is related with first tab bar item . with a button web view appears and i want to hide it when i touch the first tab bar item .. Please help thx
To get the controller, just:
[[tabBarController viewControllers] objectAtIndex:indexOfTheTab]
tabBarController refers to UITabBarController *tabBarController in you AppDelegate.
EDIT 1: add this to your AppDelegate.h
-(UITabBarController*)getTabBarController;
add this to your AppDelegate.m
-(UITabBarController*)getTabBarController { return tabBarController; }
Now, you can access it from everywhere:
[(AppDelegate*)[[UIApplication sharedApplication] delegate] getTabBarController]
Don't forget #import "AppDelegate.h"
EDIT 2: In you AppDelegate.m
In the first method, just add: self.tabBarController.delegate = self;
Then, override:
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
//something like : [tabBarController hideTheStuffs];
}
With EDIT 2 you can listen to the user touches in the tab bar, even if the tab item is already the current one. With EDIT 1 you can access your delegate from everywhere if needed.
EDIT 3:
Do you have a IBOutlet UIWebView *myWebView in you ControllerView.h (the one with the view that shows the web view).
If not, add this outlet, then connect it to your webview in interface builder.
In the controller, add a method in .h and .m :
-(void)hideTheWebView { myWebView.hidden = YES; }
As this method is declared in the interface (.h), you can call it from the AppDelegate, method tabBarController:didSelectViewController.

iPhone - presentModalViewController via UITabBarItem and dismissModalViewController cleanly

I have a tabBarController that I add by placing the following code into:
AppDelegate.h:
...
UITabBarController IBOutlet *tabBarController;
}
#property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;
AppDelegate.m:
...
[self.window addSubview:tabBarController.view];
[self.window makeKeyAndVisible];
[tabBarController setDelegate:self];
I then use the following code to present a modal barcode scanning View Controller:
- (void)tabBarController:(UITabBarController *)tbc didSelectViewController:(UIViewController *)vc {
// Middle tab bar item in question.
if (vc == [tabBarController.viewControllers objectAtIndex:2]) {
ScanVC *scanView = [[ScanVC alloc] initWithNibName:#"ScanViewController" bundle:nil];
// set properties of scanView's ivars, etc
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:scanView];
[tabBarController presentModalViewController:navigationController animated:YES];
[navigationController release];
[scanView release];
}
}
When it does actually get presented I think this method isn't visually appealing, because when I dismiss the modal view I am brought back to an empty view.
A lot of barcode scanning applications or applications that simply display an image picker for example; do this quite successfully. I'm just wondering what kind of implementation they would use in order to achieve such an effect.
This is a screenshot of an application called Path, which has the exact same functionality I'm after:
I also noticed that in these applications, if you are on any other tab bar item other than the middle one let's say, and you click on the tab bar item that presents the modal view, once it gets dismissed it doesn't actually bring them back to an empty view it dismisses like normal, however the actual tab bar item that presents the modal view is never selected. I would be happy with this type of functionality if that's the only way to implement this type of effect.
Any help would be greatly appreciated as I've been stuck in this for quite some time. Also I'm not even sure whether it's the right way to put all of this code in my AppDelegate in order for the View Controller to be presented as a modal. It all seems, just, wrong.
Not entirely what I'm after, but I think I can move forward from this:
http://idevrecipes.com/2010/12/16/raised-center-tab-bar-button/
When you dismiss the modal view controller, tell the tab bar to select whatever tab was originally selected.
- (void)dismissModalViewControllerAnimated:(BOOL)animated
{
// do whatever you need to do when dismissing
// savedTabIndex is an int ivar
// tabBarController is a reference, set when showing the modal view
[[self tabBarController] setSelectedIndex:savedTabIndex];
}
You would have to save the original tab bar selection in a variable at the start of tabBarController:didSelectViewController:.
- (void)tabBarController:(UITabBarController *)tbc
didSelectViewController:(UIViewController *)vc
{
// Save the tab bar index (if it's not the photo tab)
if ([tabBarController selectedIndex] != 3]) {
savedTabIndex = [tabBarController selectedIndex];
}
}
There could be mistakes in this code, I just typed it without testing.
I found a really easy solution by playing around UITabBarControllerDelegate--I only tried this in iOS 7 though.
First, subclass UITabBarController, make it its own UITabBarControllerDelegate, and create a property that'll hold a reference to the tab you want to launch a modal with. In my app, it's called the "Sell" tab.
#property (strong, nonatomic) UIViewController *sellTab;
Then, in your init method, just create that view controller and add it to the tabs.
_sellTab = [[UIViewController alloc] init];
_sellTab.title = #"Sell";
self.viewControllers = #[homeTab, historyTab, _sellTab, bookmarksTab, profileTab];
Now here's where the magic is: override the following tab bar controller delegate methods. Code is pretty self-explanatory.
#pragma mark - Tab bar controller delegate
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
return viewController != self.sellTab;
}
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
{
if (item == self.sellTab.tabBarItem) {
[self presentViewController:[[UINavigationController alloc] initWithRootViewController:[[PostAdViewController alloc] init]] animated:YES completion:nil];
}
}
This will launch a modal which, upon dismissal, shows the same tab you were in before launch.
You shouldn't present a modal view, when the user clicks on a tab bar item.
You could instead present a modal view from within a view that's presented by one of the tabs.
Or, if you just have a single main view and the scan view you want to present modally, you should just use a button to present the scan view from within your main view. You could for instance use a toolbar with a single button in it, instead.

TabBarController delegate is not working

Can any one help me,
when i am using my UITabBarController delegate it is not working..
I called a delegate method like this..
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
[self.navigationController popToRootViewControllerAnimated:NO];
}
If what you're doing is subclassing a UITabBarController, then... oddly enough... you can get it working by setting itself as a delegate:
- (void)viewDidLoad
{
[super viewDidLoad];
self.delegate = self;
}
Then the didSelectViewController action will fire normally:
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
NSLog(#"View Changed");
}
Don't forget to add your UITabBarControllerDelegate class to your .h file:
#interface MyTabBarController : UITabBarController <UITabBarControllerDelegate>
#end
If you are using tab bar customizing by extending UITabBarController and trying to change tab bar selected index programmatically then it will not call delegates.
Please see the note inside "UITabBarDelegate":
// Note: called when a new view is selected by the user (but not programmatically)
This might help you
-(void)applicationDidFinishLaunching:(UIApplication *)application {
tabBarController.delegate=self;
// Add the tab bar controller's current view as a subview of the window
[window addSubview:tabBarController.view];
}
specify
UITabbarcontrollerDelegate in .h file
then
-(void)applicationDidFinishLaunching:(UIApplication *)application {
tabBarController.delegate=self;
// Add the tab bar controller's current view as a subview of the window
[window addSubview:tabBarController.view];
}
Read the documents to get a deeper understanding of the relationships between navigation controllers, tabBar controllers, and the view and navigation hierarchy.
Then review the code you've provided. Which view/controller is the container? You are popping the navigationController of self, which is not the same as the tabBarController. I don't think you actually need this method if you are looking to switch between tabs.
Try commenting out this method. It is an optional method in the UITabBarController delegate protocol. If you comment it out, you should get the default behavior of the tab controller, which should be to select the appropriate viewController and switch to the new view.
You typically only need to use this method if you want some action taken as you switch between view controllers.