Changing date not reflected in previous view - ios

I am reading some book and I stumbled this thing.
In my ViewController when a user clicks change date button following
code is called:
- (IBAction)changeDate:(id)sender {
DateViewController *vc = [[DateViewController alloc] init];
[vc setItem: item];
[[self navigationController] pushViewController:vc animated:YES];
}
item is a pointer to a custom class object, which has ivar of type NSDate *;
Now, inside DateViewController when user already picked new date and wants
to navigate to previous view, I have following code:
- (void)viewWillDisappear:(BOOL)animated
{
NSLog(#"%#", [datePicker date]);
item.dateCreated = [datePicker date]; // get selected date
}
This code works and when user goes back from above code change is reflected
in item data structure and user can see new date. However, if I change above code, to following code, it doesn't work anymore, any clues why?
(This does NOT work):
- (IBAction)changeDate:(id)sender {
DateViewController *vc = [[DateViewController alloc] init];
vc.userDate = currentItem.dateCreated;
[[self navigationController] pushViewController:vc animated:YES];
}
DateViewController:
- (void)viewWillDisappear:(BOOL)animated
{
NSLog(#"%#", [datePicker date]);
self.userDate = [datePicker date];
}

In the first case, item is a mutable instance, because you can change the date that it contains. In the second case you are supplying the NSDate itself, which is immutable.
So, in the first case, you pass a reference to item which can be edited and these edits are available later.
But, the second case doesn't edit the original date, it just stores the chosen date into a property on the view controller which is in the middle of being dismissed.
Generally it is better to make the communication clear, so the view controller would pass the chosen date back to the caller by delegation or provide a property (like in your second case) that can be queried once the selection is made. Your first option is effectively hiding the data exchange by sharing the instance item while the second view controller is on display.
Immutable means that the object itself (its contents) can not be changed. It does not prevent any reference to the object from being changed. If we use arrays (where there are mutable an immutable versions) to demonstrate:
NSArray *a = [NSArray array];
NSArray *b = a;
[b editSomething]; // illegal (not a true method name but just an example of something you might want to try)
b = nil; // just nils b, no affect on a at all
And
NSMutableArray *a = [NSArray array];
NSMutableArray *b = a;
[b addObject:#"String"]; // edits a, because a and b are the same object
b = nil; // just nils b, no affect on a at all
The NSArray is like the situation where you just pass the NSDate. The NSMutableArray is like the situation where you pass item (because you can change the contents).

Related

Passing data with delegate but still nil why?

I made 2 viewcontrollers and implemented on tabbar controller. I passed some data from A vc to B vc with using delegate. When I checked the log it showed me correct value. But when I moved to B vc the value I passed was nil. (the value is for tableview.) Here is my code.
in A vc
-(void)passData {
NSMutableDictionary *infoDic = [[NSMutableDictionary alloc] init];
[infoDic setObject:url forKey:#"file_url"];
[downloadArr addObject:fileInfoDic];
Bvc getDataFromA:downloadArr];
[Bvc reloadTableView];
}
in B vc
-(void)getDataFromA:(NSMutableArray *) downloadArr{
self.downloadArr = [downloadArr mutableCopy];
NSLog(#"my download list%#", self.downloadArr); // This time was ok.
}
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
NSLog(#"Array status %#", self.downloadArr);//This time it showed me nil
[self.tableView reloadData];
}
To have the array printed inside viewWillAppear without nil
NSLog(#"Array status %#", self.downloadArr);//This time it showed me nil
you need to give it a value before you show bVC from aVC , whatever you use present/segue/push , also don't forget to declare it as strong , you need to do this
bvc = [[self.tabBarController viewControllers] objectAtIndex:1];
[bvc loadViewIfNeeded];
[bvc getDataFromA:downloadArr];
Please check the way you move to B-VC and whether the instance of (B-VC) you used in the A -VC is same to self in the B-VC “viewWillAppear” method.
It seems when u move to B-VC, A new instance has been created.
Or you can implement the 'setDownloadArr' method, log the value and show when the value become nil.

How to save and load again data on views when back many times in iOS

I'm working on a project that has many view controllers. Suppose that they are:
A -> B -> C -> D -> E ->F ->G -> H. Each of them has a back and a next button to switch to another view and has many text fields.
I typed text into every textfield. From H view, I can go back to previous views by popviewcontroller and review typed data. but when I click on next button again, all of data on the view were lost. I need to back/next continuous without losing data. How can I do that?
Create a Singleton class.
Give in Singleton class a property like Form *form;
If you start your first ViewController create a new Form
[Singleton sharedInstance].form = [[Form alloc] init];
On leave first ViewController set property from TextField
[Singleton sharedInstance].form.name = textField.text
On leave second ViewController set property
[Singleton sharedInstance].form.mail = textField.text
In each ViewController in viewWillAppear method set stored text
self.textField.text = [Singleton sharedInstance].form.name
or
self.textField.text = [Singleton sharedInstance].form.mail
It's a simple example, but hope it helps to understand what is to do :)
What about using a NSMutableDictionary to keep the models for each view controller as a key value pair. And Each View Controller initialized with this NSMutableDictionary
- (id) initWithDataDictionary:(NSMutableDictionary *)aDataDictionary
{
self = [super init];
_myDataModel = (MyDataModel*)[aDictionary valueForKey:#"MyKeyName"];
if(_myDataModel == nil)
{
_myDataModel = [MyDataModel alloc] init];
aDataDictionary setValue:_myDataModel forKey:#"MyKeyName"];
}
return self;
}
- (void) viewDidLoad
{
[super viewDidLoad];
[self displayData];
}
- (void) displayData
{
self.text1.text = _myDataModel.name;
}
You may also able to keep a key-value pair for setting focus to a last focused textfield.
I see two options here:
If you use storyboards unwind segues are very good option.
Else you can create own delegate.

Variables and Transferring Data between View Controllers

I know that there are tutorials everywhere, but I can't figure this out for some reason. I have a tab bar controller. Each tab links to a navigation controller, which is segued to a view controller. So, 2 main view controllers (StatusVC and TransactionsVC).
In StatusVC, I have a text field. In TransVC, I have a table view. A person adds a cell to the table. Math is done behind the scenes. The cell values are added together (numbers). This information is sent back to StatVC for calculations and displaying of the data. I've already got the math part down. My question: how do I transfer the data between view controllers, and better yet, how do I store this data so that it doesn't get deleted on quit (NSUserDefaults probably)?
This can be broken down I suppose, the transferring of data, the saving of data, and the displaying of data when the tab is pressed and view is shown.
I'm hoping this is making sense. Anyway, here's the code I've got. You're looking at TranVC. User enters data into the table with an alert view. You are looking at part of the Alert View delegate methods. This is when the user enters data into a cell (presses done). Look for key areas with the ******* comments.
StatusViewController *statVC = [[StatusViewController alloc]init]; //*******init
// Set the amount left in the budget
NSString *amountToSpend = statVC.amountLeftInBudget.text;
double budgetLabel = [amountToSpend doubleValue];
NSString *lastItem = [transactions objectAtIndex:0];
double lastLabel = [lastItem doubleValue];
double totalValue = budgetLabel - lastLabel;
NSString *amountToSpendTotal = [NSString stringWithFormat: #"%.2f", totalValue];
statVC.amountLeftInBudget.text = amountToSpendTotal; //*******set text (but not save), either way, this doesn't work
// Set the amount spent
NSString *sum = [transactions valueForKeyPath:#"#sum.self"];
double sumLabel = [sum doubleValue];
NSString *finalSum = [NSString stringWithFormat:#"%.2f", sumLabel];
//Set the amountSpent label
statVC.amountSpent.text = finalSum; //*******set text (but not save), either way, this doesn't work
// The maxed out budget section
if ([statVC.amountLeftInBudget.text isEqualToString: #"0.00"]) //*******set color (but not save), either way, this doesn't work
{
statVC.amountLeftInBudget.textColor = statVC.currencyLabel.textColor = [UIColor redColor];
} else if ([statVC.amountLeftInBudget.text compare:#"0.00"] == NSOrderedAscending)
{
statVC.amountLeftInBudget.textColor = statVC.currencyLabel.textColor = [UIColor redColor];
} else if ([statVC.amountLeftInBudget.text compare:#"0.00"] == NSOrderedDescending)
{
statVC.amountLeftInBudget.textColor = statVC.currencyLabel.textColor = [UIColor colorWithRed:23.0/255.0 green:143.0/255.0 blue:9.0/255.0 alpha:1.0];
}
if ([statVC.amountLeftInBudget.text compare:#"0.00"] == NSOrderedAscending)
{
// Create our Installation query
UIAlertView *exceed;
exceed = [[UIAlertView alloc]
initWithTitle: #"Budget Exceeded"
message: #"You have exceeded your budget amount"
delegate: self
cancelButtonTitle: #"Okay"
otherButtonTitles: nil];
[exceed show];
}
Any help with this would be amazing.
This is indeed a common question.
There are various solutions. The one I recommend is to use a data container singleton. Do a google search on the singleton design pattern in Objective C. You'll even find examples of it here on SO.
Create a singleton with properties for the values that you want to share. Then teach your singleton to save it's data. You can use user defaults, you can use NSCoding, you can extract the data to a dictionary and save it to a plist file in your documents directory, or various other schemes as well.
Like Duncan suggested, a Singleton pattern might be the best route to go. If you place the shared data into a model class, you can create a class method that can be used to acquire a singleton object.
MyModel.m
#implementation MyObject
- (id) init
{
return nil; // We force the use of a singleton. Probably bad practice?
}
// Private initializer used by the singleton; not included in the header file.
- (id)initAsSingleton {
self = [super init];
if (self) {
// Initialize your singleton instance here.
}
return self;
}
+ (MyModel *)sharedMyModel {
static MyModel *myModel = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
myModel = [[MyModel alloc] initAsSingleton];
});
return myModel;
}
MyModel.h
#interface MyModel : NSObject
+ (MyModel *)sharedMyModel; // Singleton instance.
#end
This does not protect against you using [[MyModel alloc] init];. It returns a nil object which is probably poor programming on my end, but it does force you to use the singleton object instead. To use in each one of your view controllers, you just use the following line to grab the singleton instance.
MyModel *model = [MyModel sharedMyModel];
Store the data into it, and return to your other view controller and grab the singleton again. You'll have all of your data.
After thinking about it, you could also force the default initializer to just return your singleton instance like:
- (id)init {
return [MyModel sharedMyModel];
}

Object creation of views and share data between view in iOS

I am new to iOS.I am recently stuck with a problem.
I have a view A and View B. View A has a navigation controller. view A has a button to switch to B.When i am clicking this button every time B creates a new object. how can i track this object to share data between this two view.
Thanks
There are several ways to do this.
You could have a property of B, that A sets before you push. (NSDictionary, Array, String etc)
This not the best way however it would work.
UIViewController *viewB = [[UIViewController alloc]init];
[viewB setMyProperty:#"some data!"];
[self.navigationController pushViewController:viewB animated:YES];
You could also use NSNotificationCenter to pass the object to the next view.
NSDictionary *dictionary = [NSDictionary dictionaryWithObject:
[NSNumber numberWithInt:index]
forKey:#"index"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"myNotification"
object:self
userInfo:dictionary];
The way I usually handle this is to setup and object that holds my data with an associated protocol initialized in my AppDelegate. Then any view that needs to read/write something just grabs a Pointer to that object and runs with it.
#class AppData;
#protocol AppDataProtocol
- (AppData*)theAppData;
#end
in the View you can grab the pointer with this.
-(AppData*)theAppData {
id<AppDataProtocol> theDelegate = (id<AppDataProtocol>)[UIApplication sharedApplication].delegate;
AppData* theData = (AppData*)theDelegate.theAppData;
return theData;
}
and this.
appData = [self theAppData];
You are then able to easily access any property of appData.
-(void)fnButtonA{
ViewB *vcB = [[ViewB alloc] initWithData:DataToB];
[[self navigationController] pushViewController:vcB animated:Yes];
}
In ViewB.m edit the init function to
-(UIViewController *)initWithData:(NSMutableDictionary*)data

io looking for insert and cancel pattern

I've been looking around for a good pattern to implement a insert then cancel pattern when working with a UINavigationBar and UITableView.
I have I have a "insert"button in my TeamsViewController navigation bar (screenshot)
Which when I run it runs this code:
-(void)insertTeam
{
if( !detailViewController ) {
detailViewController = [[TeamDetailViewController alloc] init];
}
if( !teams ) {
teams = [NSMutableArray array];
}
Team *team = [[Team alloc] init];
[teams addObject:team];
int lastIndex = [teams count];
[detailViewController setEditingTeam:[teams objectAtIndex:lastIndex - 1]];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:detailViewController];
[self presentModalViewController:navigationController animated:YES];
}
Which is great if the user fills out all the info, but if they hit cancel on the next view, there's an empty object in my arrary.
I'm sure there's a great pattern to achieve this but I've looked at all the TableView sample codes, two different ios books, and tried googling it, but haven't found a pattern for this.
My thought is something like the following:
When user cancels, set a canceled ivar in my Team object to YES
Back in my TeamsViewController, when the view appears check the last object in my teams array and see if it's property canceled is YES, if so remove that last object.
But this doesn't seem so slick and I was figuring there was some better way to achieve this. TIA.
I would be tempted to make the TeamsViewController a delegate of the TeamDetailViewController. The delegate would implement a method such as - (void)teamCreated:(Team *)team; and it would update the array. Since there seems to be no point to having a Team in the array that's incomplete, I would have the TeamDetailViewController create the Team and pass it back in the delegate call. On a cancel, there would be no need to do anything except pop the controller.

Resources