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 ..
Related
My app has a search view(search bar) which is used all over the app. I don't want to create duplicated code so I created a view controller called MySearchViewController to handle the search job, then I created a singleton object in AppDelegate. In every view controller, I added my search view like this:
- (void)viewDidLoad
{
MySearchViewController* search = [AppDelegate searchViewController];
[self.view addSubView:search.view];
}
My questions, Is it a good way? It's a singleton so it can be added to many views. Do I need to remove the view from last view before adding to current view?
Understand that you are mixing some concepts that are not necessarily related: avoid duplicated code and Singletons.
Wikipedia says this about singletons:
In software engineering, the singleton pattern is a design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system. The concept is sometimes generalized to systems that operate more efficiently when only one object exists, or that restrict the instantiation to a certain number of objects. The term comes from the mathematical concept of a singleton.
The most important characteristic of a singleton (in my humble opinion) is that the object is instantiated only once and every single place in your application will use the same instance. Well, to use your search feature everywhere and avoid duplicated code you don't need the search view to be instantiated only once, maybe the data that comes with it, but not the view itself.
Two better ways of achieving this:
1 - You can create a ViewController with your search and just embed this on the other views using a Container View, you can use blocks or a delegate protocol to communicate between your controller and the view that is embedding it.
2 - You can create a Parent class of the ViewController that will include the search bar, like a SearchViewController and all the other viewControllers that needs the same feature will inherit from it.
The singleton could be useful if you are planing to share the same search data and text between all the ViewControllers of the application, but it would be a singleton only with these information, the UISearchBar and all other view elements should not be part of the singleton.
Ideally, you should instantiate a fresh instance of MySearchViewController every time when you want to add it to another view to avoid problems.
Do I need to remove the view from last view before adding to current view?
Its not required to remove it from previous super view because whenever you add this singleton MySearchViewController's view to some other view, it will automatically gets removed from last super view and now its super view is your new view where you have added it.
If you want to add a view from a different view controller, your view controller has to be that view controller's parent view controller:
- (void)viewDidLoad
{
MySearchViewController* search = [AppDelegate searchViewController];
[self addChildViewController:search];
[self.view addSubView:search.view];
}
also, make sure that when the search.view is added, it is already initialised.
Why you do not use NSObject class ?, i do not know your requirement , but if you want to store latest updated value in whole project(in execution) then you should use the singleton, but if you do not want to store value (i mean one result for whole project) then you should use NSObject derived Class. advantage is singleton consumes memory so memory will be wasted. NSObject class will be reusable and only allocated when it is required and then ARC will take care of all things. If you want to know how to create NSObject and use of it then you can give me reply.
Here is some code to load a XIB as part of a custom object with the object gets initialized.
Why are you not creating custom search component for search?
you can use this component all over the app.
also this is not creating duplicat code.
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code
[[[NSBundle mainBundle] loadNibNamed:#"SearchView" owner:self options:nil] objectAtIndex:0];
[self addSubview:self.view];
self.frame = self.view.frame;
}
return self;
}
Please check below code. Hope this is work for you.
- (void)viewDidLoad
{
if ([self.view viewWithTag:123456]) {
[[self.view viewWithTag:123456] removeFromSuperview];
}
MySearchViewController* search = [AppDelegate searchViewController];
search.view.tag = 123456; // give a any random tag to view
[self.view addSubView:search.view];
[self addChildViewController:search];
}
Please make sure given tag is not assign to other object except search.view in self.view.
Thanks
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).
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.
This may be impossible, but I'm trying to save the state of my application between scene transitions, but I can't figure out what to do. Currently I love the way that when you have an application running and hit the home button, you can go back to that application just where you left off, but if you transition between scenes (in a storyboard), once you get back to that scene the application state was not saved.
I only have two different scenes that need to be saved (you transition back and forth from one to the other). How can I go about saving a storyboard scenes state without taking up precise memory?
More Detailed: Here is my entire storyboard. You transition back and forth between scenes using the plus toolbar button. On the second scene the user can tap on the table view cells and a real image will fill the image view (See figure 1.2)
Figure 1.1
In figure 1.2 you see what happens when you tap inside one of the many table view cells (an image view pops up.)
Figure 1.2
THE PROBLEM: When you tap a table view cell, which fills an image view (shown in figure 1.2) it works fine if you stay on that scene or even hit the iPhone home button (if you hit the iPhone home button and then reopen the app the scene's state was saved and the image view filled with a simple image still shows just like we left it), but if I transition (using the plus button) back to the first scene, and then use the plus button on the first scene to get back to the second scene the image view that I created (shown in figure 1.2) disappears and the second scene loads without saving the state and image views we filled.
EDIT: I tried using the same view controller for both scenes, but it didn't solve the problem.
UPDATE: I just found the following code (that I think stores a views state). How could I use this and is this what I've been looking for?
MyViewController *myViewController=[MyViewController alloc] initWithNibName:#"myView" bundle:nil];
[[self navigationController] pushViewController:myViewController animated:YES];
[myViewController release];
I would suggest a combination of two things:
1. Take DBD's advice and make sure that you don't continuously create new views
2. Create a shared class that is the data controller (for the golfers, so that the data is independent of the scene)
The correct way to make the segues would be to have one leading from the view controller on the left to the one on the right. However, to dismiss the one on the right you can use
-(IBAction)buttonPushed:(id)sender
[self dismissModalViewControllerAnimated:YES];
}
This will take you back the the view controller on the left, with the view controller on the left in its original state. The problem now is how to save the data on the right.
To do this, you can create a singleton class. Singleton classes have only one instance, so no matter how many times you go to the view controller on the right, the data will always be the same.
Singleton Class Implementation (Of a class called DataManager) - Header
#interface DataManager : NSObject {
}
+(id)initializeData;
-(id)init;
#end
Singleton Class Implementation (Of a class called DataManager) - Main
static DataManager *sharedDataManager = nil;
#implementation DataManager
+(id)initializeData {
#synchronized(self) {
if (sharedDataManager == nil)
sharedDataManager = [[self alloc] init];
}
return sharedDataManager;
}
-(id)init {
if(self == [super init]) {
}
return self;
}
#end
Then, inside your view controller code you can grab this instance like this
DataManager *sharedDataManager = [DataManager initializeDataManager];
This way you will have the same data no matter how many times you switch views.
Also, you can better adhere to MVC programming by keeping you data and your view controllers separate. (http://en.wikipedia.org/wiki/Model–view–controller)
Figure 1.1 has a fundamental flaw which I believe the basis of your problem.
Segues (the arrows between controllers on the storyboard) create new versions of the UIViewControllers. You have circular segues. So when you go "back" to the original screen through the segue is really taking you forward by creating a new version.
This can create a major problem for memory usage, but it also means you can't maintain state because each newly created item is an empty slate.
Since your are using a UINavigationController and pushViewController:animated: you should "pop" your controller to get rid of it.
On your "second" scene, remove the segue from the + button and create an IBAction on a touchUpInside event. In the IBAction code add the "pop"
- (IBAction)plusButtonTapped {
[self.navigationController popViewControllerAnimated:YES];
}
I see what you mean. This should happen to every application, as when the last view controller in the navigation stack is transitioned away from, it is deallocated and freed. If you need to save values such as text or object positions, a plist may be the way to go. See this related question for how to use a plist.
Apple isn't going to do this for you. You should probably just save the state of each view using NSUserDefaults and each time your application launches re-load your saved data.
If you are storing everything in CoreData you would only need to save the active view and a few object ids, if not you would need to save any data you have.
Don't expect iOS to save anything that you have in memory between launches. Just store it in NSUserDefaults and load it each time.
Store the state of the scene in NSUserDefaults or inside a plist file then when loading up the scene just load it with the settings from there. If the images are loaded from the internet you might also want to save them locally on your iphones hard drive so it runs a bit smoother.
I don't think you should cycle the segues, just use one that connects viewcontroller 1 from viewcontroller 2 should be enough and that way you make sure that no additional viewcontrollers are being made (memory problems maybe?)
However for your particular problem, I believe that you should use core data to save the exact state of your table, view because ios doesn't save the exact state of view at all times. it will require work but you will achieve what you want. You will need to save the exact photo( using a code or enums that will be saved), the location in the table view, the score or well whatever data you need to save that state.
The best of all is that coredata is so efficient that reloading the data when the app is relaucnhed or into foreground it takes no time, and ive used core data to load more than 5k of records until now and works just fine and its not slow at all.
When i get back home ill provide a code you might use to get an idea of what i mean.
The key here is to:
Have some sort of storage for the data that your application needs. This is your application's data model.
Give each view controller access to the model, or at least to the part of the model that it needs to do its job. The view controller can then use the data from the model to configure itself when it's created, or when the view is about to appear.
Have each view controller update the model at appropriate times, such as when the view is about to disappear, or even every time the user makes a change.
There are a lot of ways that you can organize your data in memory, and there are a lot of ways that you can store it on disk (that is, in long term storage). Property lists, Core Data, plain old data files, and keyed archives are all possibilities for writing the data to a file. NSArray, NSDictionary, NSSet, and so on are all classes that you can use to help you organize your data in memory. None of that has anything to do with making your view controllers feel persistent, though. You'll use them, sure, but which one you choose really doesn't matter as far as updating your view controllers goes. The important thing, again, is that you have some sort of model, and that your view controllers have access to it.
Typically, the app delegate sets up the model and then passes it along to the view controllers as necessary.
Something else that may help is that you don't have to let your view controller(s) be deleted when they're popped off the navigation stack. You can set up both view controllers in your app delegate, if you want, so that they stick around. You can then use the ones you've got instead of creating new ones all the time, and in so doing you'll automatically get some degree of persistence.
I am currently developing an iPad application. For business reasons there wont be any data persistence on the device. The data will be accessed from a back-end server as needed using NSURLConnection. I have developed a 'model' object which does all the network access. The UI has a split view controller with a table view controller inside a UINavigationControlller as the root controller. User will drill-down on the table view controller to eventually load the detail view controller. The table Viewcontrollers are passing a reference to the model object when they are being loaded into the UINavigationController so that they can dynamically generate parts of the Table View Cell from the model. In order to be responsive, each Table View controller sets itself as the delegate of the Model object in the view will appear and when the cell is selected, queries the model object, which in turn updates the UI via a delegate method.
My question is where is the best place to set and unset the delegate of the data model?. Currently I am setting the delegate in the ViewWillAppear and setting it to nil immediately after navigation Controller:pushViewController:Animated.
// Setting the delegate
- (void)viewWillAppear:(BOOL)animated {
// set ourself as the delegate
[[self dataModel] setDelegate:self];
// Get the count of studies
[[self dataModel]GetListOfDiagnosticStudyResultsForID:[[self currentPatient]patientID]];
}
// setting delegate to nil
DiagnosticStudiesViewController *selectedVC = [[DiagnosticStudiesViewController alloc] init];
selectedVC.dataModel = self.dataModel;
[[self dataModel]setDelegate:nil];
[[self navigationController]pushViewController:selectedVC animated:YES];
Is this appropriate? Could you think of any issues with this pattern. The program is very responsive and I do not see any issues in the instruments. Is there a better way to do this?.
Sorry that this question is long winded.
I think this is an okay approach but there are a couple of considerations to be made:
You're sharing the dataModel with 2 views so you may have to update the view when you return to the DiagnosticsStudiesViewController's parent (self in your code) depending on how dataModel data is displayed.
This might get hairy in the future if you need to thread your code. In that case you might have to make a copy of the dataModel to pass to DiagnosticsStudiesViewController or handle edits to dataModel in a thread-safe manner.
You'll obviously require a network connection for both view controllers to work so you've made a workflow decision with your two view controllers by pulling dataModel from the server. In the future it may be hard to uncouple these view controllers.
If it works for your case and the decision has been made to not persist I think you'll be fine.