Is it possible to force rebuild previous view of a Navigation Controller - objective-c

I have a table view in my previous view which is get data from an array in my app. I have a view to update data which is push on cell select. Once data is updated in the view i call
[self.navigationController popViewControllerAnimated:YES];
to go back to previous view. But the label get stacked with old and new data I don't know why... If I go one view back and come back again to the tableview everything is fine only new data is shown..
So I guess I have to rebuild view to avoid the problem. Is this possible ?

Your question initially asked about rebuilding a controller, so here's the answer to that:
I'm assuming that your have a navigation stack like this:
An instance of FirstController
An instance of SecondController
An instance of ThirdController
A thing happens in your third controller, and you now want the stack to look like this:
An instance of FirstController
A new instance of SecondController
The first thing to do is to define a delegate protocol for ThirdController, in your header file like this:
#protocol ThirdControllerDelegate;
#class ThirdController : UIViewController
#property (nonatomic, weak) id<ThirdControllerDelegate> delegate;
... your existing stuff ...
#end
#protocol ThirdControllerDelegate <NSObject>
- (void)thirdControllerDidDoTheThing:(ThirdController *)thirdController;
#end
Instead of having the ThirdController pop itself, it should tell its delegate that the thing happened, like so:
[self.delegate thirdControllerDidDoTheThing:self];
You'll also want to define a delegate protocol for SecondController, in the same way, and you'll want to specify that SecondController can act as a delegate for a ThirdController:
#import "ThirdController.h"
#protocol SecondControllerDelegate;
#class SecondController : UIViewController <ThirdControllerDelegate>
#property (nonatomic, weak) id<SecondControllerDelegate> delegate;
... your existing stuff ...
#end
#protocol SecondControllerDelegate <NSObject>
- (void)secondControllerDidDoTheThing:(SecondController *)secondController;
#end
Notice the extra bit in there where we put <ThirdControllerDelegate> after the #class line.
Now we find the part of the SecondController that shows the ThirdController, and have it set the controller's delegate first:
- (void)showThirdControllerAnimated:(BOOL)animated
{
ThirdController *thirdController = [[ThirdController alloc] init];
thirdController.delegate = self;
[self.navigationController pushViewController:thirdController animated:animated];
}
When the SecondController gets the message from the ThirdController, it should pass it on to its delegate, like this:
- (void)thirdControllerDidDoTheThing:(ThirdController *)thirdController
{
[self.delegate secondControllerDidDoTheThing:self];
}
Finally we modify FirstController so that it can act as the delegate to the SecondController:
#import "SecondController."
#class FirstController : UIViewController <SecondControllerDelegate>
When we show the SecondController, we make the FirstController its delegate:
- (void)showSecondControllerAnimated:(BOOL)animated
{
SecondController *secondController = [[SecondController alloc] init];
secondController.delegate = self;
[self.navigationController pushViewController:secondController animated:animated];
}
Finally we implement the SecondController's delegate method to pop to the first controller, then show a new secondController.
- (void)secondControllerDidDoTheThing:(SecondController *)secondController
{
[self.navigationController popToViewController:self animated:NO];
[self showSecondControllerAnimated:NO];
}
Done.
You've since altered your question; in the case you now describe you can follow the steps above to make the SecondController the delegate of the ThirdController, but then inside thirdControllerDidDoTheThing you just reload the data of your SecondController's view; if it's a UITableView or UICollectionView you'd do that with the reloadData method.

you should refresh your table view every time it is going to be shown; otherwise, the old data would be cached.
In the controller that controls the table view:
- (void)viewWillAppear {
[tableView reloadData];
}

Related

Data encapsulation, Instance variable cannot be accessed

I'm having some trouble understanding what classes can read what variables in other classes. I've read to many different things online and cant seem to find anything solid in here. I've literally wasted the past two days trying to get my program to work but no classes can read any other classes variables. Any help will be GREATLY appreciated.
This is my ViewController.h:
#interface ViewController : UIViewController
{
#public
NSString *nameOfLabel;
}
#property (strong, nonatomic) IBOutlet UILabel *firstLabel;
- (IBAction)Switch:(id)sender;
- (IBAction)changeLabel:(UIButton *)sender;
-(NSString *) nameOfLabel;
#end
nameOfLabel is a public variable and should be able to be accessed by an outside class, right?
ViewController.m:
#import "ViewController.h"
#import "NewView.h"
#interface ViewController ()
#end
#implementation ViewController
- (IBAction)Switch:(id)sender {
NewView * new = [[NewView alloc] initWithNibName:nil bundle:nil];
[self presentViewController: new animated:YES completion:NULL];
}
- (IBAction)changeLabel:(UIButton *)sender {
nameOfLabel = #"Test Name";
_firstLabel.text = nameOfLabel;
}
-(NSString *) nameOfLabel {
return nameOfLabel;
}
#end
changeLabel button changes *firstLabel.text to "Test name".
second class is NewView.h:
#import "ViewController.h"
#interface NewView : UIViewController
#property (strong, nonatomic) IBOutlet UILabel *secondLabel;
- (IBAction)changeSecondLabel:(UIButton *)sender;
#end
and NewView.m:
#import "NewView.h"
#interface NewView ()
#end
#implementation NewView
{
ViewController *view;
}
- (IBAction)changeSecondLabel:(UIButton *)sender {
view = [[ViewController alloc] init];
_secondLabel.text = view.nameOfLabel;
}
#end
changeSecondLabel should change secondLabel.text to nameOfLabel which is 'Test name', however, the label actually disappears which makes me think that nameOfLabel cannot be reached. Ive played around with nameOfLabel, making it a #property and then synthesising it, as well as trying putting it in { NSString *nameOfLabel; } under #implementation but I still get the same result.
This line: view = [[ViewController alloc] init]; creates a new ViewController which doesn't know anything about what you may have done to some other ViewController. In your case, it specifically doesn't know that changeLabel: was called on another ViewController before this new one ever existed.
When the second view controller (NewView) is presented, it has no reference to the first view controller (ViewController) and it's data.
Here are a couple of suggestions.
In modern Objective-C I'd recommend using properties instead of exposing a variable.
Look over the naming in general. "ViewController" is not a good name for example.
If the property is part of an internal state of the class, declare it in a class extension.
Before you present the second view controller, set a reference to the string from the first view controller.
Part of ViewController.m:
#interface ViewController ()
#property (copy,nonatomic) NSString *nameOfLabel;
#end
#implementation ViewController
- (IBAction)Switch:(id)sender {
NewView *new = [[NewView alloc] initWithNibName:nil bundle:nil];
new.secondLabel.text = self.nameOfLabel;
[self presentViewController: new animated:YES completion:NULL];
}
First of all please read about coding standards, it's not a good practice to:
Name variables like "new"
Name methods like "Switch"
Name UIViewController like "view" or "NewView"
Regarding logic:
This is all messed up here. What you actually do is you create viewController with nameOfLabel which is empty and is only changed on button press. I assume you press that button so it's changed. Then on switch action you create another viewController and present it. Then from inside that new viewController you create another new viewController which has empty nameOfLabel, get this empty value and put it inside secondLabel.
There are couple of ways you can do to change secondLabel:
Move nameOfLabel to model and read it from there when you want to change secondLabel,
Because your new viewController is child of viewController that keeps nameOfLabel you can access it by calling [[self presentingViewController] nameOfLabel] but make it property first,
Pass nameOfLabel through designated initializer.
Well, if you want a simple demonstration of access of a public ivar, the syntax is:
view->nameOfLabel;
^^
not dot-syntax:
view.nameOfLabel;
(dot-syntax just goes through accessor methods).
I've only seen a handful of warranted edge cases over the years; there's rarely, rarely ever a good reason to make an ivar public (also, protected is also rarely a good choice).

Objective-c proper delegation

I'm new to objective-c and, maybe I haven't grassped the concept of delegation very clearly yet, but i hope to do it by using it. I'm trying to implement a delegation in my app.
Idea is that i have class TableViewController which has NSMutableArray used for TableView initialization. I need to reinitialize this Array from my DropDown class. I'v tried to do that using delegation but failed to do it yet, maybe there is something wrong with it. I could pass TableViewController to DropDown class and edit the table via object. But i'd like to get it done using delegation.
Here is my TableViewController.h
#protocol TableViewControllerdelegate;
#interface TableViewController : UIViewController<UITableViewDataSource,UITableViewDelegate,MFMessageComposeViewControllerDelegate>
{
ControllerType controllerType;
}
#property (retain, nonatomic) IBOutlet UITableView *tableView;
#property (retain, nonatomic) NSMutableArray *dataArray;
#property (retain, nonatomic) NSArray *imageArray;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil andType:(ControllerType)type;
- (void)sendSMS: (NSString *) sms;
#end;
Here is my DropDown.h
#import "TableViewController.h"
#interface DropDownExample : UITableViewController <VPPDropDownDelegate, UIActionSheetDelegate> {
#private
VPPDropDown *_dropDownSelection;
VPPDropDown *_dropDownSelection1;
VPPDropDown *_dropDownSelection2;
VPPDropDown *_dropDownSelection3;
VPPDropDown *_dropDownSelection4;
VPPDropDown *_dropDownDisclosure;
VPPDropDown *_msg;
VPPDropDown *_dropDownCustom;
NSIndexPath *_ipToDeselect;
}
+ (bool) uncheck:(UITableViewCell *) cell andData:(NSString *) data;
- (void)reloadData;
#end
And this is how i try to edit my tableview object array
TableViewController *newControll = (TableViewController*)[UIApplication sharedApplication].delegate;
NSMutableArray *arrayWithInfo = [[NSMutableArray alloc] initWithObjects:AMLocalizedString(#"Status", nil),AMLocalizedString(#"Call", nil),AMLocalizedString(#"Location", nil),AMLocalizedString(#"Control", nil),AMLocalizedString(#"Sim", nil),AMLocalizedString(#"Object", nil),AMLocalizedString(#"Info", nil),nil];
newControll.dataArray = arrayWithInfo;
[arrayWithInfo release];
[newControll.tableView reloadData];
I get it running, but it get's '-[AppDelegate setDataArray:]: unrecognized selector sent to instance after reaching this code.
OK, I am not sure if I got this right but it finally clicked for me what delegation is and why I need it. Hopefully you'll understand too once you read through my scenario.
History
Previously, in my UITabBar app, I wanted to show a custom form view overlaid on top of my view controller to enter name and email.
Later I also needed to show the same custom overlay on top of another view controller on another tab.
At the time I didn't really know what delegation was for, so the first method I used to tackle this problem was NSNotificationCenter. I duplicated the same code to my second view controller and hooked it up to a button press event.
On pressing a button on the second view controller on another tab, it certainly showed my custom overlay, just like my first view controller.
However, this is where the problem starts.
The Problem
I needed to close my custom form view. So using NSNotificationCenter, I posted a notification and the listener callback method for the notification was told to close my custom view.
The problem was, using NSNotificationCenter, all listeners both in my first tab and my second tab responded to the posted notification and as a result, instead of closing just the custom form view overlaid on top of my second view controller, it closed ALL my custom view, regardless of where the custom view was opened from.
What I wanted was when I tap on the "X" button to close my custom form view, I only want it to close it for that single instance of the custom view, not all the other ones I had opened.
The Solution: Delegation
This is where it finally clicked for me - delegation.
With delegation, I tell each instance of my custom form view who the delegate was, and if I was to tap on the "X" button to close my custom view, it only close it for that single instance that was opened, all the other view controllers were untouched.
Some Code
Right, down to some code.
Not sure if this is the best way to do it (correct me if I am wrong) but this is how I do it:
// ------------------------------------------------------------
// Custom Form class .h file
// ------------------------------------------------------------
#protocol MyCustomFormDelegate <NSObject>
// if you don't put a #optional before any method, then they become required
// in other words, you must implement these methods
-(void)sendButtonPressed;
-(void)closeButtonPressed;
// example: these two methods here does not need to be implemented
#optional
-(void)optionalMethod1;
-(void)optioinalMethod2;
#end
#interface MyCustomFormView : UIView
{
...
id<MyCustomFormDelegate> delegate;
}
...
#property (nonatomic, retain) id<MyCustomFormDelegate> delegate;
#end
// ------------------------------------------------------------
// Custom Form class .m file
// ------------------------------------------------------------
...
#implementation TruckPickerView
#synthesize delegate;
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
...
[btnSend addTarget:self selector:#selector(sendEmail) forControlEvent:UIControlEventTouchUpInside];
...
[btnClose addTarget:self selector:#selector(closeForm) forControlEvent:UIControlEventTouchUpInside];
}
return self;
}
-(void)sendEmail
{
// code sends email
...
// ------------------------------------------------------------
// tell the delegate to execute the delegate callback method
//
// note: the implementation will be defined in the
// view controller (see below)
// ------------------------------------------------------------
[delegate sendButtonPressed];
}
-(void)closeForm
{
// ------------------------------------------------------------
// tell the delegate to execute the delgate callback method
//
// note: the implementation will be defined in the
// view controller (see below)
// ------------------------------------------------------------
[delegate closeButtonPressed];
}
// ------------------------------------------------------------
// view controller .h file
// ------------------------------------------------------------
#import "MyCustomFormView.h"
// conform to our delegate protocol
#interface MyViewController <MyCustomFormDelegate>
{
...
// create a single instance of our custom view
MyCustomFormView *customForm;
}
#property (nonatomic, retain) MyCustomFormView *customForm;
// ------------------------------------------------------------
// view controller .m file
// ------------------------------------------------------------
#synthesize customForm;
-(void)viewDidLoad
{
customForm = [[MyCustomFormView alloc] initWithFrame:....];
// tell our custom form this view controller is the delegate
customForm.delegate = self;
// only show the custom form when user tap on the designated button
customForm.hidden = YES;
[self.view addSubview:customForm];
}
-(void)dealloc
{
...
[customForm release];
[super dealloc];
}
// helper method to show and hide the custom form
-(void)showForm
{
customForm.hidden = NO;
}
-(void)hideForm
{
customForm.hidden = YES;
}
// ------------------------------------------------------------
// implement the two defined required delegate methods
// ------------------------------------------------------------
-(void)sendButtonPressed
{
...
// email has been sent, do something then close
// the custom form view afterwards
...
[self hideForm];
}
-(void)closeButtonPressed
{
// Don't send email, just close the custom form view
[self hideForm];
}
You get that error, because (as the error says) you're sending a setDataArray: message to your app delegate (the AppDelegate class).
[UIApplication sharedApplication].delegate;
This will return the delegate of you app. There are a couple of ways to find out which class is your app's delegate, but usually it's called AppDelegate (as in your case) and it's implementing the UIApplicationDelegate protocol too.
You can't simply cast that to a completely different class. If your app delegate has an ivar or property of type TableViewController you have to use accessors to get it. If it's a property, you can use the dot notation. If it's an ivar, you can either implement a getter method that returns the ivar, or make it a property instead.
// assuming your app delegate has a TableViewController property called myTableViewController.
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
TableViewController *tableViewController = appDelegate.myTableViewController;
This will fix the error, but your use of the delegate pattern is wrong too. I don't see where you're using any custom delegates. You forward declare a TableViewControllerdelegate protocol, but I don't see any declaration of it, or I don't see where you're trying to use it.

How to call viewDidLoad after [self dismissModalViewControllerAnimated:YES];

Okay. If you have two viewControllers and you do a modal Segue from the first to the second, then you dismiss it with [self dismissModalViewControllerAnimated:YES]; it doesn't seem to recall viewDidLoad. I have a main page (viewController), then a options page of sorts and I want the main page to update when you change an option. This worked when I just did a two modal segues (one going forward, one going back), but that seemed unstructured and may lead to messy code in larger projects.
I have heard of push segues. Are they any better?
Thanks. I appreciate any help :).
That's because the UIViewController is already loaded in memory. You can however use viewDidAppear:.
Alternatively, you can make the pushing view controller a delegate of the pushed view controller, and notify it of the updates when the pushed controller is exiting the screen.
The latter method has the benefit of not needing to re-run the entire body of viewDidAppear:. If you're only updating a table row, for example, why re-render the whole thing?
EDIT: Just for you, here is a quick example of using delegates:
#import <Foundation/Foundation.h>
// this would be in your ModalView Controller's .h
#class ModalView;
#protocol ModalViewDelegate
- (void)modalViewSaveButtonWasTapped:(ModalView *)modalView;
#end
#interface ModalView : NSObject
#property (nonatomic, retain) id delegate;
#end
// this is in your ModalView Controller's .m
#implementation ModalView
#synthesize delegate;
- (void)didTapSaveButton
{
NSLog(#"Saving data, alerting delegate, maybe");
if( self.delegate && [self.delegate respondsToSelector:#selector(modalViewSaveButtonWasTapped:)])
{
NSLog(#"Indeed alerting delegate");
[self.delegate modalViewSaveButtonWasTapped:self];
}
}
#end
// this would be your pushing View Controller's .h
#interface ViewController : NSObject <ModalViewDelegate>
- (void)prepareForSegue;
#end;
// this would be your pushing View Controller's .m
#implementation ViewController
- (void)prepareForSegue
{
ModalView *v = [[ModalView alloc] init];
// note we tell the pushed view that the pushing view is the delegate
v.delegate = self;
// push it
// this would be called by the UI
[v didTapSaveButton];
}
- (void)modalViewSaveButtonWasTapped:(ModalView *)modalView
{
NSLog(#"In the delegate method");
}
#end
int main(int argc, char *argv[]) {
#autoreleasepool {
ViewController *v = [[ViewController alloc] init];
[v prepareForSegue];
}
}
Outputs:
2012-08-30 10:55:42.061 Untitled[2239:707] Saving data, alerting delegate, maybe
2012-08-30 10:55:42.064 Untitled[2239:707] Indeed alerting delegate
2012-08-30 10:55:42.064 Untitled[2239:707] In the delegate method
Example was ran in CodeRunner for OS X, whom I have zero affiliation with.

Understanding custom Delegate

So I have an app, and in the app there is a tableView, I have a uinavigationbarbutton that presents a modal viewController. When the user hits a go button in the modal interface, I want it dismiss the modal view and get some of the information in the modal view. I will than put that info in the tableView. To do this, I wrote a custom delegate, but it doesn’t work. I included my code below. Thanks for any help.
TrackerMainViewController.h //the tableView
#import "NewItemViewController.h"
#interface TrackerMainViewController : UITableViewController <UITableViewDelegate, DetailDelegate>
TrackerMainViewController.m
-(void)finishedAddingFoodItemFromDetail:(NSDate *)date whatWasEaten:(NSString *)whatFood whichMeal:(NSString *)meal {
NSLog(#"in delegate method here");
[self.tableView reloadData];
[self dismissModalViewControllerAnimated:YES];
}
NewItemViewController.h // the modal view
#protocol DetailDelegate <NSObject>
-(void)finishedAddingFoodItemFromDetail:(NSDate *)date whatWasEaten:(NSString *)whatFood whichMeal:(NSString *)meal;
#end
#interface NewItemViewController : UIViewController {
id <DetailDelegate> _delegate;
}
#property (nonatomic, retain) id <DetailDelegate> delegate;
#end
NewItemViewController.h
#implementation NewItemViewController
#synthesize delegate = _delegate;
//the go button in the modal view
- (IBAction)Go:(id)sender {
[self.delegate finishedAddingFoodItemFromDetail:[NSDate date] whatWasEaten:#"chicken" whichMeal:#"breakfast"];
}
I put a log in both the go button and in the implementation of the delegate in the tableview, but only the go log is being called.
Thanks
In the code you posted, you dont set the delegate. You need to set it similar to this detailView.delegate = self, otherwise it is nil. You can send messages to a nil-object without any warning and error, nothing will happen.

Set delegate to parent view controller

I have three view controllers in my app pushed to the navigation controller. When I'm on the third view controller I want to send a message to the first one. I think a delegate is the way to go here, but I'm not able to set it correctly.
In my third view controller's h-file I have this:
#protocol AddSudokusViewControllerDelegate
- (void)saveSudoku:(Sudoku *)sudoku;
#end
#interface [...]
id<AddSudokusViewControllerDelegate> delegate;
[...]
#property (nonatomic, retain) id<AddSudokusViewControllerDelegate> delegate;
Then I synthesize it in the .m file.
In my first view controller I have this (.h):
#interface SudokusViewController : UITableViewController <AddSudokusViewControllerDelegate>{
[...]
}
- (void)saveSudoku:(Sudoku *)sudoku;
So far so good I think. Now I want to set the delegate of the third view controller to first when I create it in the second controller. I thought I could do it like this, but it doesn't work.
sudokuDetailViewController = [[SudokuDetailViewController alloc] init];
[sudokuDetailViewController setDelegate:[[self navigationController] parentViewController]];
[[self navigationController] pushViewController:sudokuDetailViewController animated:YES];
Am I doing this the right way or is there another approach when you have three controllers like this?
Best Regards
Linus
Try this,
sudokuDetailViewController.delegate = [self.navigationController.viewControllers objectAtIndex:0];