In my app, I am listing core data entries in a tableview. I want to allow the user to edit records using a detail view presented modally as a form view. I am observing peculiar behavior when editing records.
The flow:
User loads tableview with records. -working
User selects a record for editing. -working
User edits record in a view controller presented as a modal form view. -working
User saves edits and dismisses form view. -working
Tableview shows correct changes for the previously edited record. -working
User repeats steps 2 - 4 selecting a different record to edit. -working
Tableview shows correct data for all records. -Not Working.
At step 7, the tableview reverts the display of the first edited record to its original state. Subsequent record edits result in all previous edits reverting to their original state. If the tableview is dismissed and reloaded, the records are correct, showing all the edits.
I have used [tableview reload] in the tableview's ViewWillAppear method, but it does not seem to be fired when the modal form view controller is dismissed.
In my tableviewcontroller code:
-(void)viewWillAppear:(BOOL)animated
{
[self.tableView reloadData];
}
Searching around, I have not found a solution and am hoping someone can point me in the right direction.
Thanks!
When you presenting a modal view, main view controller's view never disappears. So, after you dismiss your modal view, viewWillAppear() won't be called.
You could try to implement a custom delegate function in your modal view, and set it in the main view controller, when your data is updated, fire the delegate function to reload your tableView, which is at your main viewController.
Understand what is delegate function in iOS, and
how to create a delegate function is like this:
In your ModalView.h:
// define the protocol for the delegate
#protocol ModelViewDelegate <NSObject>
- (void) didUpdateData;
#end
#interface ModalView: ViewController {
//create a delegate instance
id delegate;
}
// define delegate instance
#property (nonatomic, assign) id <ModelViewDelegate> delegate;
In your modalView.m:
#synthesize delegate;
and then inside your function, put the delegate function at place where you need to fire, for example:
- (void) updateDataIntoDatabase{
....
//Update work done.
[self.delegate didUpdateData];
//dismiss your modalView;
}
So, in your MainViewController.h,
#import ModalView.h
and
#interface ModalView: ViewController <ModelViewDelegate > {
...
}
Inside your MainViewController.m, you will get a warning saying that you need to implement the delegate function which you already declared. So, set the delegate function, and do the things that you like to do:
- (void) didUpdateData{
[self.tableView reloadData];
}
DON'T forget to set your modelView delegate to self after you instantiate the modalView instance. If not your delegate function won't be fired.
modalView.delegate = self;
Use completion block when you trigger the Modal viewcontroller:
-(void)editModal:(id)sender
{
KDSecondViewController *secondVC = [[KDSecondViewController alloc] init];
[self presentViewController:secondVC animated:YES completion:^{
//-- reload your table view when user dismiss the modal view
[self.tableView reloadData];
}];
}
Related
I'm trying to make a form that one of the filed takes value from a two level selections' result.
The main progress will something like:
EditViewController ===> CategoryViewController (which embedded inside a NavigationController by storyboard and popped up as a modal view) ===> SubCategoryViewController (Which will be pushed to NavigationController).
Now I have a problem. After user tap to select a value in SubCategoryViewController, I'm supposed to dismiss SubCategoryViewController and return the value to EditViewController. But I don't know exactly how.
Please suggest any solution.
Thank you.
EDIT:
Every one of those view controllers should have a public property for a weak reference to a model object that represents whatever is being edited.
So every ____ViewController.h file would have:
#property (weak, nonatomic) CustomItem *item.
in its interface (assuming a strong reference is somewhere in some data store or array of all the items).
When EditViewController is preparing for the segue to show CategoryViewController modally, it should assign that same reference to CategoryViewController's item property after assigning any data entered in EditViewController's form to item:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
//TODO: assign data from controls to item, for example:
//self.item.title = self.titleField.text;
CategoryViewController *vc = (CategoryViewController *)segue.destinationViewController
vc.item = self.item; //pass the data model to the next view controller
}
Likewise for the segue from CategoryViewController to SubCategoryViewController. This ensures that every ViewController is editing the same object in memory. When you dismiss SubCategoryViewController (assuming somewhere in all of this CategoryViewController was already dismissed), viewWillAppear: will be called on EditViewController- there you can refresh any changes made in the modal views to the item property, just like you would when first displaying the view (it's actually the same method that's called):
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.titleField.text = self.item.title;
self.categoryLabel.text = self.item.category;
self.subcategoryLabel.text = self.item.subcategory;
//etc....
}
I have 2 ViewControllers
ViewControllerWithCollectionView (FIRST) and ModalViewControllerToEditCellContent (SECOND)
I segue from FIRST to SECOND modally. Edit cell. Return.
After dismissing SECOND controller, edited cell doesn't get updated until i call
[collection reloadData]; somewhere manually.
Tried to put it in viewWillAppear:animated:, when i check log, it's not called (after dismissing SECOND)
I've tried various solutions, but i can't brake thru (maybe I'm just too exhausted). I sense that I'm missing something basic.
EDIT dismiss button
- (IBAction)modalViewControllerDismiss
{
self.sticker.text = self.text.text; //using textFields text
self.sticker.title = self.titleText.text;// title
//tried this also
CBSStickerViewController *pvc = (CBSStickerViewController *)self.stickerViewController;
//tried passing reference of **FIRST** controller
[pvc.cv reloadData];//called reloadData
//nothing
[self dismissViewControllerAnimated:YES completion:^{}];
}
It's tough to tell from the posted code what's wrong with the pointer to the first view controller that you passed to the second. You should also be able to refer in the second view controller to self.presentingViewController. Either way, the prettier design is to find a way for the first view controller to learn that a change has been made and update it's own views.
There are a couple approaches, but I'll suggest the delegate pattern here. The second view controller can be setup to have the first view controller do work for it, namely reload a table view. Here's how it looks in almost-code:
// SecondVc.h
#protocol SecondVcDelegate;
#interface SecondVC : UIViewController
#property(weak, nonatomic) id<SecondVcDelegate>delegate; // this will be an instance of the first vc
// other properties
#end
#protocol SecondVcDelegate <NSObject>
- (void)secondVcDidChangeTheSticker:(SecondVc *)vc;
#end
Now the second vc uses this to ask the first vc to do work for it, but the second vc remains pretty dumb about the details of the first vc's implementation. We don't refer to the first vc's UITableView here, or any of it's views, and we don't tell any tables to reload.
// SecondVc.m
- (IBAction)modalViewControllerDismiss {
self.sticker.text = self.text.text; //using textFields text
self.sticker.title = self.titleText.text;// title
[self.delegate secondVcDidChangeTheSticker:self];
[self dismissViewControllerAnimated:YES completion:^{}];
}
All that must be done now is for the first vc to do what it must to be a delegate:
// FirstVc.h
#import "SecondVc.h"
#interface FirstVc :UIViewController <SecondVcDelegate> // declare itself a delegate
// etc.
// FirstVc.m
// wherever you decide to present the second vc
- (void)presentSecondVc {
SecondVc *secondVc = // however you do this now, maybe get it from storyboard?
vc.delegate = self; // that's the back pointer you were trying to achieve
[self presentViewController:secondVc animated:YES completion:nil];
}
Finally, the punch line. Implement the delegate method. Here you do the work that second vc wants by reloading the table view
- (void) secondVcDidChangeTheSticker:(SecondVc *)vc {
[self.tableView reloadData]; // i think you might call this "cv", which isn't a terrific name if it's a table view
}
I have a UITableView embedded in a UIView that I can't figure out how to update. When I come to this page with the UITableView embedded in the UIView there's a button that when pressed brings up a modalForm. There is a textField that the user enters a name into and then presses another button to "Create" the object, dismiss the modalForm, and update the app data. However, I'm not sure how to refresh my UITableView data… Is there a way to tell the UITableView to refresh when the modalForm view is dismissed?
EDIT: I know i need to send this message [tableView reloadData]; in order to refresh, but I'm wondering where I can put it so it gets called upon dismissal of the modalForm?
In the view controller that is presenting and dismissing the modal, you should be calling the dismissViewControllerAnimated:completion method to dismiss the modal. The modal should not dismiss itself. You can use a completion block to execute whatever code you would like that will execute when the modal is finished dismissing. Example below.
[self dismissViewControllerAnimated:YES completion:^{
//this code here will execute when modal is done being dismissed
[_tableView reloadData];
}];
Of course don't forget to avoid capturing self strongly in the block.
If you end up having the modal dismiss itself, you will need either a delegate method so the modal can communicate back to the presenting view controller, or a notification sent by the modal and captured by the presenting view controller, or you could implement viewWillAppear: in the presenting view controller. This method will fire everytime the view is about to appear. Which means the very first time as well as after a modal is dismissed and is about to show the view it was presenting over top of.
----------------------------------------------------------------------------------------
Below is an example of writing your own protocol and using it.
MyModalViewController.h
#protocol MyModalViewControllerDelegate <NSObject>
//if you don't need to send any data
- (void)myModalDidFinishDismissing;
//if you need to send data
- (void)myModalDidFinishDismissingWithData:(YourType *)yourData
#end
#interface MyModalViewController : UIViewController
#property (weak) id <MyModalViewControllerDelegate> delegate;
//place the rest of your properties and public methods here
#end
The where ever you want to in your MyModalViewController implementation file, call your delegate method of choice. You should first make sure your delegate actually responds to the selector though.
MyModalViewController.m
if ( [self.delegate respondsToSelector:#selector(myModalDidFinishDismissing)] )
[self.delegate myModalDidFinishDismissing];
In your viewcontroller that is presenting the modal, you need to state in the header file that you conform to the protocol, you need to set the delegate of the modal as the viewcontroller, and make sure you actually implement the delegate method you intend on using.
MyPresentingViewController.h
#interface MyPresentingViewController : UIViewController <MyModalViewControllerDelegate>
MyPresentingViewController.m
myModal.delegate = self;
- (void)myModalDidFinishDismissing {
//do something
[tableView reloadData];
}
You can have a delegate variable in your modal form class. When you dismiss your modal form, you can call something like [delegate performSelector:#selector(modalFormClosed)] which calls the [tableView reloadData].
#interface ModalForm : UIViewController
#property (nonatomic, weak) id delegate;
#end
#implementation ModalForm
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.delegate performSelector:#selector(modalFormClosed)];
}
#end
In your class that uses the ModalForm:
myModalForm.delegate = self;
And make sure you have the modalFormClosed method too:
- (void)modalFormClosed {
[self.tableView reloadData];
}
Or you can send a NSNotification (look into NSNotificationCenter) when your modal form disappears.
I wish I had the reference, but some years ago I saw a note from Apple saying a modal view should never dismiss itself. Instead you call a method on a presenting view controller. In this method the presenter dismisses the modal view controller. Typically I put a category on my UIViewControllers:
#interface UIViewController (Extension)
-(void)simpleDismissAnimated:(BOOL)animated;
#end
#implementation UIViewController (Extension)
-(void)simpleDismissAnimated:(BOOL)animated {
[self dismissViewControllerAnimated:animated completion:nil];
}
Either include it in all your view controller subclasses, or in your pch file.
So have your modal view controller call:
[self.presentingViewController simpleDismissAnimated:YES];
Then in your presenting view controller, override the implementation of simpleDismissAnimated: to something like:
-(void)simpleDismissAnimated:(BOOL)animated {
[_tableView reloadData];
[self dismissViewControllerAnimated:animated completion:nil;
}
That should take care of it.
Try this line:
[self.tableView reloadData];
You can try putting [tableView reloadData] in viewWillAppear method where your tableView is.
I had the same problem. ReloadData didn't work me. I solved it so that i made new method like this:
- (void)insertNewString:(NSString *)text{
if (!_yourArrayWithData) {
_yourArrayWithData = [[NSMutableArray alloc] init];
}
[_yourArrayWithData insertObject:text atIndex:0];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
and when i want to add something to tableView, i use button with action like this:
- (IBAction)SaveNewPhrase:(id)sender {
NSString *mainText = _NewPhraseTextView.text; //I take nsstring from textView
if([mainText isEqual:#""]){
return;
}else{
NSMutableArray *contentFile = [NSArray arrayWithContentsOfFile:docPathContents()];
NSMutableArray *copyContentFile = [[NSMutableArray alloc] init];
//save array with new item
[copyContentFile addObject:mainText];
[copyContentFile addObjectsFromArray:contentFile];
[copyContentFile writeToFile:docPathContents() atomically:YES];
[self insertNewString:mainText]; //refresh tableView
[_NewPhraseTextView resignFirstResponder];
}}
I hope, that it helps.
I am pushing self view to self.navigationcontroller by allocating. I have a tableView on that view so I am changing the content of tableview. But when I am pressing back button (that is automatically created), I am not able to show previous content. Its showing updated content.
Please suggest.
You set code in viewWillAppear method
- (void)viewWillAppear:(BOOL)animated
{
//code set here
}
If you fill the tableView's data in viewWillAppear: or viewDidAppear:, it will reload even if you only press the back button of your top viewController. If you do not want to have your content changed, you are supposed to use initWithNibName: or viewDidLoad: methoads. They are called only at creation time of the view.
Based on the comments on #Kirti's post, You can check if your viewcontroller is being popped by following method, and take some necessary actions for you controller holding table.
-(void) viewWillDisappear:(BOOL)animated
{
if(![self.navigationController.viewControllers containsObject:self])
{
YourControllerWithTable *instance = [self.navigationController.viewControllers objectAtIndex:self.navigationController.viewControllers.count - 1];
instance.loadOldContent = YES;
}
}
In viewWillAppear: of YourControllerWithTable, you can check:
-(void) viewWillAppear:(BOOL)animated
{
if(loadOldContent)
{
//Do your work here
}
}
You don't push UIView instances onto a UINavigationController instance, only instances of UIViewController.
I have a UITableView which modally presents a UIViewController when a cell is tapped. The UIViewController receives data from a model object corresponding to the tapped cell, and displays an interface to edit those data. When the user completes the edits, a button tap dismisses the UIViewController, and writes the edits to the model object.
Will the following code present any memory or design problems?
In presenting UITableView subclass implementation, acting as delegate for presented UIViewController:
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
UINavigationController *navigationController = segue.destinationViewController;
navigationController.delegate = this;
navigationController.dataModel = someDataModel;
}
// delegate callback
- (void) onViewControllerDone: (UIViewController *)controller {
[self.tableView reloadData];
}
In presented UIViewController subclass implementation:
- (IBAction) done: (id)sender {
// directly modify dataModel passed into UIViewController with data from UI
[self.dataModel.someProperty setString: self.textView.text];
[self.delegate onViewControllerDone:self];
}
Something smells funny about passing the data model into the view, and letting the view make the changes. I'm new to Objective-C / iOS development, and not sure if there's a better/preferred way to do this?
In my opinion, there isn't any flaw in what you're doing though there may be a more elegant approach to your problem. One option would be passing back the information to the original controller via delegation, so that for instance once you're done editing a certain field, the delegate is notified of this immediately and makes the required changes to the data model. This way, there's no passing of the model reference back and forth and you actually make sure that only one controller is responsible for editing it's contents. Something like:
- (void)doneEditing {
if ([_delegate respondsToSelector:#selector(fieldChanged:)]) {
[_delegate fieldChanged:self.newFieldValue];
}
}