Managing memory across multiple views - iphone

I'm building an app that has a main view which consists of a Map View. A second view has some necessary configuration options (config view). I segue to this config view using a partial curl segue.
The problem I'm having is the state of my config view is not being maintained.
For example,
I will segue into the config view, make some changes to the settings and return to the main Map View. Once I return to the config view again the values are back to their default values. The value in question is distanceFilterValue.
Here's the implementation of my config view controller:
#interface SimpleConfigViewController()
//private interface inside implementation
#property (weak, nonatomic) UISlider * distanceFilterSlider;
#property (strong, nonatomic) NSNumber *distanceFilterValue;
#end
#implementation SimpleConfigViewController
#synthesize distanceFilterLabel = _distanceFilterLabel;
#synthesize distanceFilterSlider = _distanceFilterSlider;
#synthesize distanceFilterValue = _distanceFilterValue;
- (NSNumber *)distanceFilterValue {
if (!_distanceFilterValue) {
_distanceFilterValue = [NSNumber numberWithFloat:250.0];
}
return _distanceFilterValue;
}
- (IBAction)distanceSliderValueChanged:(UISlider *)sender {
self.distanceFilterValue = [NSNumber numberWithFloat:sender.value];
//update GUI
self.distanceFilterLabel.text = [NSString stringWithFormat:#"%.f m", sender.value];
}
#end
It seems to me that because I keep a strong pointer to distanceFilterValue, this value should be correct when I return back to config view. I'm clearly missing something here.
Thanks in advance for your help.

I could be wrong, but I guess the config view is unloaded, and loaded again from the XIB when it's pushed the second time.
You should store your values in a model object anyway and not in a controller!

I think that Erik is right, everytime you call viewDidLoad for your configView it will reset. You could use a Singleton or NSUserDefaults to solve this.

Related

iOS 6 issue during setting text on a label

I've a problem with some label. I made a public method that takes information from a table view and shows this info in 3 label, a text view and it load a pic from the web. I setup all the variable in the table view didSelectRowAtIndexPath method, but I'm having some issue to display that info. I made so:
in the table view controller I called the other method so:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
self.detailCharacterViewController = [[DetailCharacterViewController alloc] init];
[self.detailCharacterViewController setupDetailViewControllerWithName:[self.arrayCharacters[indexPath.row] objectForKey:#"name"] andSurname:[self.arrayCharacters[indexPath.row] objectForKey:#"surname"] andKind:[self.arrayCharacters[indexPath.row] objectForKey:#"kind"] andDescription:[self.arrayCharacters[indexPath.row] objectForKey:#"description"] andImage:[self.arrayCharacters[indexPath.row] objectForKey:#"URLpic"]];
}
and I implemented the method so:
- (void)setupDetailViewControllerWithName:(NSString*)name andSurname:(NSString *)surname andKind:(NSString*) kind andDescription:(NSString*)description andImage:(NSString*)url {
NSLog(#"name = %#", name);
self.labelName.text = name;
self.labelSurname.text = surname;
self.labelKind.text = kind;
self.textDescription.text = description;
NSURL *urlPic = [NSURL URLWithString:url];
NSData *dataPic = [NSData dataWithContentsOfURL:urlPic];
[self.imagePic setImage:[UIImage imageWithData:dataPic]];
}
If I look to the log i see the right things, but if I look to the GUI on iPhone or iPhone simulator it will remain blank. What's wrong with this code?
Thank you for the suggestion.
#Alex Blundell
#import <UIKit/UIKit.h>
#interface DetailCharacterViewController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *labelName;
#property (weak, nonatomic) IBOutlet UILabel *labelSurname;
#property (weak, nonatomic) IBOutlet UILabel *labelKind;
#property (weak, nonatomic) IBOutlet UIImageView *imagePic;
#property (weak, nonatomic) IBOutlet UITextView *textDescription;
- (void)setupDetailViewControllerWithName:(NSString*)name andSurname:(NSString *)surname andKind:(NSString*) kind andDescription:(NSString*)description andImage:(NSString*)url;
#end
The problem is that your DetailCharacterViewController's view is not loaded yet when you call setupDetailViewControllerWithName:..., so none of the outlets are connected (yet). Instantiating a view controller via alloc-init does not load its view automatically.
You could force loading the view by calling [self view] at the beginning of your setup method. This works because accessing the view property will automatically load the view if it wasn't loaded before.
Alternatively, you could make the name, surname, etc. NSString properties of the view controller and set the corresponding labels in viewDidLoad.
Are you using Interface Builder/Storyboards to design your interface? Have you made sure to connect the IBOutlets for each UITextField/View?
You should identify the objects in the header file on your view controller class, and ensure they're prefixed by IBOutlet for them to appear in Interface Builders connections pane. E.g.
IBOutlet UITextField labelName;
[...]
Then you need to go to IB and go to the connections pane for the view controller to connect each text field/view.
This problem happened to me as well.
It has to do with the fact that iOS actually hooks up your outlets really really late.
If you put a breakpoint in your setupDetailViewControllerWithName: and check your IBOutlet variables, they will be nil. That's why you are seeing a blank view. (The good old nil erroring)
The work-around I will do is to declare properties in the DetailCharacterViewController class. (Such as #property (copy, nonatomic) NSString* name).
And in the place where you are calling setupDetailViewControllerWithName:, set those properties instead such as:
self.detailCharacterViewController.name = name;
Finally in your DetailCharacterViewController's viewDidLoad:, you set the values of the labels using the saved properties. (The outlets are for sure hooked up then).
(By the way, this problem exists for prepareForSegue: as well. The destination view controller's outlets are all still nil in prepareForSegue:. So you cannot set them directly there yet)

iPhone - passing data from child to parent in navigation stack

Dear fellow programmers,
What is the best way to pass information from a child in a navigation stack up one or more levels? I have searched online and can't seem to find the best way to begin implementing this.
Specifically this is my model: I have a grouped table view with several rows. When a user clicks on a row I take them to an MVC .ie click on 'select date' row takes them to a MVC with a datepicker setup (the parent passes data to the child in the process). Once this date picker (or other piece of data) has been selected, how do I pass the data back? Heres some code of the setup if it helps:
/* following is inside didSelectRowAtIndexPath of parent in navigation stack */
if (row == 1) {
DateViewController *datevc = [[DateViewController alloc] init];
datevc.selectedDate = [self dateToEnter];
[self.navigationController pushViewController:datevc animated:YES;
[datevc release];
}
DateViewController.h
#import <UIKit/UIKit.h>
#interface EnterProcedureDateViewController : UIViewController {
IBOutlet UIDatePicker *datePicker;
NSDate *selectedDate;
}
#property (nonatomic, retain) IBOutlet UIDatePicker *datePicker;
#property (nonatomic, retain) NSDate *selectedDate;
-(IBAction)selectButtonPressed:(id)sender;
#end
DateViewController.m
#import "DateViewController.h"
#implementation EnterProcedureDateViewController
#synthesize datePicker;
#synthesize selectedDate;
-(IBAction)selectButtonPressed:(id)sender {
/* how do I pass the date pickers date back to the ivar dateToEnter of parent?? */
[self.navigationController popViewControllerAnimated:YES];
}
-(void)viewWillAppear:(BOOL)animated {
if (selectedDate == nil) {
datePicker.date = [NSDate date];
} else {
datePicker.date = selectedDate;
}
[super viewWillAppear:animated];
}
...
#end
Thanks in advance as always
Andy
UINavigationController has a property, viewControllers, which returns an NSArray of all the view controllers in the stack.
The "parent" view controller (i.e. the one below your currently visible one) can be obtained by:
TableViewController *parentViewController = (TableViewController*)[self.navigationController.viewControllers objectAtIndex:self.navigationController.viewControllers.count - 2];
You can then call any methods or set properties as required.
In call backs, one of the best and recommended ways to do it is Delegates.
In your case, you will need to create a DataViewControllerDelegate and put here all the methods that Delegates of this class can call. Now, the parent should implement this delegate and register itself as the delegate when initializing it.
It's better with an example, take a look at Jonathan's answer on making custom delegates.
Something like this?
I can't see all of your code structure, so I've made some assumptions about inheritance and class structure.
Get's the view of the parent view controller, casts it to the appropriate subclass of UIView and passes a message to it.
[((MayNeedCastToSpecificClass*)[self.view parentViewController].view) setMyDate:date];
in your parentviewcontroller add the property
#property (nonatomic, retain) NSDate *selectedDate;
in childViewController after selecting the date
parentViewController *parentViewController =[self.navigationController.viewControllers objectAtIndex:self.navigationController.viewControllers.count - 2];
parentViewController.selectedDate = self.selectedDate;
[self.navigationController popViewControllerAnimated:YES];
then you can access the property from parentviewcontroller

why isn't my original variable changed when I pass by reference a CoreData managed Object variable?

why isn't my original variable changed when I pass by reference a CoreData managed Object variable?
So I have in my iPhone application, a coredata managed object called Config. One of the variables in the XCode 4 produced *.h and *.m files is "#dynamic height;" (I point this out because I wonder if it's related to this). When accessing this variable in code it is an NSNumber.
So when I setup a new data selection view/controller I set a variable in this controller to be equal to height, so that when I change it in the next view & come back, it should be changed in the 1st view (pass by ref concept).
Only problem is that it doesn't seem to change the value?
Some code extracts:
#interface Config : NSManagedObject {
#private
}
#property (nonatomic, retain) NSNumber * height;
#end
#implementation Config
#dynamic height;
#end
Calling the 2nd view
// Prepare
SelectorController *sc = [[SelectorController alloc] initWithStyle:UITableViewStyleGrouped];
sc.returnValue = self.config.height;
// Show New Window
[self.navigationController pushViewController:sc animated:YES];
[sc release];
Section Controller
#interface SelectorController : UITableViewController {
NSNumber *_returnValue;
}
#property (nonatomic, retain) NSNumber *returnValue;
#end
NSNumber is immutable, that is, you can't change its value after it's created. When you assign a new value to an NSNumber property, it's a completely different object and the original object doesn't change at all.

Passing a managed object context with a tab controller

Okay, I've tried to figure this out over and over again.
I know the best practice is to have the App Delegate pass the managed object context to the first view controller in an application, and then have each subsequent view controller pass the managed object context down. However, when I'm using a Tab Bar Controller in my application, I can seem to wrap my head around that extra layer.
The only way I've been able to figure out how to do it is have the root view controller of each tab "Reach Back" into the app delegate to grab the context, but as I understand it this is poor form.
You can use interface builder to achieve the same thing.
Here is a slightly modified (for some additional clarity) version of Rog's original suggestion - notice the IBOutlet's
#interface AppDelegate : NSObject <UIApplicationDelegate> {
ViewController1 *vc1;
ViewController2 *vc2;
ViewController3 *vc3;
}
#property (nonatomic, retain) IBOutlet ViewController1 *vc1;
#property (nonatomic, retain) IBOutlet ViewController2 *vc2;
#property (nonatomic, retain) IBOutlet ViewController3 *vc2;
Then on the implementation file:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
vc1.managedObjectContext = self.managedObjectContext;
vc2.managedObjectContext = self.managedObjectContext;
vc3.managedObjectContext = self.managedObjectContext;
// Continue with your implementation logic
}
Then from within Interface Builder ctrl drag from your App Delegate to the View Controller nested within the Tab Bar Controller and hook up the relevant View controller from the contextual menu that appears.
The key was, in the end, not to rely on interface builder to build the tab bar controller. By doing it manually in code I'm able to easily pass the managed object context to the view controller as I create them in applicatoinDidFinishLaunchingWithOptions:
I used this article as my basis: http://www.iphonelife.co.uk/creating-a-uitabbarcontroller-programmatically/
You can also just do something like this in your AppDelegate:
CoreDataUsingViewController *vc = (CoreDataUsingViewController *)[[tabBarController viewControllers] objectAtIndex:1];
vc.managedObjectContext = self.managedObjectContext;
I was adding coreData to an existing project with a few different build targets and didn't want to recreate all the different UITabBarControllers from scratch. It was pretty easy to do this way, though I'm not sure if it's the most artful way to do it or not.
See also
How to share a ManagedObjectContext when using UITabBarController
Not sure if I understand your issue correctly but why not simply pass the MOC to the other view controllers in the same manner? Here's an example:
#interface AppDelegate : NSObject <UIApplicationDelegate> {
ViewController1 *vc1;
ViewController2 *vc2;
ViewController3 *vc3;
}
// Declare properties as per normal
Then on the implementation file:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
vc1.managedObjectContext = self.managedObjectContext;
vc2.managedObjectContext = self.managedObjectContext;
vc3.managedObjectContext = self.managedObjectContext;
// Continue with your implementation logic
}
I hope it helps!
Rog

AppDelegate: Get value from ViewController?

I would like to get at
- (void)applicationWillTerminate:(UIApplication *)application
a variable from a view controller class.
I have build an tabbar application and only added the tabbar controller to the appdelegate.
[window addSubview:tabBarController.view];
How can i get an variable from the TestViewController:
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#interface TestViewController : UIViewController {
IBOutlet UILabel *testLabel;
NSString *currentString; //Value that i want to save at applicationWillTerminate
}
#property (nonatomic, retain) UILabel* testLabel;
#property (nonatomic, retain) NSString* currentString;
#end
It's somewhat incidental that TestViewController hasn't been dealloc'd by the time you reach applicationWillTerminate - it might make sense to store that value a level higher in your application. That approach would be to always store currentString in the UIApplicationDelegate so that you don't have to fetch it later:
#implementation TestViewController
- (void)setCurrentString:(NSString *)currentString {
((MyAppDelegate *)[[UIApplication sharedApplication] delegate]).currentString = currentString;
}
#end
Expanding on dbarker's answer, it sounds like what you really need is to save the currentString value in your data model. The proper place to do that is in the viewController itself.
If your data model is just that one string, you can create a property in the app delegate to hold it. Then the viewController writes to the app delegate property whenever the value of currentString changes in a view and/or its value when the view closes.
This way, the data (which is the entire point of the app anyway) is always in place when the app closes regardless of how many views you open.
It is the proper role of controllers to move information from the interface to the data model. Strictly speaking, the viewController shouldn't store any data at all beyond that needed by the interface itself. That should be a property of the data model which the viewControllers set by sending a message to the data model object with the values taken from the interface.
In this case, you would not have a currentString property in your view controllers. Instead they would have a property that is just a reference to the data model's currentString property. The view controllers would continuously update that property but would store nothing themselves.
The advantage of this design is obvious. If you need the value anywhere in your app, you have one location and one call to get it. No single part of the app needs to even know of the existence of any other part of the app save for the data model.
Not 100% sure what you are asking for but here is a guess:
UITabBarController's have a property called viewControllers which return all view controllers associated with the tabbar.
Assuming that the TestViewController was the first tab you could get to it by:
- (void)applicationWillTerminate:(UIApplication *)application {
TestViewController* test_view_controller = [tabBarController.viewControllers objectAtIndex:0] ;
NSString* value = test_view_controller.currentString ;
}
Note this would break if you decided to later move the TestViewController to a different position in the tabbar.
-- Edit --
Check all controllers and get the string from the controller that is of type TestViewController.
NSString* value = nil ;
for ( id unknownController in tabBarController.viewControllers ) {
if ( [unknownController isKindOfClass:[TestViewController class]] ) {
value = ((TestViewController*)unknownController).currentString ;
}
}
// value should be the value of the string.