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.
Related
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];
}
I'm developing an app which uses ABPeopleViewController, and i want to when the user finalized choosing a contact, go backward two viewcontroller before.
Here's how i am arriving to ABPeoplePickerNavigationController:
Tap in a button of a main view controller --> load modal (dialog) view controller --> tap in a button of the modal view controller --> load ABContacts.
I'm implementing the delegate of ABContacts in the modal view, which in turn has a delegate in the main view controller.
I want to go back from ABPeoplePicker delegate method to the main view controller.
Hope this understands and someone can help me, i didn't find anything like this.
My MainViewController.h:
#protocol ModalViewDialogDelegate
- (void)didReceiveMail:(NSString *)mail;
#end
#interface SetUpViewController : UIViewController<UITextFieldDelegate, ModalViewDialogDelegate>{
}
//...
My MainViewController.m:
//...
- (void)didReceiveMail:(NSString *)mail{
[self.presentedViewController dismissViewControllerAnimated:YES completion:nil];
//...
My ModalView.h:
#import <AddressBook/AddressBook.h>
#import <AddressBookUI/AddressBookUI.h>
#protocol ModalViewDialogDelegate;
#interface DialogViewController : UIViewController<ABNewPersonViewControllerDelegate, ABPeoplePickerNavigationControllerDelegate>{
id<ModalViewDialogDelegate> delegate;
}
#property (nonatomic, assign) id<ModalViewDialogDelegate> delegate;
#property (nonatomic, retain) NSString * mailSelected;
//...
My modalView.m:
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier{
//...here i get the email person property and then i want to go backwards to the main view controller, not the modal.
[self dismissViewControllerAnimated:YES completion:nil];
//don't know if it's ok like this, because in the implementation also dismiss presented viewcontroller.
[_delegate didReceiveMail:self.mailSelected];
return NO;
}
return YES;
}
Try putting this
[_delegate didReceiveMail:self.mailSelected];
inside the completion block of the
[self dismissViewControllerAnimated:YES completion:nil];
that precedes it.
(If that doesnt work you can simply call the dissmiss twice on your maincontroller delegate method, each dismiss will remove one from the stack)
[[[self presentingViewController] presentingViewController] dismissViewControllerAnimated:NO completion:nil];
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.
Like many others I started to code an experiment today where I would have two view controllers and be able to switch between them. I got this to work using a navigation controller, but I have a question about the implementation.
In my TwoViewsAppDelegate, I define the navigation controller and the rootViewController.
#interface TwoViewsAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
UINavigationController *navigationController;
RootViewController *rootViewController;
}
and set them up as follows:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
rootViewController = [[RootViewController alloc] init];
navigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
[window setRootViewController:navigationController];
[self.window makeKeyAndVisible];
return YES;
}
Then in my rootViewController, I define the level2ViewController that I
am going to switch to, and a button that I'm going to press to make the
switch happen:
#interface RootViewController : UIViewController {
UIButton *theButton;
Level2ViewController *level2ViewController;
}
Here's the response to the button being pressed in RootViewController.m:
-(void)level1ButtonPressed:(id)sender
{
if (level2ViewController == nil)
{
level2ViewController = [[Level2ViewController alloc] init];
}
[self.navigationController pushViewController:level2ViewController animated:YES];
}
The problem is that if there was going to be a level3ViewController,
it would have to be defined as a member of level2ViewController, etc.
for however many view controllers i wanted to push onto the stack.
It would be nice to be able to define all the view controllers in one
place, preferably the app Delegate. Is this possible?
To solve this, you can create a callback-type method which uses the delegate of the class that'll be sending the requests for the view controllers. Best explained through code...
RootViewController.h
#import "RootInterfaceView.h"
// all the other VC imports here too
#interface RootViewController : UIViewController <RootInterfaceViewDelegate>
{
RootInterfaceView *interface;
}
RootViewController.m
-(void)rootInterfaceView: (RootInterfaceView*)rootInterfaceView didSelectItem:(NSUInteger)itemTag
{
switch (itemTag)
// then create the matching view controller
}
RootInterfaceView.h
// imports here if required
#protocol RootInterfaceViewDelegate;
#interface RootInterfaceView : UIView <RootInterfaceItemViewDelegate>
{
id <RootInterfaceViewDelegate> delegate;
}
#property (nonatomic, assign) id delegate;
#end
#protocol RootInterfaceViewDelegate <NSObject>
#optional
-(void)rootInterfaceView: (RootInterfaceView*)rootInterfaceView didSelectItem:(NSUInteger)itemTag;
#end
RootInterfaceView.m
// remember to synthesize the delegate
-(void)rootInterfaceItemSelected: (RootInterfaceItemView*)rootInterfaceItemView
{
NSUInteger theTag = rootInterfaceItemView.tag;
if ([self.delegate respondsToSelector:#selector(rootInterfaceView:didSelectItem:)])
[self.delegate rootInterfaceView:self didSelectItem:theTag];
}
Alternatively, if the only options from level 2 were either back to root/pop one VC or to push controller 3, then it'd be fine for level 2 to be importing 3 to allow for it's creation.
I have a modal view which gets the user to select some data to add to a table. When the user presses a save button, the modal view should disappear and send the required data back to the view controller that presented the modal view for further processing. To achieve this, I have set up a protocol. The protocol method in the original view controller does not get called. My code is below, what am I doing wrong?
The header file (modal view controller):
#protocol AddTAFDataSource;
#interface AddTAFViewController : UIViewController <UIPickerViewDelegate, UIPickerViewDataSource> {
id<AddTAFDataSource> dataSource;
NSString *newICAOCode;
}
#property (nonatomic, assign) id<AddTAFDataSource> dataSource;
- (IBAction)saveButtonPressed;
#end
#protocol AddTAFDataSource <NSObject>
- (void)addNewTAF:(AddTAFViewController *)addTAFViewController icao:(NSString *)icaoCode;
#end
The implementation file (modal view controller):
#import "AddTAFViewController.h"
#import "TAFandMETARViewController.h"
#implementation AddTAFViewController
#synthesize dataSource;
...
- (IBAction)saveButtonPressed {
[self.dataSource addNewTAF: self icao: newICAOCode];
}
#end
Presenting view controller header file:
#import "AddTAFViewController.h"
#interface TAFandMETARViewController : UITableViewController <AddTAFDataSource> {
}
#end
And finally, the presenting view controller:
#import "AddTAFViewController.h"
...
- (void)insertNewObject:(id)sender {
AddTAFViewController *addTAFViewController = [[AddTAFViewController alloc] initWithNibName: #"AddTAF" bundle: [NSBundle mainBundle]];
addTAFViewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[(AddTAFViewController *)self.view setDataSource: self];
[self presentModalViewController: addTAFViewController animated: YES];
addTAFViewController = nil;
[addTAFViewController release];
}
- (void)addNewTAF:(AddTAFViewController *)addTAFViewController icao:(NSString *)icaoCode {
newICAO = icaoCode;
[self dismissModalViewControllerAnimated: YES];
}
Just to remind, it is the above -(void)addNewTAF: method that does not get messaged. Any help/pointers in the right direction are much appreciated.
Replace:
[(AddTAFViewController *)self.view setDataSource: self];
With:
[addTAFViewController setDataSource:self]
After all, the dataSource is a property of the controller, not a controller's view.
Rather than trying to use a separate object (your dataSource) to pass data between the two view controllers, you could simply use add properties to contain the data directly in the view controller you're going to present modally (here, the AddTAFViewController).
Then in the method you use to dismiss the modal view controller, before dismissing it you can send [self modalViewController] to get the modal view controller, and at that point the parent view controller can send it any messages it wants. That would allow you to grab whatever data you need from the modal view controller, so you wouldn't need the data source and the protocol at all.
You are wrong at this point:
[(AddTAFViewController *)self.view setDataSource: self];
you should write this instead:
addTAFViewController.dataSource = self;