I currently have two view controllers and I linked them up via storyboard segue(navigation controller). When the user presses a button on the first view, the screen goes to the second view. In the second view, there are text fields so that the user can edit the detailed information. When I press the back navigation button, how can I pass that detailed information from the second view controller back to the first view controller?
I use this code to send data from the first view controller to the second view controller
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
SettingsViewController *settingsViewController = [segue sourceViewController];
[settingsViewController setAddressString:[self addressString]];
NSLog(#"Settings address string of SettingsViewController to: %#", [self addressString]);
}
The only way I can think of right now of passing the data from the second view controller to the first view controller is to override the viewWillDisappear method of the second view controller. But is there a way that works by using the segue method? And what is the recommended way of doing this?
You could use delegation.
In your second view controller header file you'll have to create a delegate method.
#protocol SettingsDelegate <NSObject>
- (void)userHasCompletedSettings:(NSArray *)userSettings;
#end
#interface SettingsViewController : UIViewController
#property id<SettingsDelegate>delegate;
// Any other properties you have
#end
In your first view controller when calling your segue you set the delegate to self.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
SettingsViewController *settingsViewController = [segue sourceViewController];
[settingsViewController setAddressString:[self addressString]];
NSLog(#"Settings address string of SettingsViewController to: %#", [self addressString]);
settingsViewController.delegate = self;
}
In your second view controller you need to call the method. You can implement your own Back button so that you can set up an IBAction to call your delegate method.
- (IBAction)backButtonPressed:(id)sender
{
// Package up your settings details in the array
[self userHasCompletedSettings:arrayOfMySettings];
[self dismissViewControllerAnimated:YES completion:nil];
}
You also need to set up the method for the backButtonPressed action to call on the first view controller.
-(void)userHasCompletedSettings:(NSArray *)userSettings
{
// Do what you need to do with the settings.
}
You can pass the detail controller a reference to the master view controller (i.e. self), and let it call methods to set the results.
For example, you can do this:
#interface MasterController : UIViewController
-(void)updateAddressString:(NSString*) address;
#end
#interface DetailController : UIViewController
-(void)setAddressString:(NSString*) address;
#property (nonatomic, readwrite) MasterController *master;
#end
Now you can write this:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
SettingsViewController *settingsViewController = [segue sourceViewController];
[settingsViewController setAddressString:[self addressString]];
[settingsViewController setMaster:self];
NSLog(#"Settings address string of SettingsViewController to: %#", [self addressString]);
}
When the DetailController needs to update the address in the master, it does this:
[_master updateAddressString:updatedAddress];
You can create a class which represents your model object with properties that correspond to fields in SettingsViewController.
You create it and pass to your SettingsViewController in prepareForSegue method and assign it to property/ivar of the current viewController. So you have a model object instance shared between two viewControllers. When you enter information into fields - assign it to your custom model object properties.
Your first viewContoller can get information from properties of the shared model object.
Related
iOS - How can I pass information between two view Controllers linked through a Manual Segue?
I have two View Controllers each assigned to their respective Views in the Storyboard. I Control-Click Drag from on to the other and select a "manual" "push" segue.
From what I understand I can execute this segue form anywhere in the code by giving it an identifier and using:
performSegueWithIdentifier:#"identifierA" sender:self
What would be the best way to send information about something that was selected to the newly created View Controller?
Whenever you perform a segue, this method - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender notifies the controller that a segue is about to be performed.
So from this method you get to know about the destination view controller and you can set any property/ pass value to any property of destination view controller.
Something like this.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
if([segue.identifier isEqualToString:#"yourIdentifier"] ){
YOUR_Destination_Controller *vc = (YOUR_Destination_Controller *) segue.destinationViewController;
vc.someProperty = YES; //in example someProperty is a BOOL
}
else {
//Do whatever accordingly
}
}
Hope this helps
I am slightly confused about your question.
Let's assume you have VC1 that opens VC2.
If you want to pass information from root controller(vc1) to new one(vc2)
With segues best you can do is create public property in VC2 and set it before method executes. You can attach just before method executes in prepareForSegue method. So implementation will be something like this:
//
// VC1.m
// StackOverflow
#import "VC1.h"
#import "VC2.h"
#implementation VC1
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if( [segue.identifier isEqualToString:#"yourSegueId"] )
{
if( [segue.destinationViewController isKindOfClass:[VC2 class]] )
{
[(VC2*)segue.destinationViewController setMyPassedString:#"YOUR STRING FROM VC1"];
}
}
}
#end
//
// VC2.h
// StackOverflow
#import <UIKit/UIKit.h>
#interface VC2 : UIViewController
#property(nonatomic, strong) NSString* myPassedString;
#end
I personally don't like this approach as you are creating public properties on VC2, which may not be needed at all. However this is a limitation on how storyboard works, and only way to avoid this is to use good old fashioned xib's and designated initializers where you can put your params.
If you want to pass information from new controller(vc2) back to root(vc1)
Here you could basically use two approaches: by passing weak reference to vc2, store it, and then use it when needed to update something on vc1. This is called delegate pattern, however it could be used in much much more powerful and encapsulated way called BLOCKS.
Here is simple implementation with blocks:
//
// VC2.h
// StackOverflow
#import <UIKit/UIKit.h>
#interface VC2 : UIViewController
#property(nonatomic, copy) void(^vc1UpdateBlock)(NSString* string);
#end
//
// VC2.m
// StackOverflow
#import "VC2.h"
#implementation VC2
-(void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
_vc1UpdateBlock(#"PUT YOUR PASSED STRING HERE");
}
#end
//
// VC1.m
// StackOverflow
#import "VC1.h"
#import "VC2.h"
#implementation VC1
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if( [segue.identifier isEqualToString:#"yourSegueId"] )
{
if( [segue.destinationViewController isKindOfClass:[VC2 class]] )
{
[(VC2*)segue.destinationViewController setVc1UpdateBlock:^(NSString * stringFromVC2) {
NSLog(#"I'm printing in VC1 string %#, passed from VC2", stringFromVC2);
}];
}
}
}
#end
Again if you use xib files directly you can use designated initializers and hide block property, however with storyboards you must create your block publicly available.
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 want to create a game which is basically ask your buddy to put a word. And you will guess what word has been entered. For that reason, I have to pass guesword from FirstViewController to SecondViewController.
First View Controller
- (IBAction)doneBtn:(id)sender {
self.guessWord = [enterTF.text lowercaseString];
NSLog (#"Guessword s %#", guessWord);
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Make sure your segue name in storyboard is the same as this line
NSLog (#"%#", [segue identifier]);
if ([[segue identifier] isEqualToString:#"F2S"])
{
// Get reference to the destination view controller
SecondViewController *gvc = [segue destinationViewController];
// Pass any objects to the view controller here, like...
gvc.guessWord =self.guessWord;
}
}
Second View Controller
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(#"%#",guessWord);
}
In the SecondViewController guessword output "null". Why am I getting null?
In the second view controller, declare your variable in the .h file, say a string type:
#property (nonatomic, strong) NSString *someText;
Synthesize it in the .m file:
#synthesize someText;
In the first view controller's segue method:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Make sure your segue name in storyboard is the same as this line
NSLog (#"%#", [segue identifier]);
if ([[segue identifier] isEqualToString:#"F2S"])
{
// Get reference to the destination view controller
SecondViewController *gvc = [segue destinationViewController];
gvc.someText = **YOUR TEXT HERE** ; //this should set the value in the second view controller
}
}
If the passed value is not a string, change accordingly.
Here is the sequence of events happening in your code:
The segue object is created. Source and destination view controllers are set on the segue, causing the second view controller to be loaded.
-viewDidLoad: is called on the second view controller.
-prepareForSegue: is called on the first view controller.
-viewWillAppear: and -viewDidAppear: are called on the second view controller.
-viewDidLoad: is called before guessWord is configured in -prepareForSegue:. Try -viewWillAppear: or -viewDidAppear: instead when logging guessWord.
The relevant part of my storyboard appears as follows:
You can see the custom "Container Controller" view houses two Container Views, one which links to a Navigation Controller via embedded segue, and another that links to a custom "Master View Controller" (which implements a Table View Controller) via embedded segue. The Navigation Controller component further has a relationship with a custom "Location Filter Controller."
I need to implement delegation such that when one of the UISteppers in the Location Filter Controller is incr./decr., the table view in the Master View Controller knows to update the data it displays accordingly.
I am not unaccustomed to working with protocols/delegates, but this unique situation of talking between views housed in segues is really tricking me! For the most part I have had success following the example here: Passing Data between View Controllers. In this case however, I am not able to directly link instantiated views as he indicates to do in 'Passing Data Back' step 6.
I had considered using a singleton object from which each of these views could get/set the necessary data, but the issue here is that the table view would not necessarily know when to update its contents, despite having data with which it could/should update.
Here is a code snippet from ContainerController.m where I setup the embedded segues to function:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
DataHold *data = [[DataHold alloc] init]; // <-- this actually is a singleton object
if([segue.identifier isEqualToString:#"locationEmbedSegue"])
{
}
else if([segue.identifier isEqualToString:#"tableEmbedSegue"])
{
[[segue destinationViewController] setDelegate:data.detailTableViewController];
// ^ This part actually sets up a delegate so that the table view (Master View Controller)
// delegates to the detail view controller of the overarching split view controller
// and tells it what to display when a row is pressed.
}
}
Thanks for any help!
I think you are on the right track setting the table view delegate to your Location Filter Controller.
I found that a simple way to work with embeded view controller is to add "placeholders" property for them, and set these property when the segue is "performed".
// MyContainerController.h
#property (strong, nonatomic) MyLocationFilterController *detailViewController;
#property (strong, nonatomic) UITableViewController *masterViewController;
// MyContainerController.m
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"locationEmbedSegue"])
{
UINavigationViewController *dest = (UINavigationViewController *)segue.destinationViewController;
self.detailViewController = dest.topViewController;
}
else if([segue.identifier isEqualToString:#"tableEmbedSegue"])
{
self.masterViewController = (UITableViewController *)segue.destinationViewController;
[self.masterViewController.tableView setDelegate:self.detailViewController];
}
}
I came to this question recently and found there may be one problem with the answer above.
Move the setDelete: method out. This makes sure no controller is nil.
Then code becomes:
// MyContainerController.h
#property (strong, nonatomic) MyLocationFilterController *detailViewController;
#property (strong, nonatomic) UITableViewController *masterViewController;
// MyContainerController.m
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"locationEmbedSegue"])
{
UINavigationViewController *dest = (UINavigationViewController *)segue.destinationViewController;
self.detailViewController = dest.topViewController;
} else if([segue.identifier isEqualToString:#"tableEmbedSegue"])
{
self.masterViewController = (UITableViewController *)segue.destinationViewController;
}
[self.masterViewController.tableView setDelegate:self.detailViewController];
}
Does unwinding a storyboard segue in ios6 replace the need to make a source scene implement a delegate to pass data back from the child scene to the parent scene in ios5?
The way I usually do it is:
Parent Controller Header:
Call the Delegate of the child scene
#interface ParentViewController : UIViewController <ChildViewControllerDelegate>
//ok not much to show here, mainly the delegate
//properties, methods etc
#end
Parent Controller Main (body):
Prep the segue, set the delegate, create a return method from child scene
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"toChildScene"])
{
UINavigationController *childViewController = segue.destinationViewController;
childViewController.delegate = self;
}
}
#pragma mark - Delegate Segue Methods
-(void) childViewControllerDidSave: (ChildViewController *) controller Notes:(NSString *)sNotes
{
someTextLabel.Text = sNotes
[self dismissModalViewControllerAnimated:YES];
}
Child Controller Header:
create the delegate, reference the parent scenes methods
#class ChildViewController;
#protocol ChildViewControllerDelegate <NSObject>
-(void) childViewControllerDidSave: (ChildViewController *) controller Notes:(NSString *)sNotes
#end
#interface ChildViewController : UIViewController
#property (weak, nonatomic) id <ChildViewControllerDelegate> delegate;
//properties, methods, etc
#end
Child Controller Main (body):
call the parent scenes method
- (IBAction)someAction:(id)sender
{
[self.delegate childViewControllerDidSave:self sNotes:someTextField.text];
}
So now the million Dollar question:
Is this process now simpler in iOS 6? Can I cut a lot of the work out using unwinding a segue / exit segue? Any example would be greatly appreciated.
Yes.
Unwind segues are an abstracted form of delegation. In iOS 6, it's simpler to use unwinds rather than delegates to pass data backwards when dismissing view controllers.
In the parent view controller, create an unwind method that returns an IBAction and takes a UIStoryboardSegue as the argument:
- (IBAction)dismissToParentViewController:(UIStoryboardSegue *)segue {
ChildViewController *childVC = segue.sourceViewController;
self.someTextLabel.Text = childVC.someTextField.text;
}
Then, in the child view controller, Control-drag from your dismiss button to the green exit icon to connect the unwind segue: