Is there a best practice for assigning UITabBarController-managed controller properties? - ios

Say you've got a UIViewController subclass called BeverageViewController, and you are using four separate instances of this controller within a UITabBarController collection. You've also got a data structure of, say, four NSString string objects, #"Beer", #"Wine", #"Whiskey", and #"More Whiskey". Each string corresponds to one BeverageViewController instance, which will use the string for a text label within its view. Hence, when a particular BeverageViewController instance is executing its viewDidLoad method, it expects to have a public property assigned with with one of these four strings.
When one has such a controller as a segue destination within a UINavigationController hierarchy, one typically uses prepareForSegue:sender: to set a property of this destination controller before viewDidLoad is called.
I've been exploring how to set a controller property before viewDidLoad, when the controller is part of a UITabBarController collection. I've determined that implementing a UITabBarControllerDelegate's tabBarController:shouldSelectViewController: method is an ok approach, except you have to dick around to get the relevant tab index of the controller:
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {
NSInteger index = [tabBarController.viewControllers indexOfObject:viewController];
BeverageController *controller = (BeverageController *)viewController;
controller.beverageName = self.beverageNames[index];
return YES;
}
Don't be fooled: you can't use the tabBarController.selectedIndex; you can't use the delegate method tabBarController:didSelectViewController:, because this method fires after viewDidLoad. Furthermore, you can't use self.tabBarController.selectedIndex within the controller's implementation of viewDidLoad. These three approaches seem the most obvious, but they do not facilitate the goal above.
Oh lawd, lawd, if only tabBarController:didSelectViewController: fired before the controller's viewDidLoad method. But it doesn't. Nor does it precede viewWillAppear, and preparing the view within viewDidAppear is, well, moronic.
What's the best way to set a controller property within a UITabBarController collection that enables one to have access to the property within a viewDidLoad method, without resorting to abandoning IB and procedurally managing the UITabBarController? Is it my approach above? Is it to implement a custom UITabBarController and manipulate the controllers in its viewControllers property? Or did I miss the APIobvious?

If your setting everything up in IB and those strings for the BeverageController are constant, i.e. they don't change during runtime, you can set them up in IB directly.
In the IB's inspector there is an option called "User Defined Runtime Attributes". If your BeverageController subclass has a property called let's say controllerName, you just select that VC in IB and click the little plus sign under "User Defined Runtime Attributes", the attribute name would be controllerName, then select the string-type and input you value (f.e. "Beer").
If you need to change the values dynamically, your approach with tabBarController:shouldSelectViewController: is fine.

I use application:didFinishLaunchingWithOptions: to set up my viewControllers. Get the tabBarController from your window, and then get each viewController from the tabBarController.
Something like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
NSAssert([tabBarController isKindOfClass:[UITabBarController class]], #"Must be a tabBarController");
MBFirstViewController *firstVC = tabBarController.viewControllers[0];
firstVC.foo = #"Bar";
MBSecondViewController *secondVC = tabBarController.viewControllers[1];
secondVC.foo = #"Baz";
return YES;
}

Related

Pass data from UITabBarController to its child view controllers objective C in Real Time

I have a UITabBarController with Tabs (say Tab1, Tab2, Tab3, Tab4) and UITabBarController is my RootViewController and I'm making an API Call in the same. Since it is a RootViewController I'm displaying Tab1 as my default View. When I get the results to my API Call in UITabBarController. I need to share the details in real time to my Tabs. I have tried few ideas(like NotificationCenter, Singleton Class) but it's not working out. Can somebody help me to fix this? Thanks in advance. If you have a working example I kindly request you to share it with me.
Img Representation:
UITabBarController has a property viewControllers that is an array of view controllers displayed (by index for tab position, but you probably don't need that). One simple solution is when your UITabBarController subclass gets data, it can loop over the viewControllers array and check each for delegate conformance / responds to selector and update accordingly.
That's probably the simplest. Another way would be to have the view controllers in the tabs register for updates. So they conform to a delegate protocol, and get a reference to their tab bar controller (the tab bar controller subclass), and call the tab bar saying "observe updates." The tab bar controller keeps storage of registered observing objects and calls each with new data.
This assumes you are setting the tab's view controllers in IB. If you are doing it programmatically, then with the second option you can just link the tabs to the tab bar controller when you add them. If set in IB you could also do it by overriding -prepareForSegue since embedding the tabs in the root is considered a segue, but you'd still have to cast the destinationViewController as whatever subclass can receive data.
The first option is simple enough and though I don't like casting, it's unavoidable to take advantage that the references you want are right there. Plus, there will in reality only ever be a single-digit number of tabs, so it can't get expensive. Hope this helps.
This is just same like passing the values to another viewController but here you need to use tabBarController's delegate method in below example:
Passing value using delegate method from controller A:
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
ControllerB *controllerB = (ControllerB *) [tabBarController.viewControllers objectAtIndex:1];
//In this example, there is only have 2 tab bar controllers (A and B)
//So, index 1 is where controller B resides and index 0 where controller A resides.
self.controllerB.data.text = #"some value!";
//This will change the text of the label in controller B
}
Getting value in controller B
// set property in controller B .h file
#property(strong, nonatomic) UILabel *data;
// in controller B .m file viewDidLoad method
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"getting data: %#",data.text); // getting value
}
How about
-Declare a delegate protocol, say, Tab1ViewControllerDelegate, with a method - (void) tab1ViewController:(Tab1ViewController *) tab1ViewController, didReceiveData: (NSDictionary *) data
make a TabBarController subclass a delegate of your tab1ViewController
when tab1ViewController gets it's data, call [self.delegate tab1ViewController: self, didReceiveData: datDict]
then your TabBarController can distribute the data to its array of viewControllers

Use prepareForSegue:Sender when switching VC in UITabBarController

I have googled far and wide, but everything is extremely confusing. I need it so when the tab bar gets switched to a different view controller, is there a method that gets called when the view controllers are about to switch, get the destination controller and set some variables to add some annotations to a MKMapView. How can I do this?
There are many options, but one of them is to implement the UITabBarControllerDelegate protocol and then set the class that implements it as the delegate of your UITabBarController. The delegate receives a message - tabBarController:didSelectViewController:
In that method you can implement the behavior you desire by looking at the last view controller and the next one. To get access to those view controllers from your delegate, you may need to add them as weak properties to your delegate class. You can also access all of your UITabBarController's sub view controllers through its viewControllers property which is an array of view controllers.
After some experimentation with the delegate methods, I was able to find an answer.
I used the - (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController method.
This feels like a fairly decent replacement because you can get each controller easily.
Warning: This method should not be used to handle large blocks of code, but to set up a view controller to run large blocks of code.
A test implementation:
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
FirstViewController *currentController = (FirstViewController *)[tabBarController selectedViewController];
SecondViewController *destinationController = (SecondViewController *)viewController;
// If you want, do some code on these here. For more precision, read on.
return YES;
}
This can be used with logic to determine whether you should execute some specific code: EX:
if ([[tabBarController selectedViewController] class] == [FirstViewController class]) {
if ([viewController class] == [SecondViewController class]) {
// It is going from first to second. Do some code here.
}
}
Also, you have to have set the delegate
TabBarController .h
#interface TabBarController : UITabBarController<UITabBarControllerDelegate>
...
TabBarController .m
...
- (void)viewDidLoad
{
self.delegate = self;
}
...

ios Pass data from TabbarController to ViewController in Storyboard

I have a TabBarController with 4 tabs setup in storboard editor. When I select the 4th tab, I want to send a string to its view controller. In my TabbarController class (named TabBarVC) I implemented the following method:
- (void)tabBarController:(UITabBarController *)theTabBarController didSelectViewController:(UIViewController *)viewController {
NSUInteger indexOfTab = [theTabBarController.viewControllers indexOfObject:viewController];
if(indexOfTab == 3) {
SupplierListVC *slvc = (SupplierListVC *) viewController;
slvc.locationType = #"favorite";
self.tabBarController.selectedViewController = slvc;
}
}
This method is being called just fine but the string is not getting passed. When I debugged, I noticed that the above piece of code is called after viewDidLoad of SupplierListVC. What am I doing wrong here? How can I pass the string when I select the tab?
As you said, this method is called after viewDidLoad. The method name itself should tell you that: **DID**SelectViewController
Past tense. English may not be your first language, but a good understanding of English verb tense goes a really, really, really, really, really long way when working with Apple's method names. They're not misleading. They tell you exactly when/what/why that method is for, just in the method name.
Now then, the string SHOULD be appropriately passed into the view controller--you're probably just checking it at the wrong time.
But with a tab bar, the view controllers it contains aren't loaded each time that tab is switched to. The tabs are preloaded once and will only ever be unloaded if the tab bar controller itself is dismissed (or perhaps the view controller is removed from the tab bar controller's view controllers array).
You can use tabBarController:shouldSelectViewController:
This method, like the one you're using, gives you a reference to the tab bar controller and the view controller it's trying to switch too. It returns a BOOL value however. If you return NO the switch won't happen. You can simply always return YES if you want, the main point here though is that this method is called before the switch starts and therefore definitely before viewWillAppear and viewDidAppear are called.
I had the same issue when passing data from tabbar controller to viewcontroller.
NumberpadViewController *numpad= [[self viewControllers] objectAtIndex:1];
numpad.number=#"12345";
I had get number as nil in viewdidload method.
- (void)viewDidLoad {
NSLog(#"number#",number);//nil value
}
So I had used viewdidappear method to perfom processing on the number data.
-(void)viewDidAppear:(BOOL)animated{
NSLog(#"number#",number);//12345
}

Send UITableView row From One tableviewController to another

I want to send a UITableViewCell at indexPath.row from one controller to another. I can remove the row using removeObjectAtIndex, but unable to send the removed row into another controller.
I'm trying to store the removed row in an NSMutableArray in one controller, but don't know how to populate it in another controller.
Below is the code -
ViewController *view= [self.storyboard instantiateViewControllerWithIdentifier:#"ViewController"];
view.anotherviewArray= [self.arrayFromAFNetworking objectAtIndex:indexPath.row];
If anyone can give me an idea, it would be helpful.
I believe it's bad practice to retain UI elements and pass them around your app. You should instead have some kind of a model containing your data, and pass this model from one view controller to the other. I'd recommend checking out tableview frameworks such as the free Sensible TableView framework, as they do an excellent job of providing such a model for you automatically.
I personally think that it's wrong approach to pass UI object as parameter to another controller.
As I would do it is create some object that encapsulates data model from this cell and pass this object to another view controller.
#interface DataObject : NSObject
#property id field1;
#end
UI part of cell can be easily copied in Interface Builder, so I don't see problem in that. Probably it would be great to have cell class that could fill necessary field from the object with data. This class you can use in both view controller that have to show the same cell
#interface CustomTableViewCell : UITableViewCell
- (void)customizeCellWithDataObject:(DataObject *)dataObject;
#end
I hope it makes sense to you
Assuming that you DO want to set the other data source with only this row, you need to pass it as an array.
view.anotherviewArray= [NSArray arrayWithObject:[self.arrayFromAFNetworking objectAtIndex:indexPath.row]];
But it's hard to tell from the little code that you have provided. I assume that since you are instantiating the viewController you are also transitioning to it below the provided code. If you are trying to set the array for a viewController already presented, you need to access that one, not create another, perhaps by having saved a reference to it an ivar within the current viewController or another accessible class.
I would also not name a ViewController view, it is confusing to anyone reading the code later on.
Editing for my comment below about traversing the hierarchy. Here is some code that I used in one iPad project to return the final presented viewController. This method is in the appDelegate. It is somewhat specific to my project, where there is only one navigationController. But you can adapt it to yours. You would test for a viewController that is of the class of your target view controller.
- (UIViewController *)topViewController {
UIViewController *rootViewController = self.window.rootViewController;
UIViewController *topViewController = rootViewController.presentedViewController;
while (topViewController && topViewController.presentedViewController) {
topViewController = topViewController.presentedViewController;
if ([topViewController isKindOfClass:[UINavigationController class]]) {
UIViewController *presentedViewController = [(UINavigationController *) topViewController topViewController];
if (presentedViewController) {
topViewController = presentedViewController;
}
}
}
return topViewController;
}
The other approach is to set a property to it when it is created and presented. We don't have enough code to get a good idea of your app as a whole. Where are you creating the ViewController instance that you are displaying? By that I mean where you are calling a segue to it, or pushing it onto a navigationController or call presentViewController. Wherever that is, you need to set a property or ivar to it. Let's say that you use a property in your appDelegate as a very generic case.
In your MyAppDelegate.h file you have
#property(nonatomic,strong) ViewController *viewController;
Wherever you first create it you set that property
MyAppDelegate *appDelegate = (MyAppDelegate*)[[UIApplication sharedApplication] delegate];
appDelegate.viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"ViewController"];
I now think you are trying to add this to a mutableArray in the other ViewController. Then replacing your code from the tableViewCell above you would use
MyAppDelegate appDelegate = (MyAppDelegate)[[UIApplication sharedApplication] delegate];
[appDelegate.viewController.mutableDataArray addObject:self.arrayFromAFNetworking objectAtIndex:indexPath.row];
[appDelegate.viewController.tableView reloadData];
I will say that it is not great practice to use the appDelegate for the property. But it is a generic answer that would work. It's best to put it in a class which is common to the viewControllers that you are passing data between. Perhaps a single parent which holds these two viewControllers?

Passing data between views with a UITabBarController when using storyboards

I've been converting an application to use storyboards. I'm sure this is a simple problem, but somehow I can't seem to figure out the 'correct' way of doing it, coming as we are from the old XIB world.
One of the subsections of it contains a UITabBarController, each with some subviews within it.
The action that launches this set of tabs works perfectly; I detect the segue, and set some data properties within my (custom) UITabBarController.
Next, I would like to be able to pass that data to the child views when they get created. But - because these tabs are simply 'relationships' and not segue's, I can't do what I do everywhere else, which is override the 'prepareForSegue' function.
In the old XIB universe, I'd simply bind some IBOutlets together between the tab controller and the child views. But I can't do that in storyboards, because the parent and children are separate 'scenes'.
I've tried making my UITabBarController class implement its own delegate, override 'didSelectViewController' and doing 'self.delegate = self' which almost works, except for the fact that it is never called with the first tab when the view is initially shown.
What's the "correct" (or 'best') way to do this? Please don't tell me to get/set some value on the app delegate, as this is 'global variable' territory - nasty.
Try looping through the view controllers on the UITabBarController, e.g. in this example the setData method is called from the segue in to the UITabBarController, and it then loops through the child view controllers, making a similar call on the child controller to set the data on that too;
- (void)setData:(MyDataClass *)newData
{
if (_myData != newData) {
_myData = newData;
// Update the view.
[self configureView];
}
}
- (void) configureView {
for (UIViewController *v in self.viewControllers)
{
if ([v isKindOfClass:[MyDetailViewController class]])
{
MyDetailViewController *myViewController = v;
[myViewController setData:myData];
}
}
}

Resources