I have a picker view in a view controller A (as inputView of a textfield).
To enable the user to select a new value (one which is not a row of the pickerview yet), there is a button which presents another view controller B modally, where the user can create a new value. Upon closing, I want the textfield and its inputView pickerView to be updated with the new value.
My pickerview is backed by an NSArray from CoreData. Unfortunately the pickerview isn't updated, when I dismiss the View controller B, although the new value is updated in core data.
How can I achieve that?
One of the good solution is to implement the delegate pattern (a common pattern in Cocoa) :
In ViewControllerB.h declare a ViewControllerBDelegate protocol.
Then in your ViewControllerB interface add a delegate as ivar.
//ViewControllerB.h
#class ViewControllerB;
#protocol ViewControllerBDelegate <NSObject>
#required
- (void)viewControllerB:(ViewControllerB *)controller didChangeValueTo:(NSString *)value;
#end
#interface ViewControllerB : UIViewController
#property (weak, nonatomic) id<ViewControllerB> delegate;
[...]
Then when the value has changed (or when the user validate the change) send the event to the delegate like this :
if ([self.delegate respondsToSelector:#selector(viewControllerB:didChangeValueTo:)])
{
[self.delegate viewControllerB:self didChangeToValue:newValue];
}
In ViewControllerA just do
ViewControllerB *viewController = [...]; //initialization
[viewController setDelegate:self];
and add the method :
- (void)viewControllerB:(ViewControllerB *)controller didChangeValueTo:(NSString *)value
{
[...];//your stuff here
}
Related
I've a TableViewController in which I'm saving the selected cells in an NSMUtableArray. After selecting these cells user clicks on a confirm button and in this button action I'm trying to pass that NSMUtableArray so that I can display it in another viewController tableView
#import <UIKit/UIKit.h>
#protocol SelectedDXDelegate;
#interface AddDXTableViewController : UITableViewController
#property (nonatomic, strong) NSMutableArray *favDXArray;
#property (strong, nonatomic) UISearchController *searchController;
#property (nonatomic, strong) DX *AddEditDX;
#property (weak) id<SelectedDXDelegate> delegate;
- (IBAction)confirmPressed:(id)sender;
#end
#protocol SelectedDXDelegate <NSObject>
#required
-(void)getSelectedDX:(NSMutableArray *)DXselected;
#end
So when confirm button is pressed
- (IBAction)confirmPressed:(id)sender {
[self.delegate getSelectedDX:selectedDX];
[self.navigationController popViewControllerAnimated:YES];
}
it gets me to the
-(void)getSelectedDX:(NSMutableArray *)DXselected
{
myDXSelected = DXselected;
}
But it crashes the app here at reloadData in
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.DXTableView reloadData];
}
You seem to have mixed up which view controller should be the delegate and which is the delegator. Also, you are just allocating a new instance of the AddDXTableViewController and assigning this as the delegate. This won't work; you need to have the existing instance of your view controller set as the delegate.
From what I can tell from your question, it is actually an instance of DXViewController that is to be the delegate of AddDXTableViewController
Presumably in DXViewController you have some code something like:
AddDXViewController *newViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"AddDXViewController"];
[self.navigationController pushViewController:newViewController animated:YES];
What you need to do is set your delegate at this point:
AddDXViewController *newViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"AddDXViewController"];
newViewController.delegate = self;
[self.navigationController pushViewController:newViewController animated:YES];
Having said all of that, since you are using a storyboard, a delegate is probably an unnecessarily complicated way of achieving your requirement; You can use a segue to move between the first and second view controller and an unwind segue to return back to the first. You can then implement prepareForSegue in the second view controller and use that to provide the array back to the first view controller
You have to make the UIViewController added in storyboard to AddDXTableViewController type in the identity inspector tab in story board.
See here the image
Here you can see the class type is ViewController, click on the dropdown and select the type to AddDXTableViewController
then type cast the viewController to AddDXTableViewController. As per my guess the instantiateViewControllerWithIdentifier: returns UIViewController which does not have delegate may cause the crash
AddDXTableViewController *addDXTVC = (AddDXTableViewController *)[self.storyboard instantiateViewControllerWithIdentifier:#"DXViewController"];
addDXTVC.delegate = self;
Let me know If it works
I am having trouble linking up a switch. I understand how to connect the switch as an outlet and have it have actions change the boolean state of a value, but I need the switch to perform an action in a different view controller.
Here's the situation: I have a main table view controller, called View Controller A. I have a second view controller, lets call it view controller B, that controls a menu sidebar (on a regular view controller, not a table view) triggered by a bar item. I want to be able to open up the menu, hit a switch in the sidebar, and have something change in the main table view that is controlled by view controller A.
Is there any way that I can accomplish this? I seem to have no way of accessing or changing the IBOutlets in View Controller A from B. Is there a way that I can have the action in B linked with the switch change the boolean state of a value, and have an action waiting in controller A that will respond to a change in boolean? I am not sure how to solve this problem. Help is appreciated!
You should use delegation pattern. You'll have an action waiting in controller A, but instead of responding to value changed in B the action will be triggered by B when appropriate
ViewControllerB.h
// Create delegate protocol and property
#protocol ViewControllerBDelegate <NSObject>
- (void)switchPressed:(BOOL)switchStatus;
#end
#interface ViewControllerB : NSObject
#property (nonatomic,weak) id<ViewControllerBDelegate> delegate;
#end
ViewControllerB.m
// When switch is tapped, call delegate method if it is implemented by delegate object
- (IBAction)flip: (id) sender {
UISwitch *onoff = (UISwitch *) sender;
if ([self.delegate respondsToSelector:#selector(switchPressed:)]) {
[self.delegate switchPressed:onoff.on];
}
}
ViewControllerA.h
// Conform to ViewControllerB protocol
#import ViewControllerB.h
#interface ViewControllerA : NSObject,ViewControllerBDelegate
ViewControllerA.m
// Set self (VC A) as VC B's delegate
- (void)ViewDidLoadOrSomeOtherFunction {
ViewControllerB *vcB = [[ViewControllerB alloc] init];
vcB setDelegate = self;
}
// Implement delegate method
- (void)switchPressed:(BOOL)switchStatus {
if (switchStatus) {
// Make changes on VC A
}
}
Currently I have a UITableViewController with static cells which acts like a form for user input. There is one cell with a given amount of entries. This entries can be selected in another UITableViewController by clicking on the cell. Programmatically you select an object from the class "EventType". This object should be forwarded to the first UITableViewController when selecting one entry.
I am able to call the second UITableViewController and dismiss it by calling:
[self dismissViewControllerAnimated:YES completion:nil];
My problem is that I don't know how to forward the object to the first UIViewController and afterwards I want to update the label in the cell with a property of the object.
How do I pass the data back?
If we want to pass the data back from SecondViewController to FirstViewController, we need to use protocols and delegates. To do this, we will have to make FirstViewController a delegate of SecondViewController. If we do this, it allows SecondViewController to send a message back to FirstViewController, thus enabling us to send data back.
If FirstViewController has to be a delegate of SecondViewController, it must conform to SecondViewController’s protocol. We have to make sure we specify the protocol correctly. This tells FirstViewController what methods it needs to implement.
In SecondViewController.h, after all the #import statements, but before the #interface line, we need to specify the protocol as given below:
#class SecondViewController;
#protocol SecondViewControllerDelegate <NSObject>
- (void)addItemViewController:(SecondViewController *)controller didFinishEnteringItem:(NSString *)item;
#end
Next, still in the SecondViewController.h, you need to setup a delegate property and synthesize in SecondViewController.m as given below:
#property (nonatomic, weak) id <SecondViewControllerDelegate> delegate;
In SecondViewController, we call a message on the delegate when we pop the view controller.
NSString *itemToPassBack = #"This value is going back to FirstViewController";
[self.delegate addItemViewController:self didFinishEnteringItem:itemToPassBack];
That’s it for SecondViewController. Now in FirstViewController.h, tell FirstViewController to import SecondViewController and conform to its protocol.
#import "SecondViewController.h"
#interface FirstViewController : UIViewController <SecondViewControllerDelegate>
In FirstViewController.m, implement the following method from our protocol:
- (void)addItemViewController:(SecondViewController *)controller didFinishEnteringItem:(NSString *)item
{
NSLog(#"This was returned from SecondViewController %#",item);
}
Now, all we need to do is tell SecondViewController that FirstViewController is its delegate before we push SecondViewController on to navigation stack. Add the following changes in FirstViewController.m:
SecondViewController *secondViewController = [[SecondViewController alloc] initWithNib:#"SecondViewController" bundle:nil];
secondViewController.delegate = self
[[self navigationController] pushViewController:secondViewController animated:YES];
And that’s it! You are now set to send data from SecondViewController to FirstViewController.
The source
I'm currently trying to have a better understanding on how the mechanisms of passing data between controllers work and I'm a little confused especially when passing data back from a second view controller to the main view controller.
This is what I have that works but don't fully understand. I have two view controllers, in the first one I have a button that when clicked it basically goes to the second view controller and a label which shows a message sent from the second view controller. In the second view controller I have a button and a textField, the button basically sends whatever is in the textfield to the label in main view controller.
Here is the code...
// FirstVC.h
#import <UIKit/UIKit.h>
#import "SecondVC.h"
#interface FirstVC : UIViewController <passNames>
#property (nonatomic, strong) NSString* firstNameString;
#property (weak, nonatomic) IBOutlet UILabel *firstNameLabel;
#end
//FirstVC.m
#import "FirstVC.h"
#implementation FirstVC
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier]isEqualToString:#"secondController"])
{
UINavigationController *navController = segue.destinationViewController;
SecondVC *vc2 = (SecondVC*)navController.topViewController;
[vc2 setDelegate:self];
}
}
-(void)viewWillAppear:(BOOL)animated
{
self.firstNameLabel.text = _firstNameString;
}
-(void)setFirstName:(NSString *)firstName
{
_firstNameString = firstName;
}
#end
//SecondVC.h
#import <UIKit/UIKit.h>
#protocol passNames <NSObject>
-(void)setFirstName:(NSString*)firstName;
#end
#interface SecondVC : UIViewController
#property (retain)id <passNames> delegate;
- (IBAction)send:(UIBarButtonItem *)sender;
#property (nonatomic, strong) NSString *firstNameString;
#property (weak, nonatomic) IBOutlet UITextField *firstNameText;
#end
//SecondVC.m
#import "SecondVC.h"
#import "FirstVC.h"
#interface SecondVC ()
#end
#implementation SecondVC
- (IBAction)send:(UIBarButtonItem *)sender
{
_firstNameString = _firstNameText.text;
[[self delegate]setFirstName:_firstNameString];
[self dismissViewControllerAnimated:YES completion:nil];
}
#end
Can someone explain how the prepareForSegue method works in the above code? The reason for this question is because I added an NSLog and it looks like this method is only called in the transition from main view controller to the second controller. Why is this method needed if it is not called when transitioning from second view controller to main view controller which in my case is what I'm doing? It makes sense to use it when passing data from main view controller to a second controller not on the case shown above.
Can some explain the whole mechanism when passing data back to the main view controller?
FYI, I do understand about protocols and delegation.
Thanks a lot.
In your case, you are setting your delegate method of the second view controller to self in mainViewController in you prepareForSegue. This means that apart from navigating to the SecondViewController, you are implementing the callback mechanism in your main view controller, so that your delegate method gets called when the value is passed from the second view controller and this delegate method collects the value as a parameter to handle it in the main View Controller. You might have set the delegate of VC2 as self inn your prepareForSegue because you are creating the instance of VC2 in this method to navigate to the second controller.
Your goal is to hand back the data, like this:
[[self delegate] setFirstName:_firstNameString];
But you can't do that unless you know who to send setFirstName: to, and the compiler won't let you do it unless you guarantee that whoever you are sending setFirstName: to can accept that message.
That is what prepareForSegue prepares. FirstVC has declared that it adopts the passNames protocol, which means that it implements setFirstName:. And now you are saying:
[vc2 setDelegate:self];
...where self is the FirstVC instance. This solves both problems at once. The SecondVC instance (vc2) now has a delegate (the FirstVC instance), it is the right object to send the info back to, and because its delegate is declared as adopting passNames, we know that SecondVC can actually send setFirstName: to that delegate.
Now to the heart of your actual question: The reason for doing this in prepareForSegue is merely that this is the only moment when the FirstVC instance and the SecondVC instance "meet" one another! There is no other moment when the FirstVC instance has a reference to the SecondVC instance so as to be able to call setDelegate on it in the first place. If you weren't using segues and storyboards, the FirstVC would simply create the SecondVC instance directly - and would set itself as its delegate, just as you do:
SecondVC *vc2 = [SecondVC new];
UINavigationController *nav = [
[UINavigationController alloc] initWithRootViewController: vc2];
[vc2 setDelegate:self];
[self presentViewController: nav animated: YES completion: nil];
This is one reason I don't like storyboards: they muddy the story. It's all so simple and obvious when you don't use them and just do everything directly like this.
In FirstViewController I have a tableview. A button is clicked to push SecondViewController where an item is typed in and a button is pressed to add the item.
In SecondViewController.h file there is:
#protocol SecondViewControllerDelegate <NSObject>
-(void)itemAdded:(NSString *)item;
#property (nonatomic, weak)id <SecondViewControllerDelegate> delegate;
In `SecondViewController.m
- (IBAction)myButton:(id)sender {
[self.delegate itemAdded:#"someText"];
}
In FirstViewController.h
#interface SecondViewController: UITableViewController <UIAlertViewDelegate, SecondViewControllerDelegate>
In FirstViewController.m
-(void)itemAdded: (NSString *) item{
[self.items addObject: item];
}
Everything is working fine except that the [self.delegate itemAdded:#"someText"]; doesn't call the itemAdded function in the FirstViewController can anyone help?
The problem is likely that the delegate of the second view controller is nil. Where ever you create the second view controller, presumably somewhere in the first view controller, you need to set secondViewController.delegate = self so that when the second view controller needs to call back to the delegate, it's pointing to the first controller instead of nil.
Also, you have declared the SecondViewController class as a SecondViewControllerDelegate, but this is incorrect. A second view controller wouldn't be the delegate of itself, rather a FirstViewController will be. You need to move to the interface of FirstViewController, then the compiler won't complain when you try to set a FirstViewController as the delegate to the SecondViewController.