I have a table in my root view controller which has the "add" button mapped to another view controller which has text field to input the name of the list and a save button. That save button is mapped back to the main controller. When the data is passed back to the main controller, it doesn't add the item to the table.
This is what my table view controller's viewDidLoad look like:
- (void)viewDidLoad
{
[super viewDidLoad];
self.tableView.allowsMultipleSelectionDuringEditing = YES;
if (!self.listArray) self.listArray = [[NSMutableArray alloc] init];
[self.listArray addObject:#"New Item"];
[self.tableView reloadData];
[self updateButtonState];
}
This adds the "New Item" to the list and shows up in the table. But when I pass the listname from the segue, I can see the log entry "Adding..." but doesn't add the item to the table. Here's the method for add a new item from a segue.
-(void) addList:(NSString *)listName
{
NSLog(#"Adding %#", listName);
[self.listArray addObject:listName];
[self.tableView reloadData];
}
Prepare for segue
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
LSNewListViewController *sourceView = [segue sourceViewController];
LSMainViewController *destView = [segue destinationViewController];
[destView addList:[[sourceView listName] text]];
}
Rather than using a storyboard segue to push another view controller, try setting a protocol in your "add" view controller. Then set your "main" view controller as the delegate and handle the adding there. From there you can connect the "add" button to call the delegate method and pop the view controller.
LSNewListViewController.h
#protocol LSNewListViewControllerDelegate;
#interface LSNewListViewController : UIViewController
#property (nonatomic, weak) id <LSNewListViewControllerDelegate> delegate;
#end
#protocol LSNewListViewControllerDelegate <NSObject>
#optional
- (void)didAddToList:(NSString*)item;
#end
LSNewListViewController.m
#implementation LSNewListViewController
// Hooked up to the Add button in the storyboard (touchUpInside)
- (IBAction)addToListAction:(id)sender {
if ([self.delegate respondsToSelector:#selector(didAddToList:)]) {
[self.delegate didAddToList:[sourceView listName] text]];
}
}
#end
LSMainViewController.h
#import "LSNewListViewController.h"
#interface LSMainViewController : UIViewController <LSNewListViewControllerDelegate>
#end
LSMainViewController.m
#implementation LSMainViewController
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
LSNewListViewController *destView = [segue destinationViewController];
destView.delegate = self;
}
// Delegate method
- (void)didAddToList:(NSString*)item {
NSLog(#"Adding %#", listName);
[self.listArray addObject:listName];
[self.tableView reloadData];
[self.navigationController popViewControllerAnimated:YES];
}
#end
Related
I try to pass data from one ViewController to secondControler however seem it not work. I use NSNotification.
- 2 Controller have same class "ViewController"
In viewcontroller.m
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(ProcessBarLoading) name:#"buttonPressed" object:nil];
}
-(void)ProcessBarLoading{
_labelTest.stringValue = #"TESTING";
}
- (IBAction)test:(id)sender {
[[NSNotificationCenter defaultCenter] postNotificationName:#"buttonPressed" object:self];
NSStoryboard *storyboard = [NSStoryboard storyboardWithName:#"Main" bundle: nil];
NSViewController * vc = [storyboard instantiateControllerWithIdentifier:#"SheetViewController"];
[self presentViewControllerAsSheet:vc];
}
When run program and press button, there're no update Label Text at all. Do you know why and how I can fix.
New Code:
In SecondViewController.m
#interface SencondViewController ()
#end
#implementation SencondViewController
#synthesize progressValue;
#synthesize labelView;
- (void)viewDidLoad {
[super viewDidLoad];
// Do view setup here.
labelView.stringValue =progressValue;
}
In FirstViewCOntroller:
- (IBAction)test:(id)sender {
self->uide = #"0";
[self performSegueWithIdentifier:#"showRecipeDetail" sender:self->uide];
NSStoryboard *storyboard = [NSStoryboard storyboardWithName:#"Main" bundle: nil];
NSViewController * vc = [storyboard instantiateControllerWithIdentifier:#"SheetViewController"];
[self presentViewControllerAsSheet:vc];
- (void)prepareForSegue:(NSStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"showRecipeDetail"]) {
SencondViewController * secondVC = segue.destinationController;
secondVC.progressValue = uide;
}
}
- (IBAction)test2:(id)sender {
uide = #"80";
[self performSegueWithIdentifier:#"showRecipeDetail" sender:uide];
[self.view displayIfNeeded];
}
So whether I press Button 1(test) other Button2 (test2) alway show new view with update value. What I need is only show 1 view.
Why do you need use a nsnotification the easy way is use a prepareForSegue or Delegation
This is an examample
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (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:#"myId"]) {
SecondViewController *vc = segue.destinationViewController;
vc.myDataToPass = self.myValueInMyFirstViewController;
}
}
Notification pattern is not recommended for doing this. Use notification
when you want to pass some data to multiple objects on some event.
To solve this problem:
Step 1:
You should change your View Controller names to FirstViewController and SecondViewController, and have a property declared in your SecondViewController whose value you want to set from the FirstViewController.
Step 2:
Finally, in the prepare for Segue method of the FirstViewController, set the data.
In Objective-C, you can try this code:
#import "FirstViewController.h"
#import "SecondViewController.h"
#interface FirstViewController ()
#end
#implementation FirstViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
//This will trigger the prepareForSegue method
-(IBAction) someButtonClick {
[self performSegueWithIdentifier:#"YourSequeId" sender:nil];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
SecondViewController * secondVC = segue.destinationViewController;
secondVC.someValue = #"PassYourValueHere";
}
#end
and in the header file of the SecondViewController, declare the property:
#import <UIKit/UIKit.h>
#interface SecondViewController : UIViewController
#property (nonatomic,strong) NSString *someValue;
#end
In the implementation file of the SecondViewController, write:
#import "SecondViewController.h"
#interface SecondViewController ()
#property (nonatomic,weak) IBOutlet UITextField *yourTextField;
#end
#implementation SecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.yourTextField.text = self.someValue
// Do any additional setup after loading the view.
}
#end
I have an UITableViewController created with storyboard.
Than I have an anchor that call a popOver created with storyboard as well. The popover is another UItableViewController, when I click on a row I should call back the first controller and pass an object.
I have tried this in the popover object:
(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"Fortune"]){
NSIndexPath *indexPath = (NSIndexPath *)sender;
ASFortuneTeller * aController = [segue destinationViewController];
[aController setWYPT:[allWYPTs objectAtIndex:indexPath.row]];
}
}
Basically I need to pass a NSMutableDictionary bag to the first UITableViewController.
But I have noticed that in this way I create a new ASFortuneTeller object , that is not what I want... I want just to call back the first controller and pass an object.
How can I do it?
A quick solution (when it involves always the same two classes) could be:
In the .h file of the first view controller, define a method (or just a property):
-(void)selectedWYPT:(NSMutableDictionary*)wypt;
Within the .h file of your second view controller make a property
#property FirstUIViewController *firstView;
In the first view controller, you will open the second view controller via a segue, so there you can use:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"segueIdentifier"]) {
SecondUIViewController *destination = segue.destinationViewController;
destination.firstView = self;
}
}
When the row is selected in the second view you can use
if (self.firstView)
[self.firstView setWYPT:[allWYPTs objectAtIndex:indexPath.row]];
to pass the data back to the first view.
As said, this would be a quick solution when always the same two classes are involved.
An other way would be to use protocols. When the first view controller won't always be FirstUIVierController, you may use something like this:
SecondUIViewController.h
#class SecondUIViewController;
#protocol SecondUIViewControllerDelegate <NSObject>
-(void)secondUIViewController:(SecondUIViewController*)controller didSelectWYPT:(NSMutableDictionary*)wypt;
#end
#interface SecondUIViewController : UIViewController
#property id<SecondUIViewControllerDelegate> delegate;
#end
SecondUIViewController.m
where the row is selected:
if (self.delegate && [self.delegate respondsToSelector:#selector(secondUIViewController:didSelectWYPT:)])
[self.delegate secondUIViewController:self didSelectWYPT:[allWYPTs objectAtIndex:indexPath.row]];
AnyOtherUIViewController.h
#import "SecondUIViewController.h"
#interface AnyOtherUIViewController : UIViewController <SecondUIViewControllerDelegate>
...
...
AnyOtherViewController.m
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"segueIdentifier"]) {
SecondUIViewController *destination = segue.destinationViewController;
destination.delegate = self;
}
}
-(void)secondUIViewController:(SecondUIViewController*)controller didSelectWYPT:(NSMutableDictionary*)wypt {
//do something with the data
}
I use storyboard in a OS X cocoa application project with a SplitView controller and 2 others view controller LeftViewController and RightViewController.
In the LeftViewController i have a tableView that display an array of name. The datasource and delegate of the tableview is the LeftViewController.
In the RightViewController i just have a centered label that display the select name. I want to display in the right view the name selected in the left view.
To configure the communication between the 2 views controllers i use the AppDelegate and i define 2 property for each controller in AppDelegate.h
The 2 property are initialized in the viewDidLoad of view controller using the NSInvocation bellow :
#implementation RightViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
id delg = [[NSApplication sharedApplication] delegate];
SEL sel1 = NSSelectorFromString(#"setRightViewController:");
NSMethodSignature * mySignature1 = [delg methodSignatureForSelector:sel1];
NSInvocation * myInvocation1 = [NSInvocation
invocationWithMethodSignature:mySignature1];
id me = self;
[myInvocation1 setTarget:delg];
[myInvocation1 setSelector:sel1];
[myInvocation1 setArgument:&me atIndex:2];
[myInvocation1 invoke];
}
I have the same in LeftViewController.
Then if i click on a name in the table view, i send a message to the delegate with the name in parameter and the delegate update the label of the RightViewController with the given name. It works fine but according to apple best practice it’s not good.
Is there another way to communicate between 2 view controller inside a storyboard ?
I've already read a lot of post but found nothing for OS X.
You can download the simple project here : http://we.tl/4rAl9HHIf1
This is more advanced topic of app architecture (how to pass data).
Dirty quick solution: post NSNotification together with forgotten representedObject:
All NSViewControllers have a nice property of type id called representedObject. This is one of the ways how to pass data onto NSViewController. Bind your label to this property. For this simple example we will set representedObject some NSString instance. You can use complex object structure as well. Someone can explain in comments why storyboards stopped to show representedObject (Type safety in swift?)
Next we add notification observer and set represented object in handler.
#implementation RightViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserverForName:#"SelectionDidChange" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
//[note object] contains our NSString instance
[self setRepresentedObject:[note object]];
}];
}
#end
Left view controller and its table:
Once selection changes we post a notification with our string.
#interface RightViewController () <NSTableViewDelegate, NSTableViewDataSource>
#end
#implementation RightViewController
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
return [[self names] count];
}
- (nullable id)tableView:(NSTableView *)tableView objectValueForTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row
{
return [self names][row];
}
- (NSArray<NSString *>*)names
{
return #[#"Cony", #"Brown", #"James", #"Mark", #"Kris"];
}
- (void)tableViewSelectionDidChange:(NSNotification *)notification
{
NSTableView *tableView = [notification object];
NSInteger selectedRow = [tableView selectedRow];
if (selectedRow >= 0) {
NSString *name = [self names][selectedRow];
if (name) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"SelectionDidChange" object:name];
}
}
}
PS: don't forget to hook tableview datasource and delegate in storyboard
Why is this solution dirty? Because once your app grows you will end up in notification hell. Also view controller as data owner? I prefer window controller/appdelegate to be Model owner.
Result:
AppDelegate as Model owner.
Our left view controller will get it's data from AppDelegate. It is important that AppDelegate controls the data flow and sets the data (not the view controller asking AppDelegate it's table content cause you will end up in data synchronization mess). We can do this again using representedObject. Once it's set we reload our table (there are more advanced solutions like NSArrayController and bindings). Don't forget to hook tableView in storyboard. We also modify tableview's delegate methos the tableViewSelectionDidChange to modify our model object (AppDelegate.selectedName)
#import "LeftViewController.h"
#import "AppDelegate.h"
#interface LeftViewController () <NSTableViewDelegate, NSTableViewDataSource>
#property (weak) IBOutlet NSTableView *tableView;
#end
#implementation LeftViewController
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
return [[self representedObject] count];
}
- (nullable id)tableView:(NSTableView *)tableView objectValueForTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row
{
return [self representedObject][row];
}
- (void)setRepresentedObject:(id)representedObject
{
[super setRepresentedObject:representedObject];
//we need to reload table contents once
[[self tableView] reloadData];
}
- (void)tableViewSelectionDidChange:(NSNotification *)notification
{
NSTableView *tableView = [notification object];
NSInteger selectedRow = [tableView selectedRow];
if (selectedRow >= 0) {
NSString *name = [self representedObject][selectedRow];
[(AppDelegate *)[NSApp delegate] setSelectedName:name];
} else {
[(AppDelegate *)[NSApp delegate] setSelectedName:nil];
}
}
In RightViewController we delete all code. Why? Cause we will use binding AppDelegate.selectedName <--> RightViewController.representedObject
#implementation RightViewController
#end
Finally AppDelegate. It needs to expose some properties. What is interesting is how do I get my hands on all my controllers? One way (best) is to instantiate our own window controller and remember it as property. The other way is to ask NSApp for it's windows (be careful here with multiwindow app). From there we just ask contentViewController and loop through childViewControllers. Once we have our controllers we just set/bind represented objects.
#interface AppDelegate : NSObject <NSApplicationDelegate>
#property (nonatomic) NSString *selectedName;
#property (nonatomic) NSMutableArray <NSString *>*names;
#end
#import "AppDelegate.h"
#import "RightViewController.h"
#import "LeftViewController.h"
#interface AppDelegate () {
}
#property (weak, nonatomic) RightViewController *rightSplitViewController;
#property (weak, nonatomic) LeftViewController *leftSplitViewController;
#property (strong, nonatomic) NSWindowController *windowController;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
_names = [#[#"Cony", #"Brown", #"James", #"Mark", #"Kris"] mutableCopy];
_selectedName = nil;
NSStoryboard *storyboard = [NSStoryboard storyboardWithName:#"Main"
bundle:[NSBundle mainBundle]];
NSWindowController *windowController = [storyboard instantiateControllerWithIdentifier:#"windowWC"];
[self setWindowController:windowController];
[[self windowController] showWindow:nil];
[[self leftSplitViewController] setRepresentedObject:[self names]];
[[self rightSplitViewController] bind:#"representedObject" toObject:self withKeyPath:#"selectedName" options:nil];
}
- (RightViewController *)rightSplitViewController
{
if (!_rightSplitViewController) {
NSArray<NSViewController *>*vcs = [[[self window] contentViewController] childViewControllers];
for (NSViewController *vc in vcs) {
if ([vc isKindOfClass:[RightViewController class]]) {
_rightSplitViewController = (RightViewController *)vc;
break;
}
}
}
return _rightSplitViewController;
}
- (LeftViewController *)leftSplitViewController
{
if (!_leftSplitViewController) {
NSArray<NSViewController *>*vcs = [[[self window] contentViewController] childViewControllers];
for (NSViewController *vc in vcs) {
if ([vc isKindOfClass:[LeftViewController class]]) {
_leftSplitViewController = (LeftViewController *)vc;
break;
}
}
}
return _leftSplitViewController;
}
- (NSWindow *)window
{
return [[self windowController] window];
}
//VALID SOLUTION IF YOU DON'T INSTANTIATE STORYBOARD
//- (NSWindow *)window
//{
// return [[NSApp windows] firstObject];
//}
#end
Result: works exactly the same
PS: If you instantiate own window Controller don't forget to delete initial controller from Storyboard
Why is this better? Cause all changes goes to model and models sends triggers to redraw views. Also you will end up in smaller view controllers.
What can be done more? NSObjectController is the best glue between your model objects and views. It also prevents retain cycle that sometimes can happen with bindings (more advanced topic). NSArrayController and so on...
Caveats: not a solution for XIBs
I managed to get what i want by adding the following code in AppDelegate.m :
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
//
NSStoryboard *storyboard = [NSStoryboard storyboardWithName:#"Main"
bundle:[NSBundle mainBundle]];
self.windowController = [storyboard instantiateControllerWithIdentifier:#"windowController"];
self.window = self.windowController.window;
self.splitViewController = (NSSplitViewController*)self.windowController.contentViewController;
NSSplitViewItem *item0 = [self.splitViewController.splitViewItems objectAtIndex:0];
NSSplitViewItem *item1 = [self.splitViewController.splitViewItems objectAtIndex:1];
self.leftViewController = (OMNLeftViewController*)item0.viewController;
self.rightViewController = (OMNRightViewController*)item1.viewController;
[self.window makeKeyAndOrderFront:self];
[self.windowController showWindow:nil];
}
We also need to edit the storyboard NSWindowController object as follow :
Uncheck the checkbox 'Is initial controller' because we add it programmatically in AppDelegate.m.
Now the left and right view can communicate. Just define a property named rightView in OMNLeftViewController.h :
self.leftViewController.rightView = self.rightViewController;
Lets say I have a UIViewController with two buttons, both going (push) to another UIViewController that has two UIWebViews (showing two different PDF files), how can I make sure that only the one I choose via the button is showed?
You need to pass some information to the UIViewController which has the UIWebViews, saying which button was pressed. Then, based on that information, decide which of the UIWebViews to display.
As you are using storyboards, I suggest you look into prepareForSegue. It will allow you to set a property on the destination view controller with something like the following. You should add this to the UIViewController which contains the buttons.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"buttonOne"]) {
ExampleViewController *destViewController = segue.destinationViewController;
destViewController.buttonClicked = #"One";
} else if ([segue.identifier isEqualToString:#"buttonTwo"]) {
ExampleViewController *destViewController = segue.destinationViewController;
destViewController.buttonClicked = #"Two";
}
}
You can then use the buttonClicked property in the destination view controller to decide which you should display. If you have two separate UIWebViews, you could choose to hide one using webViewOne.hidden = YES; and show the other using webViewTwo.hidden = NO;.
However, it would probably be neater to only have a single UIWebView. You could then use prepareForSeque to pass in the URL of the PDF you would like it to display, rather than just sending the name of the button clicked.
Assuming you webView is in a view controller called SecondViewController and your buttons are in the view controller called FirstViewController
1) Create an object in your SecondViewController.h
#interface SecondViewController : UIViewController
#property (nonatomic, strong) NSString *whichButtonClicked;
#end
2) Import SecondViewController in your FirstViewController
#import "SecondViewController.h"
3) In you button IBAction method in FirstViewController.m . use this code
- (IBAction) firstButtonClicked
{
SecondViewController *secondViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"secondView"];
secondViewController. whichButtonClicked = #"first"
[self.navigationController pushViewController:secondViewController animated:YES];
}
- (IBAction) secondButtonClicked
{
SecondViewController *secondViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"secondView"];
secondViewController. whichButtonClicked = #"second"
[self.navigationController pushViewController:secondViewController animated:YES];
}
PS Don't forget. In you Storyboard. Set Storyboard ID for SecondViewController as secondView
4) In your SecondViewController.m use this code to check which button
if ([self.whichButtonClicked isEqualToString:#"first"])
{
///display first web view here
}
else
{
//display second web view here
}
Hope this helps
So I am able to transfer the data from the first view to the second view like this:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"Check Mark Segue"])
{
NSLog(#"Transfering Data");
AutoRenewDrop *controller = segue.destinationViewController;
controller.transferData = self.renewDate.text;
}
}
However, I try to transfer a new value back to renewDate.text when the user hits done and the transferData is working correctly but the renewDate.text does not change. Here is the code that I am using to transfer the data back:
-(IBAction)done:(UIStoryboardSegue *)segue {
[self.navigationController popViewControllerAnimated:YES];
AddR *add = [[AddR alloc] init];
add.renewDate.text = transferData;
}
Can someone tell me how to fix this?
You need to add a property that contain a reference of the first view into the second view :
#interface AutoRenewDrop
#property(weak, nonatomic) AddR *callerView;
#end
And then in the done method of the second view you can just update the variale in the caller view :
-(IBAction)done:(UIStoryboardSegue *)segue {
[self.navigationController popViewControllerAnimated:YES];
callerView.renewDate.text = transferData;
}
Of course when you instantiate the second view you will have to set the reference, in this way :
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"Check Mark Segue"])
{
NSLog(#"Transfering Data");
AutoRenewDrop *controller = segue.destinationViewController;
controller.transferData = self.renewDate.text;
controller.callerView = self; //Here, you are passing the reference to this View
}
}
You should use a delegate.
You can read a pretty good tutorial about protocols and delegates here.
Lets say you have 2 ViewControllers - VC1 and VC2, and lets say that VC1 displays VC2.
In VC2.h
#protocol VC2Delegate <NSObject>
- (void)updateData:(id)theData;
#end
#interface
#property (nonatomic, weak) id<VC2Delegate> delegate;
#end
In VC2.m
#syntesize delegate;
- (void)done
{
[delegate updateData:myData];
}
In VC1.h
#interface VC1 : UIViewController<VC2Delegate>
In VC1.m
- (void)goToVC2
{
VC2 *vc2 = [[VC2 alloc] init];
vc2.delegate = self;
// present vc2
}
- (void)updateData:(id)data
{
// update what you need
}