I have a navigation controller with several view controllers inside.
While restoring app application(_:viewControllerWithRestorationIdentifierPath:coder:) method calls for each controller one by one initially for first in stack then for second and so on.
At time of restoring second view controller I need to get reference to first one (to make some connection between them).
Is there any way to get previously restored controller at this step without saving this controller somewhere in the app? (the same is about navigation controller I don't save referents to it anywhere in the app)
My understanding is that only the last View Controller viewed before the app was sent to background will have its state restored by the encode/decode methods you listed above, with NSCoder.
BUT you still need to save your own data, e.g. using NSKeyedArchiver or Core Data in case the app is terminated by user, or the device reboots. This may be the best solution in your case.
See link for encodeRestorableState here:
https://developer.apple.com/documentation/uikit/uiviewcontroller/1621461-encoderestorablestate
Specifically it says:
This method is not a substitute for saving your app's data
structures persistently to disk. You should continue to save your
app's actual data to iCloud or the local file system using existing
techniques. This method is intended only for saving configuration
state or other information related to your app's user interface. You
should consider any data you write to the coder as purgeable and be
prepared for it to be unavailable during subsequent launches.
Encode the second view controller in the first view controller's encodeRestorableState method. e.g.
FirstViewController.m
- (void)prepareForSegue:(UIStoryboardSegue *)segue{
// configure and store it...
// self.secondViewController = controller;
}
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder {
[super encodeRestorableStateWithCoder:coder];
if (self.secondViewController) {
[coder encodeObject:self.secondViewController forKey:kSecondViewControllerKey];
}
}
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder {
[super decodeRestorableStateWithCoder:coder];
self.secondViewController = [coder decodeObjectForKey:kSecondViewControllerKey];
}
- (void)applicationFinishedRestoringState{
// self.secondViewController's properties are now also decoded and ready to be used
}
application:viewControllerWithRestorationIdentifierPath: can return nil for the second view controller and it will one from the storyboard for you and the reference will be what is returned in decodeObjectForKey.
Related
I am using a UITabBarController, and my 3rd tab observes an array on a singleton data store (implemented in viewDidLoad).
Currently if I just log out (and change root view controller from App Delegate), the app will crash when dealloc is called on that 3rd tab with the message "cannot remove observer for the key path "X" because it is not registered as an observer.
Using breakpoints, I see that viewDidLoad is never called on this 3rd tab, however dealloc is being called when I sign out. What is going on? I assume the UITabBarController is holding a reference to the 3rd tab when I enter the storyboard, but does not "load" that tab. Yet iOS calls dealloc on it when I release the tab bar controller.
Should I use a boolean to track viewDidLoad execution, or try to remove the observer with a #try statement? Is there an overall better design for this?
Do not use #try. Exceptions in Objective-C should always be considered programmer error, and should be fatal.
As you say, use a boolean ivar set in -viewDidLoad to avoid this.
The view has not been loaded because views are only loaded when they are required for display.
Raw KVO can be dangerous and unwieldy. While not required to answer this question, ReactiveCocoa significantly improves the KVO experience.
viewDidLoad is called before the view appears for the first time. UITabBarController is creating the relevant UIViewController, but the view is not loaded during creation. It is loaded on-demand, when a user visits the tab for the first time.
KVO removal is problematic, I don't think you can avoid using #try in dealloc. I would suggest to use KVOController: it's fairly easy to use and it would also handle all the edge cases for you.
May have found an even better solution. I add the observer in the method initWithCoder:(NSCoder *)aDecoder, which is called when the parent UITabController is loaded. I am using the storyboard which may be why I need to call override this method instead of regular init. Doing this now without the need for a BOOL flag or #try and no crashing.
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
[anObject addObserver:self forKeyPath:aKeyPath options:0 context:NULL];
}
return self;
}
Use a flag to set whether or not KVO has been set up. Using #try can create memory management issues depending on the state of the app.
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.
I recently figured out with some help here how to restore my text fields, button states, etc. when putting my app into the background, terminating, and restarting.
What I am now trying to figure out is how to keep the same information when going back to my main menu using the back arrow in the navigation bar. Of course, when I have filled out info on my sub view, hit the back button to go to the main menu, all of my user fields and button states are reset to their initial state.
I am not sure what code you might want to see here. I am happy to provide anything, but I am not quite sure what would be relevant.
My app set up is very simple. It looks like this:
--->navigation controller---->main menu----->calculator
Main menu is embedded in the navigation controller. I just need to be able to go back to the main menu from the calculator without losing the data the user has entered into the fields in th calculator.
Thanks in advance.
EDIT - This is what I am using to save and restore data for general state preservation / restoration - code added:
-(void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
// start level text
[coder encodeObject:_startLevel.text forKey:#"startText"];
[super encodeRestorableStateWithCoder:coder];
}
-(void)decodeRestorableStateWithCoder:(NSCoder *)coder
{
// start level text
_startLevel.text = [coder decodeObjectForKey:#"startText"];
[super decodeRestorableStateWithCoder:coder];
}
There are multiple ways to store/restore data..
You can make a Singleton class and store/retrieve data in/from
that.
You can store/retrieve data in/from NSUserDefaults
You can store/retrieve data in/from databases e.g., sqlite,
CoreData
You can store/retrieve data in/from files like text files, plist.
files.
It's up to you, what suits you the best. However, you can save data in either viewDidUnload or viewWillDisappear and retrieve that in either viewDidLoad or viewWillAppear of your Calculator View Controller.
I recently figured out with some help here how to restore my text
fields, button states, etc. when putting my app into the background,
terminating, and restarting.
So it seems like you have a mechanism for storing and retrieving data.
Now when you hit back to mainmenu, you can just save the data in viewWillDisappear delegate and restore data in viewWillAppear method of calculator controller.
Hope this helps.
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 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.