I have the relatively common setup of a TabBarController whose tabs contain NavigationControllers which have TableViewControllers as their roots. I'm trying to perform some logic on initialization of one of these TableViewControllers but can't seem to find what init function gets called.
My goal is to add a listener in the TableViewController (that I have subclassed) which can respond to events by updating the navigationController.tabBarItem.badgeVluew property.
I've tried putting code into initWithStyle: as well as init but neither of them end up getting called. I've also tried putting it in viewDidLoad, but that only gets called once the controller actually appears (I need to have it happen as soon as the controller is loaded / as soon as the tab bar item shows up).
Does anyone know where I would put this code for it to happen on initialization of the controller?
Also, this is all set up through interface builder / NIBs. I'm not adding the nav controller or tableviewcontroller manually, which is why it's not clear what init function I need to override.
If you select one of your UITabBarItems in IB, you will see 'View loaded from "YourView"'. Click into this "gray" View. In the Inspector window you will see in the Attributes Tab (the tab on the left) the title and the NIB name which will be loaded (lets call it "YourNibName").
Now select the right tab of the inspector (Identity) and change the Classname (Combo next to Class) to your "YourViewController" class, which you must create in xcode. Don't use the standard ViewController, which is already selected. The InterfaceBuilder loads your nib and attaches it to your ViewController.
Open YourNibName and change FilesOwner's Class (Inspector, right Tab) to "YourViewController", too.
Your TabBar's NIB contains a FilesOwner, too. Create a ViewController for this FilesOwner and set its Class to this Controller (i.e. TabBarController)
In "TabBarController" you can find out which Tab was selected by using this code:
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController{
if ([viewController.nibName isEqualToString:#"NIBName1"]){
// Do something here, if you like. (i.e. Save the state in a string or int)
}
if ([viewController.nibName isEqualToString:#"NIBNAme2"]){
// Do something here, if you like. (i.e. Save the state in a string or int)
}
...
}
Here you can do something "global" or preinitialize something. This is ONE thing you can do.
INIT OF YOUR VIEWS:
If you select a Tab and the view (which is handled by YourViewController) will be shown for the first time, "viewDidLoad" will be called in "YourViewController"
- (void)viewDidLoad {
// Here you can add views programatically
[self.view addSubview:myNavigationController.view];
[self.view bringSubviewToFront:myNavigationController.view];
// And if you like, do some INIT here
[super viewDidLoad];
}
I hope this is what your question was about.
Now something about the badge. It's a hack, but works fine for me.
Header file:
Add an outlet to your controller, which is representing your TabBarController:
#interface yourController : UIViewController <UITabBarControllerDelegate> {
UITabBarController *tabBarController;
}
#property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;
#end
Connect this outlet in IB with your TabBar.
Implementation:
In your TabBarControllerClass you can overwrite 'initWithNibName':
#synthesize tabBarController;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
// Do some init here
// select your desired item (it will be loaded)
// then you can assign the badge
tabBarController.selectedIndex = 1;
tabBarController.selectedViewController.tabBarItem.badgeValue = #"222";
// and select the item you will start with
tabBarController.selectedIndex = 0;
// if you like you can add a notification, which you can activate from anywhere else
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(itemBadgeChanged:)
name:#"itemBadgeChangedNotification"
object:nil];
}
return self;
}
if you don't use nib, use '- (void)loadView { ... }' instead.
You are using a subclass of the TabBar controller, maybe you can use 'self.selectedIndex = 1;' instead of 'tabBarController.selectedIndex = 1;', and so on. Just try this out
Hope this helps!
Related
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:
I m new in ios development. I m using storyboard for my application, i am creating a two UIView in one UIViewController, in my first view i have one button, i want to when i click on button, picker view(second View) will display that View have one picker, one done and one cancel button.
Use below code:
// ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController{
IBOutlet UIView *view1;
IBOutlet UIView *view2;
}
#end
ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
view2.hidden=YES;} //initially set view2 as hidden.
//IBAction Methods
- (IBAction)Go_To_SecondView:(id)sender {
view1.hidden=YES;
view2.hidden=NO;}
- (IBAction)done:(id)sender {
//your code
}
- (IBAction)cancel:(id)sender {
//your code
}
Your view Controller Scene should be like this:
Add two views and add the buttons and pickerview .
Use the hidden property set to hidden for second view when view appears.
On button click make the second view appear using hidden set to no and bringToFront methods
I have created a new project in XCode and used the new Storyboard-feature to create two different View Controllers.
The first View Controller is attached to the main files (ViewController.h, Viewcontroller.m). The second View Controller is attached to it's own set of .h/.m files (NewUserController.m/.h)
Now for the problem which I havent been able to find a solution for in the last hours;
I have added a button the second view controller and attached the button to an IBAction (verifyNumber). When I attach the 'Touched Up Inside' event the IBAction is never fired. However, when I attach the 'Touch Down' everything works fine..
Both View Controller's have got the 'user interaction enabled' selected and apart from the button the second view controller doesn't contain any other elements. Also, my manual performSegueWithIdentifier is working (switch from view1 to view2).
Can anyone spot where it has gone wrong?
The code:
ViewController.m
- (void)firstStartup {
// Future use for getting userID
// Switch to loginview
[self performSegueWithIdentifier:#"segueLogin" sender:self];
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// Fire firstStartup
[self firstStartup];
}
NewUserController.h
#import <UIKit/UIKit.h>
#interface NewUserController : UIViewController
#property (weak, nonatomic) IBOutlet UITextField *inputNumber;
- (IBAction)backgroundTap;
//- (IBAction)verifyNumber;
- (IBAction)verifyNumber:(id)sender;
#end
NewUserController.m
#import "NewUserController.h"
#implementation NewUserController
#synthesize inputNumber;
// Collect User data & Start Request
- (IBAction)verifyNumber:(id)sender; {
inputNumber.text = #"testing";
}
- (IBAction)backgroundTap {
[inputNumber resignFirstResponder];
}
UPDATE
Because of the response of NJones I have tested some more and deleted the gesturerecognizer I had present on the second view. After deleting this recognizer the UIButton works with all events (Touched Up Inside).
Does the recognizer somehow block any 'tap' events to overlaying objects (such as the UIButton)?
I have a few thoughts,
1) Why do you have:
- (IBAction)backgroundTap;
//- (IBAction)verifyNumber;
- (IBAction)verifyNumber:(id)sender;
There is a difference between verifyNumber and verifyNumber:(id)sender and they can both exist at the same time, and both can be connected in the nib.
2) Are you using any UIGestureRecoginzers on the view at all?
3) Is this button a custom button or subclass of UIButton?
4) (I truly don't think this will help solve your problem it's just good practice, and I'm already typing :)) Using a view property to check if a method was called is inconclusive at best. Try putting a log statement in the IBAction method like So:
- (IBAction)verifyNumber:(id)sender; {
NSLog(#"verifyNumber:");
inputNumber.text = #"testing";
}
I have found quite a lot on this subject but I just can't figure it out. Any help would be massively appreciated!
I have an app set up with a UITabBarController. Within one of the tabs, I am showing a UITableView which is using a UINavigationController to allow for hierarchy. All the tables rotate just fine when the orientation is changed, until I get to what is effectively the final view in the hierarchy.
The final view is not a UITableView, just a basic UIView. But I can not get this page to rotate successfully! I have remade the view from with the absolute basics required and it still doesn't want to work! The code is below but it is currently pretty much a standard template with nothing in it now.
Also, I am not using InterfaceBuilder, and shouldAutorotateToInterfaceOrientation is on all views. This is the only one I am having problems with.
SomeView.h
#import <UIKit/UIKit.h>
#interface SomeView : UIViewController
{
NSString *someID;
NSString *someName;
}
#property(nonatomic,retain) NSString *someID;
#property(nonatomic,retain) NSString *someName;
#end
SomeView.m
#import "SomeView.h"
#import "AppDelegate.h"
#implementation SomeView
#synthesize someID, someName;
-(void)loadView
{
}
-(void)viewDidLoad
{
[super viewDidLoad];
}
-(void)viewDidUnload
{
[super viewDidUnload];
}
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
return YES;
}
-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration
{
NSLog(#"willAnimateRotationToInterfaceOrientation");
}
-(void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
-(void)dealloc
{
[super dealloc];
}
#end
UPDATE 10th Nov 2011
I'm still having this issue, however looking through documents and bits this seems to be my problem (http://developer.apple.com/library/ios/#qa/qa1688/_index.html)
The view controller's UIView property is embedded inside UIWindow but alongside an additional view controller.
You may find a situation where shouldAutorotateToInterfaceOrientation is called once at startup for a given view controller but is never called again when the device is rotated. Because view controllers are tightly bound to the views they manage, they are also part of the responder chain used to handle events. View controllers are themselves descendants of the UIResponder class and are inserted into the responder chain between the managed view and its superview. So it is common practice to have one primary view controller in your application as part of the responder chain. You would typically add one primary view controller such as a UINavigationController, UITabBarController or a generic UIViewController to your UIWindow. For example, this is done by calling:
[myWindow addSubview:primaryViewController.view];
If you add an additional view controller's UIView property to UIWindow (at the same level as your primary view controller) via the following:
[myWindow addSubview:anotherController.view];
this additional view controller will not receive rotation events and will never rotate. Only the first view controller added to UIWindow will rotate.
My UITabBarController stopped to autorotate, when I added a new navigationController to it with a tableViewController and didn't notice, that my custom navigation controller's shouldAutorotateToInterfaceOrientation returns YES only for one orientation. The solution is to check shouldAutorotateToInterfaceOrientation function in each Controller inside TabBarController.
May be it will help to somebody.
And I have figured it out...
I was looking at the code which pushes the UIViewController onto the stack and I had not fully initied the UIViewController.
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];
}
}