Pass data between UITabBarController views - ios

I have searched for an entire day for a simple example on this and haven't found it yet. I am working on an app and I want to make an API call on the initial load and populate some variables that will be accessible in any of the tab views.
Example: I want to make a single API call to get my data which will include data relating to alerts for the alerts tab. I want to use this data on the initial load to update the "Alerts" tab badge. However, I want to also use that information on the alerts page once the person goes to that tab without having to make another API call to get the same information again.
I have read a ton of explanations that do not fit my requirements, so I am looking for a good example to help me out.

Use your UITabBarViewController's viewControllers property to access the array of view controllers in your tab bar controller. Typecast them according to your architecture of tab bar controller.
Then get a pointer to any of view controller using that array.
For example, say your tab bar controller has 5 tabs, each tab having a UINavigationController which has particular UIViewController as root view controllers. Say you want to set badge value of 3rd tab to your web response array count. You can do that as
[[[self.tabviewController viewControllers] objectAtIndex:2]
setBadgeValue:[NSString stringWithFormat:#"%d",[myArray count]];
You can also get to particular view controller's property by typecasting the view controllers. For example
MyViewController *myVc = (MyViewController*) [[(UINavigationController*)[[self.tabviewController viewControllers] objectAtIndex:2] viewControllers] objectAtIndex:0];
[myVc setPropertyName:propertyValue];

I had this question typed up since yesterday and made sure to search before posting. There was no question similar that I found that had the answer, and it may be very straight forward or maybe this is not the way to do it but here is how I solved this issue: using NSUserDefaults and the code example on this page

Put the data in your app delegate object. You can access it from anywhere in your app by (MyAppDelegate *)[[UIApplication sharedApplication] delegate], or you can give each of your view controllers an explicit link to it.
NSUserDefaults isn't really meant for sharing data globally in your app, although it would get the job done. It also has the benefit that the information sticks around if your app can't connect to the server next time. Is that a good thing or a bad thing?

Put all of those variables in a single class and access a shared instance of it whenever you want.
Add
+ (YourClass *)sharedObject
{
static YourClass *sharedClassObject = nil;
if (sharedClassObject == nil) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedClassObject = [[YourClass alloc] init];
//Initialise it here if necessary
});
}
return sharedClassObject;
}
To access the shared instance, simply use [YourClass sharedObject].

You should use NSNotificationCenter to post the notification that new data arrived and your new data as an object.
Each of the viewControllers that need that object should just subscribe to that notification and just consume the new data.

Related

iOS7 programming -- how to make view controller wait for data to load? [duplicate]

This question already has answers here:
How can I wait for a NSURLConnection delegate to finish before executing the next statement?
(3 answers)
Closed 8 years ago.
I am running on Xcode 5.1. I am working on a school project where we need to load tweets in application:didFinishLaunchingWithOptions:, and after that we need to display the fetched data on a view controller.
I am using interface builder because I think it is simpler, so to answer my question please keep in mind that I am looking for solution with interface builder.
My problem is:
The tweets loading function in application:didFinishLaunchingWithOptions: in AppDelegate runs in a separate thread. I know if you are programmatically pushing the view controller in AppDelegate then you can wait for the tweets to be loaded before you initialize the view controller. But since I am using the interface builder the view controller is always initialized at start, even before the tweets loading function is finished. Therefore I am looking for a way for function in AppDelegate to notify the instance of the view controller that story board created, to reload its data once all the required tweets are downloaded.
Simply put:
How do I access the instance of any view controller that's created by the interface builder from AppDelegate? Is this a right practice?
I've googled a lot of solutions but all of them involved creating a new instance, which is different than the one that is already created at run time.
I think what you are looking for is something along the lines of the following:
You want asynchronous data loading when your application begins
You don't want the user to be able to interact with the app until there is data to display.
What I would do for this is:
Dont fetch the data in did-finish-launching
Get the instance of your viewcontroller by using the 'rootViewController` property :
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
MYVIEWCONTROLLER *controller = (MY_ROOT_VIEW_CONTROLLER *)self.window.rootViewController;
[controller triggerFetch];
...
return YES;
}
in triggerFetch you would create add a UIActivityIndicator or progress bar to the main view and add a callback for the fetch function. After the fetch is complete you would remove the loading view and display the fetched content.
If your UI elements are bound to the model properly, changes to your model should reflect in the view automatically.
I'm not sure exactly what UI interface elements you are using. I'm assuming you have a UITableViewController displaying your tweets, and you are using your appDelegate as the UITVCDelegate as well to give it info on how many cells, dimensions, and the data for each cell inside its UITableView. And in the end you want it to reload data after you are done with fetching the tweets.
The simplest way would be to call [tableView reloadData] on the tableView (You can access it from the UITVC). But, that's just based on my assumptions being true about you using a UITableView to display the tweets.
Cheers!

Objective-C: what design patterns are there to hook up a model with views that are selected from a property list?

I am trying to build a MVC app with Objective-C. It is supposed to be a questionnaire app. I have all the questions and multiple choice answers stored into a property list, because I have different of questionnaires that I want to be able to load with this app. The idea is that the main model will keep track which item it should read of the property list, and select the corresponding view and viewController.
So schematically I have the following problem.
The RootView shows the start menu, that selects which questionnaire you will be able to take.
The RootViewController is the first controller called by the app delegate. It is supposed to instantiate the model and show the RootView. It furthermore controls the buttons of the RootView.
The model is supposed to wrap the items of the property list into a fitting datastructure, and supply it to the view controllers that need it.
The SelectedViewController is a controller that is a template specifically made for a type of question. The question could be a multiple choice, an open question, a 3, 5 or 7 choice likert scale kind of question, anything really. The template name that these view controllers will really get is ViewController.
The SelectedView is a tailor made view to the question type and will get the same name format as all the selected view controllers.
Here are my ideas.
My initial hunch is to use the delegate pattern, and set the model as a delegate to any SelectedViewController.
I could also use the delegate pattern to the RootViewController, and let him monitor if the SelectedViewController should be destroyed (via a delegate message). In that case, I can implement a prepareForSegue in the RootViewController to the SelectedViewController.
Since it is a questionnaire from a plist I could also add a prepare for segue to
every selected viewcontroller, but that will probably be a problem,
since there are at least 15 different ways of displaying the
questions.
Apparently there is also something like Key-Value Observing, according to this question. So that's also something I could use.
I think there is a definite way to deal with this, because the design patterns in iOS are pretty wel described, so there should be a few options for this really (or only just one). At the moment I am leaning towards setting the RootViewController as a delegate to the SelectedViewController and let the RootViewController handle the model. In this way I am extending the RootViewController to also hold all common functionality that every SelectedViewController should have.
But I am really not sure if this is the way to go, because my knowledge on design patterns is limited. My question is: what is the right option to choice in this specific situation (e.g. views and view controllers selected via a .plist file)?
There is no need for a specific pattern - you can deal with accessing an instance of a model object by name, i.e. in the same exact way that you deal with making a specific view and the view controller.
Let's say you are looking to connect the QuizQuestionViewController4MC and its QuizQuestionView4MC to their model. Let's assume that the model class is called QuizQuestionModel4MC, and that it needs to be configured with an object that you get from a key #"4MC" in the plist. Since your code learns the name of the model class only at runtime, you can create an instance using reflection:
NSDictionary *dataFromPlist = ... // Someone passes this to you
NSString *identifier = dataFromPlist[#"identifier"]; // Returns #"4MC"
NSString *ctrlName = [NSString stringWithFormat:#"QuestionViewController%#", identifier];
NSString *modelClassName = [NSString stringWithFormat:#"QuizQuestionModel%#", identifier];
id model = [[NSClassFromString(modelClassName) alloc] init];
// Configure the model with the data from plist
[model setPlistData:dataFromPlist];
// The model is ready to be used - give it to the view controller
MyBaseViewController *ctrl = [storyboard – instantiateViewControllerWithIdentifier:ctrlName];
// Give the view controller its model - now it is ready to be used
[ctrl setModel:model];
Note the class of the view controller - MyBaseViewController. This is not your root view controller, it's a base class for all your specific view controllers. It is this view controller that knows about a model, but it does not know the specific subclass in the model hierarchy. Each subclass of the view controller knows about its specific model subclass, so it can access the information from the model class directly, without going through selectors or KVP.
Of course it is up to the designer of the app to "wire up" correct view controllers to the correct models. In terms of the above example, QuizQuestionViewController4MC needs to know the structure of the QuizQuestionModel4MC in order to avoid sending unrecognized selectors to an incorrect class.

Sending Data from 2 View Controllers to One View Controller

I am coding a Calorie Tracker app in which I have 3 view controllers. They are all hooked up to a tab bar controller in the storyboard.
Diet View Controller
Exercise View Controller
Home View Controller
Essentially what I want to do is to take the data from my Exercise view Controller and My Diet View Controller and display it on my Home View Controller. This is my code for viewDidLoad in my HomeViewController.m
//Referencing both view controllers
MainViewController *mainViewController = [[MainViewController alloc] init];
ExerciseViewController *exerciseViewController = [[ExerciseViewController alloc] init];
//Doing the math
int totalCalsConsumed = mainViewController.totalLabel.text.intValue;
int totalCalsBurned = exerciseViewController.totalCalsBurned.text.intValue;
int totalAmountOfCalories = totalCalsConsumed - totalCalsBurned;
//display the data
NSString *totalAmtCalsText = [NSString stringWithFormat:#"%i", totalAmountOfCalories];
totalAmtOfCals.text = totalAmtCalsText;
Also, I cannot pass any data with segues because all my view controllers are hooked up to a tab bar, and there is no prepareForSegue method for tab bars.
All help is appreciated, and I would also like to know if I DESPERATELY HAVE TO use Core Date for this dilemma. For now I'm trying to dodge Core Date for it is a very advanced topic that I will touch upon in the future, but If I MUST use Core Data for this app I'll figure something out.
Due to your strong desire to avoid Core Data - which I feel should be considered - I can provide a quick, perhaps dirty (maybe not) solution so that you can have data accessible through out your viewControllers.
Based on what youre doing it seems as though you want to access data from your tabbar view controllers.
One way of doing this is to have a data model in one single place which can be accessible by other view controllers. Like a server with a webservice or have a singleton that keeps your data in one place.
In your case, you want a quick solution, you can do this for now which works, as long as all the ViewControllers mentioned are in your tabBar
//Create variables
int totalCalsConsumed;
int totalCalsBurned;
int totalAmountOfCalories;
//All your view controllers are accessible from your tabBar like so
NSArray *myViewControllers = self.tabBarController.viewControllers;
//We iterate through the view controllers in the tabBar
for(int i = 0; i < [myViewControllers count]; i++){
//This is the current view controller in the for loop execution
id currentVC = [myViewControllers objectAtIndex:i];
//Check if the currentVC variable is of type MainViewController
if([currentVC isKindOfClass:[MainViewController class]]{
//lets access the totalLabel property and store in variable
totalCalsConsumed = ((MainViewController *)currentVC).totalLabel.text.intValue;
}
//Check if the currentVC variable is of type ExerciseViewController
else if([currentVC isKindOfClass:[ExerciseViewController class]]){
//if so lets now access the totalCalsBurned property and store in variable
totalCalsBurned = ((ExerciseViewController *)currentVC).totalCalsBurned.text.intValue;
}
}
//Doing the math
totalAmountOfCalories = totalCalsConsumed - totalCalsBurned;
//display the data
NSString *totalAmtCalsText = [NSString stringWithFormat:#"%d", totalAmountOfCalories];
totalAmtOfCals.text = totalAmtCalsText;
I haven't tested this as I just wrote this now, but it should be good from the get go. Code commenting is there for you understand whats going on. Pretty straight forward.
tip for you mate since youre developing a fitness app
In the future, I do recommend something more persistent, where by data isnt lost if the application has quit, especially since you're creating a fitness app. I currently am using the BodyBuilding fitness app in my personal time, and have been frustrated at times when data was lost during my work out (sets information, exercises done with total reps for each set) only because the developers never bothered to store my data somewhere whilst I was doing my exercise, in case for when my battery died out.
You could choose 1 of several approaches:
You could create a Singleton and store/read all persistent data from this singleton. Singletons should be used with care though.
You could use NSNotificationCenter to send out NSNotifications whenever some value changes. UIViewControllers can register to receive NSNotifications.
You could store persistent data on the file system using CoreData or some other technology like NSUserDefaults or NSKeyedArchiver / NSKeyedUnarchiver.
Perhaps a dependency injection method could be used as well. Creating an object in the app delegate and passing it to all related UIViewControllers. Use this object in the same way you'd use the singleton I mentioned as option 1.
There's probably more options that I can't remember at this current moment.
Key value Coding , NSNotificationCentre and Delegates are preferred. But NSNotificationCentre is easiest in your case.
The UIViewController Home View Controller must add observer like this : Init :
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(methodToUpdate:) name:#"UPDATE_ME" object:(id)anyObject];
In delloc method :
[[NSNotificationCenter defaultCenter] removeObserver:self];
Post it from other 2 classes like on any UIButton action:
[[NSNotificationCenter defaultCenter] postNotificationName:#"UPDATE_ME" object:(id)anyObject];
Advantage of NSNotificationCentre is that they can add observers in multiple classes ..

Passing Data between tabs [duplicate]

I want to share a variable between a few ViewControllers in a tabbed application. I tried using the [NSUserDefaults] to save and load the variables but the application crashes each time.
Here is my code in the SecondViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
totalApples = [[NSUserDefaults standardUserDefaults]integerForKey:#"numberOfApples"];
[self setText:[NSString stringWithFormat:#"%g", totalApples] withExistingAttributesInLabel:self.l1];
}
It highlights the [super viewDidLoad]; when I click on the tab to open the second view as the cause of the crash.
Just in case : if you just need to share data between several VCs, NSUserDefaults may not be the best way for your Model. In that case, you may want to consider creating a class where the shared data is located, using the benefits of the singleton design pattern.
A tabbarcontroller contains a number of same-level viewControllers (as opposed to a UINavigationController, which contains hierarchical data, so the first viewController passes part of the data on to the next).
Those viewControllers need either:
- some object to hand them their data
- some object they can get the data from.
The second approach requires that those viewControllers have knowledge about the object that can give them their data and is therefore considered rigid design.
The first approach implies that you have some higher-level object that can get to the data (or contains it already) and can give it to the viewcontrollers. This is a much more elegant solution as the viewCOntrollers will be more pluggable.
The object that you could use here would be a subclass of UITabBarController. This object contains ('knows about') the viewControllers. If this object contains the dat (or can retrieve it), this object would then be able to give it to the viewControllers.
As LudoZik (#LudoZik: I wanted to upvote your answer, but was not allowed due to not having enough rep.). pointed out, create a custom class (or for simplicity an NSDictionary is also OK) that holds the data. This can then be owned by the subclass of the UITabBarController and given to the sub-viewControllers when necessary (e.g. when selected, or maybe already when loaded).

IOS How to retain data?

I have a app, where I've a tabview controller. All the data is dynamic, and when I enter on one tab, the data is loaded, but if I change my tab and come back to the initial tab, I haven't lost the data on it, what is awesome. My problem now is that I've built a new viewcontroller (outside the tabs) and when I go into it, and come back to the tabs I've lost all my information!
Is there any way to retain the initial data? So there when the user goes to that another view, and comes back, don't have to lose the data.
And another question. Is there anyway, to define variables that are available to every viewcontroller's in the app?
Data will not change when you move from one tab to the other
You will need to check if you have some special code in your viewWillAppear, if you load the data in this function you should know that viewWillAppear gets called when you travel tabs
About the global Data, you could define them in your appDelegate class, add properties to the appDelegate and then you can access them like this
//Add this on the header of your class
#import "MyAppDelegate.h"
//Then access the delegate like this
MyAppDelegate *myAppDelegate = [UIApplication sharedApplication].delegate;
//Access your variables
myAppDelegate.myVariables;
What is this general data? If it is objects, I would call retain. But if it was a data type, try making is static and make a method returning it. Or you could wrap it in an object, (like NSNumber for example if it was a float, double or int etc.) then call retain to that.

Resources