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.
Related
I have several view controller objects each can take in some user inputs from UITextField, save inputs to mutable arrays and display in a UITableView.
I also want these mutable arrays to be saved in files when home button is pressed by the user, so I found the code below in AppDelegate.m:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
It seems this method is perfect for saving user data whenever the user presses home button, but my question is how do I access these mutable arrays declared in different view controllers in order to save them into files? I can certainly make pointers in AppDelegate and make them point to each view controller object, but I know views can be unloaded when the program is running low on memory; therefore, if I make these pointers in AppDelegate then these view objects can never be unloaded when the memory is running low(because of strong references). What should I do? Thanks!
The general design pattern for user interfaces is the model view controller. The data storage ('model') is held in a separate object to the view controller.
For example, you can create an object that stores all of your applications data, instantiate it in the AppDelegate didFinishLaunchingWithOptions: method and store the reference in a property of your AppDelegate. Then each view controller can use [UIApplication sharedApplication].delegate.myData to retrieve the reference.
You need to get the MVC (model/view/controller) religion. Don't mix model (the NSMutableArrays) with controller (UIViewController). If you keep all the model data in separate classes then you don't have to worry about whether the view controllers exist or not. Also, it becomes super easy to keep your program logic clean. Put all the saving/loading stuff in the model classes. You can have your model classes listen for the UIApplicationWillResignActiveNotification and save when they receive it.
Unfortunately Apple's templates tend to push people toward putting a lot of stuff in app delegates or view controllers. Only really app global stuff should go in app delegate. Only controller code (that mediates between model and view) should go in a view controller.
Inside other view controllers you do this
id myid = [[UIApplication sharedApplication] delegate];
Please replace the "id" with the name of your AppDelegate Class (usually AppDeletegate).
You of course have to #import the AppDelegate in the top of your implementation file.
I suggest to listen for UIApplicationWillResignActiveNotification.
Also in iOS6 and later views are not unloaded when the memory is running low
(void)viewWillUnload Discussion In iOS 5 and earlier, when a low-memory condition occurred and the current view controller’s views
were not needed, the system could opt to remove those views from
memory. This method was called prior to releasing the actual views so
you could perform any cleanup prior to the view being deallocated. For
example, you could use this method to remove views as observers of
notifications or record the state of the views so it can be
reestablished when the views are reloaded.
In iOS 6 and later, clearing references to views is no longer
necessary. As a result, any other cleanup related to those views, such
as removing them as observers, is also not necessary.
In my app, some studies I've done show that when the user views an article, the vast majority of the time (85%+) they load the accompanying comments view controller that goes along with the article.
I'd love to load this view controller while they're reading the article, so when they tap the comments button to transition to the comments view controller the view controller is ready without any loading times.
How would I go about accomplishing something like this? At the moment when the user taps the button I call performSegueWithIdentifier: and pass it the identifier I set in the Storyboard.
And obviously, for the cases where the user decides to go back to the root view controller (say, a list of articles) I'd want to cancel the loading of that comments view controller as it would be wasteful to continue at that point.
If you keep the data model separate from the UI, you shouldn't have any trouble creating the views on the fly, and almost nothing to gain from creating them earlier.
A reasonably standard approach is to bring the view in with blank or filler data, and have the data call go out to the middle tier, with asynchronous handlers processing callbacks.
This is much easier with the block based completion handlers available in the iOS 7 flavored NSURLSession, and only slightly harder with NSURLConnection (which listens for responses on the main thread, but can be thrown into the background once you catch the response).
So my advice would be to focus on backgrounding the data calls and responses, and strongly differentiate between displaying UI and populating the UI with data. If your data manager is separate from your View Controller, nothing is stopping you from "pre-fetching" the data a little early, and then potentially having it ready when the ViewController needs it. It's a perfectly normal load balancing / customer experience technique for high value data.
The solution I'm about to describe is a bit of a hack - it doesn't really conform to the proper model-view-controller design pattern that Ryan mentioned. That being said, it might give you an idea about how to proceed. Perhaps you can improve on it to make it cleaner.
First, define a #protocol in the App Delegate. Let's call this protocol CommentQueryDelegate; it should define a method called -(void)handleCommentQuery. Also give your App Delegate a strong property to store the comment data and a weak property to store a delegate object, like so:
#property (nonatomic, strong) NSMutableArray* arrayOfComments;
#property (nonatomic, weak) id<CommentQueryDelegate> commentQueryDelegateObject;
Make sure to initialize the both of these properties to nil.
Somewhere in the article view controller, use dispatch_async() to asynchronously query your database and retrieve the comments while the user is reading the article:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
{
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
// query your database here
appDelegate.arrayOfComments = [NSMutableArray array];
[appDelegate.arrayOfComments addObject:#"someComment"];
[appDelegate.commentQueryDelegateObject handleCommentQuery];
}
If the query is completed before the user segues into the comments view, the handleCommentQuery message will be sent to the nil object, which will have no effect.
Now, in the comments view controller's viewDidLoad method, set the commentQueryDelegateObject property of the App Delegate to self. You will need to specify that the comments view controller conforms to the comment query protocol. Next, check to see if the App Delegate's arrayOfComments property is nil. If it isn't, great - display the comments immediately. Otherwise, display a UIActivityIndicatorView.
Implement the -(void)handleCommentQuery method in your comments view controller. This method should disable the activity indicator and display the comments.
One final thing to consider - the strong pointer to the arrayOfComments object will keep it from being deallocated, so you should set this pointer to nil once you're done with your article view controller.
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).
So I'm struggling with Core Data. I'm finding there are many ways to do the same thing, and that if you try to build the app using Storyboards and UIManagedDocuments you need to read all the tutorials and examples older than last year with a translation sheet. Today's question is about finding the best practice when adding a new managed object. I've seen examples done both ways:
Create the new managed object in a table view controller (after clicking +) and giving that new shining managed object to the subordinate "Add" view controller to get user input for all the object attributes. This seems simple, and the returned object is simple to understand because it contains all the individual attributes. But I've seen example code in this "Add" view controller for a "cancel" button that deletes the managed object passed in and then calls Save Context before dismissing itself. Functional, but the MVC training gnome on my shoulder is screaming at me about having this subordinate Add View delete an object and horrors directly call Save Context. The Recipe example code from Apple appears to use this method.
Send nothing to the Add view controller, and have it send back a delegate call the table view controller that returns each of the attributes as a separate passed parameter. So the return method becomes really long: controller:(UIViewController *)controller didReturnFirstName:(NSString *)firstName andLastName:(NSString *)lastName andStreetAddress:(NSString *) and... and... and.. But this is SO consistent with MVC dogma because the managed object is created back in the table view controller when it receives all the individual attributes, and the "Add" view never touches the Model(Core Data), or throws away an unused managed object when the user changes their mind.
Even with chained delegated methods, I'm still debating with myself which is a better method. Comments and ideas from those who've lived with both forms would be a welcome addition.
Thanks.
It you look at the example in Apple's tutorial, they accomplish this task by doing a number of things outlined below, in this case they have a modal view appear to input the information that is to be added to the data model:
In the modal view that appears, they create a protocol to handle either dismissing the view or saving the data and a property of type id that implements that protocol to be the delagate, this insures whatever object that is implments the required methods
In the view controller that created the modal view, they implement the protocol, including saving the object to the data model and dismissing the modal view
In the view controller that created the modal, they set the view controller that created the modal view as the modal view delegate during the seque to the modal
So, to summarize, in the modal view to collect the new data you need to:
create a protocol and property in .h and synthesize it
#protocol yourProtocol <NSObject>;
//methods that determine what happens based on what user does, it would save your core data object
#end
#property(nonatomic, weak) id<yourProtocol> delegate;
Then, in the modal view .m file you call those methods on the delegate, likely when they pick save or done, so a method for each probably as IBAction connected to a button
[self.delegate myMethod];
In the view that presented the modal view, you implement the protocol in the .h file
#interface viewController() <yourProtocol>
and finally, add your methods to the view controller that presented the modal view's .m file, this should include removing the view and saving your core data. According to Apple and other sources, the view controller that caused the popup/modal, etc... should be the one that dismisses it. Then, in the seque using the seque indentfying, set the view controller that is presented int he modal view as the modal view's delegate.
You're right, there are many approaches to take on this one.
It sounds as if you are starting with a context that may not be saved, so in order to be able to get back to your starting point I would tackle it like this:
Start by creating a new NSManagedObject to be used as a temporary object which gets passed to your "Edit" view.
If you are editing an existing object, copy the attributes from the existing object into this new temporary object (You can use a quick for loop to copy them all generically). Otherwise proceed with the newly created object.
Pass the temporary object to the "Edit" view controller and have it treat both cases the same.
a. If the user presses cancel, have either a protocol or delegate method which notifies the tableview and the tableview then simply discards the temporary object.
b. If they press save, notify the tableview and then copy the attributes from the temporary copy back to the original and delete the temporary object (if it is an edit operation), or just leave it as the newly created object if it was an "Add" operation.
This is not really an answer so feel free to ignore, but neither is any of the others (so far) a complete answer, and I feel the need to add some points without creating a new question, because I really want to get to the bottom of this too. In particular the answer should deal with
what happens when you Cancel vs Save
what if you want to use the same controller for editing an existing entity rather than creating a new one
Nicks answer deals with apple's (current) convention for passing data back, but is not a complete answer because that tutorial does not deal with core-data managed objects, and apples own samples that do use managed objects do it differently.
But on this subject, I find that the delegate convention is unwieldy in some cases and was happy to read this from a more experienced developer:
http://robsprogramknowledge.blogspot.pt/2012/05/back-segues.html
Rob details different mechanisms for that which are all equally valid, including the protocol/delegate convention (Nick's), using a data object without a protocol (so-called "shared memory") - more like some of the others suggested, and - my favourite - using a block.
A block is a nice option because, while it works like a delegate, the code remains entirely in context in the "parent" viewcontrollers source
However, the "shared memory" makes more sense because, as you say, using anything other than the data (managed) object which you've already designed to pass all of these properties around just seems silly.
The issue for me then is, what's the convention for creating these managed objects, passing them around, then either
1. saving changes
2. canceling changes
3. canceling creation of a new entity altogether
Inafzigers answer above works something like this, but why all that copying of properties? Can't I just not save my changes?
Apple' CoreDataBooks seems to deal with this by creating a child ManagedObjectContext and a new entity in that context (insertNewObjectForEntityForName) in the prepareForSegue, and in the delegate method (when it comes back):
- if Save was clicked, it saves both this new ManagedObjectContext and the parent FetchedResultsContext to store the object
- if Cancel was clicked it does nothing, effectively discarding the new managed object context, and thus, presumably, the new object
So this might be the conventional approach, except that:
There is a comment in the code of that sample (CoreDataBooks RootViewController.m) which states that the two managed contexts are not necessary, implying that you might be able to do the same with just one. Here's the full comment:
IMPORTANT: It's not necessary to use a second context for this. You could just use the existing context, which would simplify some of the code -- you wouldn't need to perform two saves, for example. This implementation, though, illustrates a pattern that may sometimes be useful (where you want to maintain a separate set of edits).
CoreDataBooks does not use a UIManagedDocument - not sure if that makes a difference
What's not clear to me is, do we really to use insertNewObjectForEntityForName to create the managed object? What about just creating the object and then only inserting it later if the user hit Save?
Also what about if we do an Edit and a Cancel - can we use the undo manager to get rid of changes?
I think the right approach would actually be to create a new, separate NSManagedObjectContext for your AddController (if a new item is to be created) or EditController (if an existing item is to be modified).
Chose A) or B) depending on your SDK:
A) You then have the choice to either save changes in that context, or discard the context.
In case of a "save", you can merge the changes into the TableController's ManagedObjectContext through Notifications (NSManagedObjectDidSaveNotification).
In case of a 'cancel", you can just discard the separate context.
B) Alternatively, if you're on OSX 10.7(+), you could use nested NSManagedObjectContexts, creating an NSManagedObjectContext in AddController (the child context), and settings it's parentContext to the TableController's NSManagedObjectContext.
In case of a "save", you'd save the child context, and the parent context.
In case of a "cancel", you'd just discard the child context.
See a detailed example of this in Apple's CoreDataBooks example http://developer.apple.com/library/ios/#samplecode/CoreDataBooks/Introduction/Intro.html
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.