I'm new to iOS programming and I'm facing a problem
I'm having a problem with custom delegate.
I'm trying to make a simple custom where it return data to the previous view controller and pop the current view controller.
I have 2 navigation view controller
1 - main view controller
2 - Adding
and here is the protocol that is written in the adding view controller
#protocol AddingDelegate <NSObject>
#required
-(void)setInformation:(Adding *)controller withObject:(Conference *)info;
and here is the where I called it in adding view controller
-(IBAction)addingConference
{
NSLog(#"Adding Button Pressed");
conferenceObject = [[Conference alloc]init];
conferenceObject.name = [NameTX text];
conferenceObject.city = [CityTX text];
conferenceObject.description = [Dectription text];
NSMutableArray *info = [[NSMutableArray alloc] init];
[info addObject:conferenceObject];
[self.delegate setInformation:self withObject:conferenceObject];
NSLog(#"adding Conference method is done");
}
I wrote the delegate at the interface in the main view controller
#interface MainViewController : UITableViewController <AddingDelegate>
#end
and here where I declared the delegate method
-(void)setInformation:(Adding *)controller withArray:(NSMutableArray *)info
{
NSLog(#"in the main view at the delegate");
[self.navigationController popToRootViewControllerAnimated:YES];
NSLog(#"Should be popped right now");
}
and this is the prepare for segue method
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"AddObject"]) {
UINavigationController *navigation = segue.destinationViewController;
Adding *addingViewController = [[navigation viewControllers]objectAtIndex:0];
addingViewController.delegate = self;
}
}
now the problem is when I push the adding on top of the stack and then fill the information and press done the adding view controller doesn't pop to show main view controller.
I tried to log everything and the logs from the main view controller doesn't show .
Please help me
What I notice here is that in the implementation of prepareForSegue:sender: the segue's destinationViewController is a navigation controller. This makes me think that your segue is not pushing the AddingController on the current navigation stack but it's presenting a new one instead. This means the new navigation controller containing the AddingController is presented modally and as such, when you try to pop the navigation stack nothing seems to happen because you're operating on the wrong navigation stack. If that is the case you have two options: 1. change [self.navigationController popToRootViewControllerAnimated:YES]; for [self dismissViewControllerAnimated:YES completion:nil]; or 2. change the segue to be a push segue instead of a modal segue and point the segue directly to the AddingController.
In Adding.m
#class Adding;
#protocol AddingDelegate
- (void)flipsideViewControllerDidFinish:(FlipsideViewController *)controller;
#end
#interface Adding : UIViewController
#property (weak, nonatomic) id <AddingDelegate> delegate; // have you forgot this one
#end
and use
[self dismissViewControllerAnimated:YES completion:nil];
You need dismiss if you want get back to previous screen and make sure you have added Navigation controller
Related
I was wondering how I would be able to update a view controller property of the presenting view controller from a method called inside the modal view controller? Right now, the only way I could think of is using NSNotificationCenter, and while this works for Dictionary items, I couldn't figure out how to use it for a custom object.
For example, my presenting view controller HomewViewController has a Parse PFObject called homeSelections, which the could be updated in the modally presented ModalViewController's property newSelections (and also a PFObject) . After the user makes her selections, I would like HomeViewController's homeSelections to also have the latest data passed from the modal view controller.
Help is appreciated - thanks.
Update 1: here is what I have done now (note that I am using a stripped down example to test things out)
In ViewController (this is the parent/presenting view controller)
#interface ViewController ()
#property (strong, nonatomic) NSArray *totalRamen;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.totalRamen = #[#"ramen1", #"ramen2", #"ramen3", #"moot"];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(#"self.totalRamen: %#", self.totalRamen);
NSLog(#"Done");
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"showModal"]){
ModalViewController *destinationVC = (ModalViewController *)segue.destinationViewController;
destinationVC.passedRamen = self.totalRamen;
}
}
- (IBAction)showModalAction:(UIButton *)sender
{
ModalViewController *destinaionViewController = [[ModalViewController alloc] init];
destinaionViewController.selectionCallback = ^(id selectedItem) {
self.totalRamen = (NSArray *)selectedItem;
NSLog(#"self.totalRAmen %#", self.totalRamen);
NSLog(#"done");
};
[self performSegueWithIdentifier:#"showModal" sender:self];
}
In ModalViewController (this is the presented/modal view controller)
#interface ModalViewController : UIViewController
#property (strong, nonatomic) NSArray *passedRamen;
- (IBAction)dismissModal:(UIButton *)sender;
typedef void(^CallbackBlock)(id value);
#property (nonatomic, copy) CallbackBlock selectionCallback;
#end
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
NSLog(#"Passed ramen %#", self.passedRamen);
NSLog(#"Done");
self.passedRamen = #[#"moot is awesome"];
NSLog(#"new ramen: %#", self.passedRamen);
NSLog(#"%#", self.selectionCallback); //nil here
//call back
CallbackBlock selectionCallback = self.selectionCallback;
if (selectionCallback){
selectionCallback(self.passedRamen); //I want to send the newly updated self.passedRamen back
} else {
NSLog(#"No show"); //Means if isn't called
}
}
- (IBAction)dismissModal:(UIButton *)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
}
Pass a callback block into the presented view controller. This way the presented view controller doesn't know anything about the view controller presenting it. Much more flexible because now anybody can present your view controller, they just pass it a block!
PresentingViewController
PresentedViewController *vc = [[PresentedViewController alloc] init]; //or get your existing one
vc.selectionCallback = ^(id selectedItem) {
//update selected items here
};
//present vc here
PresentedViewController
typedef void(^CallbackBlock)(id value);
#property (nonatomic, copy) CallbackBlock selectionCallback;
- (void)somethingWasSelected:(id)selectedItem {
CallbackBlock selectionCallback = self.selectionCallback;
if (selectionCallback) selectionCallback(selectedItem);
}
Beware of retain cycles. This block is being retained by the presented view controller so references to the presented view controller in the block without weakifying it first will create a leak. More info on this can be found here.
After the user makes her selections, I would like HomeViewController's
homeSelections to also have the latest data passed from the modal view
controller.
The easy way is to avoid having ModalViewController update HomeViewController at all. Turn the communication around -- have HomeViewController query ModalViewController and update itself when the modal is dismissed.
HomeViewController already depends on ModalViewController -- it has to know about ModalViewController in order to present it. So there's no harm in having it also know how to read the newSelections property out of ModalViewController at the appropriate time. ModalViewController, on the other hand, has no need to know anything about where its information goes. It doesn't need to know about HomeViewController in order to do its job. If you avoid telling ModalViewController anything about HomeViewController, you can easily use ModalViewController from some other view controller should the need for that ever arise. More importantly, you avoid ever needing to update ModalViewController if HomeViewController changes.
For example, your HomeViewController might look (in part) like this:
- (void)showModalViewController
{
self.modalViewController = [[ModalViewController alloc] init]; // or otherwise get the modal controller
[self presentViewController:self.modalViewController
animated:YES
completion:];
}
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
{
self.homeSelections = self.modalViewController.newSelections;
[super dismissViewControllerAnimated:flag completion:completion];
self.modalViewController = nil;
}
I have two view controllers inside a Navigation Controller.
In the first view controller I have two buttons. Both of them call the second view controller using a Push segue, but:
I need to know which button sent me in the second view controller. How?
In the second view controller I have a UIDatePicker and a Button "Ok": how can I send the chosen date to the first view controller when Ok is pressed? (And how do I receive them?)
EDIT:
I don't know if my problem is clear: now I know how to pass data from the first view controller to the second view controller with prepareForSegue, but what I really need is to pass data (the picked date) from the second view controller to the first, and how can I do it without a prepareForSegue (when Ok is pressed)?
EDIT2:
I made it. It was so simple, guys...
I decided to use modal segue:
Firstviewcontroller.h:
+(FirstViewController *)getInstance;
Firstviewcontroller.m:
static FirstViewController *instance =nil;
+(FirstViewController *)getInstance
{
return instance;
}
and in its ViewDidLoad:
instance = self;
Secondviewcontroller.m, in the OkButton IBAction:
SecondViewController *secondViewController = [SecondViewController getInstance];
//...
//modify what I need to modify in secondviewcontroller
//...
[self dismissModalViewControllerAnimated:YES];
That's it.
Thank you all anyway.
Assign Identifier to each segue in storyboard and implement
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Make sure your segue name in storyboard is the same as this line
if ([[segue identifier] isEqualToString:#"YOUR_SEGUE_NAME_HERE"])
{
// Get reference to the destination view controller
YourViewController *vc = [segue destinationViewController];
[vc setDelegate:self];
// Pass any objects to the view controller here, like...
[vc setMyObjectHere:object];
}
}
For more info about How to use storyboard and pass value check this article or this discussion on stackoverflow
for the second question you can use delegate pattern
IN SecondViewController.h
#protocol SomethingDelegate <NSObject>
- (void)dateChanged:(NSString *)dateStr; //you can use NSDate as well
#end
#interface ViewController2 : UIViewController
#property(weak) id<SomethingDelegate> delegate;
#end
in .m file
-(void) OkClicked{
[_delegate dateChanged:#"YOUR_DATE_VALUE"];
}
In FirstViewController.h
#import "SecondViewController.h"
#interface FirstViewController : UIViewController<SomethingDelegate>
in .m
-(void)dateChanged:(NSString *)dateStr{
// do whatever you need with dateStr
//also i made some change in prepareForSegue method
}
Note:- take care your naming convenes for VC
just pass the button id to the second viewcontrol.
use delegates to sent the data from second viewcontroller back to first view controller
regards
Johan
I have started working with segue to perform navigation between viewcontrollers. This is how i was performing push segue from EPSearchResultController to destination viewcontroller.
//.h file
#property (strong, nonatomic) EPSearchResultController *obj_EPFirstViewController;
//.m File
- (IBAction)submitEPSearchQuery:(id)sender {
[self performSegueWithIdentifier:#"searchResultSegue" sender:sender];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"searchResultSegue"])
{
self.obj_EPFirstViewController = (EPSearchResultController*)[segue destinationViewController];
self.obj_EPFirstViewController.searchAgainDelegate = self;
NSDictionary* resultDict= [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle]pathForResource:#"SearchResult" ofType:#"plist"]];
self.obj_EPFirstViewController.searchResultArray = [resultDict objectForKey:#"Result"];
}
}
Problem:
When i am moving back from my destination to EPSearchResultController its showing me black screen. I am moving back by using
#pragma mark - Search Again Delegate
-(void)moveBack{
[self.navigationController popViewControllerAnimated:YES];
}
Anybody having any idea why its showing me a black screen.
1) Check presentation style of a segue in Storyboard. It can be:
Push
Modal
Custom
2) Use correct dismiss method.
if Push, use [self.navigationController popViewControllerAnimated:YES]
if Modal, [self dismissViewControllerAnimated:YES completion:];
Thanks for your support...from your answers i got the idea that there is some problem with segue formation. I found it out that i made segue from button on EPSearchResultController to destination viewcontroller. But it should be from EPSearchResultController to destination viewcontroller. :-)
I Have three View controllers.
First UIViewController contain one UIImage, when I click "crop" button, go to second view controller I pass the image by push view controller, here I'am cropping my UIImage.
In second UIViewController which is inherited from image editor Viewcontroller after cropping my image, clicking "done" button.
I need to pass the cropped image to another view controller where i am having share button to share the post.
Problem is when I am puishing the editted image to another view controller in which share button is present,image is not passing.
Delegation
You implement the protocol on your View Controller
In Controller.h:
#class Controller;
#protocol ControllerDelegate <NSObject>
- (void)sendFrom:(Controller *)controller
image:(UIImage *)image;
#end
#interface Controller : UIViewController
#property (weak, nonatomic) NSObject <ControllerDelegate> * delegate;
#end
In Controller.m when you want send the image:
[self.delegate sendFrom:self
image: self.image];
After on your Detail View Controller, don't forget to assign it as delegate of the Controller. And to implement the ControllerDelegate protocol. To do that:
In your DetailController.h implement the protocol. You can do that on your .m as well.
#import "Controller.h"
#interface DetailController : UIViewController <ControllerDelegate>
You keep a reference of the Controller in The Detail Controller
#property (nonatomic, strong) ViewController *controller;
And assign its delegate in the viewDidLoad of the Detail Controller. For example:
-(void)viewDidLoad:(BOOL)animated{
[super viewDidLoad:animated];
self.controller.delegate = self;
}
Don't forget to implement the protocol method in your Detail View. Here you receive the image you passed from Controller and you save it in your Detail Controller UIImage property:
- (void)sendFrom:(Controller *)controller
image:(UIImage *)image{
self.image = image;
}
You are saving the image in the Detail Controller before you make the push?
DetailController *detailVC=[[DetailController alloc]
initWithNibName:#"DetailController"
bundle:nil];
detailVC.image = self.image;
[self.navigationController pushViewController:detailVC animated:YES];
You can implement storyboard segue to your detail controller and use following sample code.
- (void)cropButtonClick:(id)sender
{
[self performSegueWithIdentifier: #"MySegue" sender:sender];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"MySegue"]) {
DetailController *detailVC = segue.destinationViewController;
[detailVC setImage:self.image];
}
}
In my Popover controller, i'm having a table view. On selection of a cell, I want to hide the pop over.
How can I achieve it.
In Header file of Root view controller:
#property (strong, nonatomic) UIStoryboardPopoverSegue* popSegue;
In the implementation file:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if( [[segue identifier] isEqualToString:#"popover"] )
{
NSLog(#"%#",[segue destinationViewController]);
self.popSegue = (UIStoryboardPopoverSegue*)segue;
[[segue destinationViewController] setDelegate:self];
}
}
When ever you want to hide the pop over:
if ([self.popSegue.popoverController isPopoverVisible])
{
[self.popSegue.popoverController dismissPopoverAnimated:YES];
}
In the table view, add a delegate and implement the delegate in root view controller. When the delegate method is called, use above code to dismiss the pop over.
Allow me to suggest a slightly different solution, which consists in passing the popover controller reference instead of the segue reference.
In the implementation file of the source view controller:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue destinationViewController] isKindOfClass:[MyDestViewController class]]) {
MyDestViewController* viewController = (MyDestViewController*)[segue destinationViewController];
UIStoryboardPopoverSegue* popoverSegue = (UIStoryboardPopoverSegue*)segue;
[viewController setPopoverController:[popoverSegue popoverController]];
}
}
In the header file of the destination view controller:
#property (weak, nonatomic) UIPopoverController* popoverController;
In the implementation file of the destination view controller:
#synthesize popoverController;
Same file, whenever you want to dismiss the popover:
[popoverController dismissPopoverAnimated:YES];
The apple docs recommend the following:
Dismissing a popover programmatically requires a pointer to the popover controller. The only way to get such a pointer is to store it yourself, typically in the content view controller. This ensures that the content view controller is able to dismiss the popover in response to appropriate user actions.
http://developer.apple.com/library/ios/#documentation/WindowsViews/Conceptual/ViewControllerCatalog/Chapters/Popovers.html
in didSelectRowAtIndexPath try this code
[viewController.popoverController dismissPopoverAnimated:YES];