Add objects to an Array from another View Controller - ios

I have a HomeViewController which has a tableView populated with the array tableViewArray (originally empty). When I tap on a barButton, I segue modally to another View Controller called OutsideViewController which has another tableView populated by a different array.
What I would like to do is the following:
When I tap on a row in my OutsideViewController, I would like to add the selected string value to the tableViewArray so that when I go back to HomeViewController, the tableView has that new item listed in the tableView.
So far, this is what I have tried:
In the -didSelectRowAtIndexPath method of my OutsideViewController.m I have this piece of code:
NSString *selectedRow = [outsideArray objectAtIndex:indexPath.row];
NSMutableArray *temporaryArray = [NSMutableArray arrayWithObject:selectedRow];
HomeViewController *homeVC = [[HomeViewController alloc] init];
homeVC.tableViewArray = temporaryArray;
That code works but the tableView in HomeViewController is still empty when I return. Do I have to reload the tableView data?
Am I doing this right?
This is how I have set up my View Controllers in Storyboard:
HomeViewController -(modal segue)-> Navigation Controller --> OutsideViewController
Also, the return from OutsideViewController to HomeViewController is done by this line of code:
[self dismissViewControllerAnimated:YES completion:^{ }];

What you're doing wrong is you're allocationg a new HomeViewController. What I would do is keeep a reference to your HomeViewController in your OutsideViewController. Here is how.
First, in OutsideViewController.h, create a property, like this :
#property (nonatomic, weak) HomeViewController *homeVC;
Don't forget to add #class HomeViewController; in your .h, and #import "HomeViewController.h" in your .m
In HomeViewController, implement the prepareForSegue: method like this (replace ModalSegueIdentifier with your segue's identifier) :
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"ModalSegueIdentifier"]) {
OutsideViewController *modalVC = (OutsideViewController*)segue.destinationViewController;
modalVC.homeVC = self;
}
}
Then, in OutsideViewController.m, instead of doing :
HomeViewController *homeVC = [[HomeViewController alloc] init];
homeVC.tableViewArray = temporaryArray;
Do this :
_homeVC.tableViewArray = temporaryArray;
When you leave your modal VC, your HomeVC will have the correct array. Don't forget to refresh your UITableView !
NB: Of course, there are many other ways, and it's maybe not the best one. But still, it should work.

You can achieve this too using delegation. You have to create a protocol in your OutsideViewController with a method that is responsible for sending the new object to your HomeViewController. Do this in OutsideViewController.h:
#protocol OutsideViewDelegate <NSObject>
- (void)OutsideViewController:(OutsideViewController *)controller didAddObject:(NSString *)object;
#end
In the implementation file you have to change a little bit the didSelectRowAtIndexPath: method:
NSString *selectedRow = [outsideArray objectAtIndex:indexPath.row];
[self.delegate OutsideViewController:self didAddObject:selectedRow];
In your HomeViewController.h you have to make your class conforms to the protocol:
#interface HomeViewController : UIViewController <OutsideViewDelegate>
After, create a property for the delegate:
#property (nonatomic, weak) id <OutsideViewDelegate> delegate;
To finish the process, implement the protocol in your HomeViewController.m to receive the new object from the OutsideViewController:
- (void)OutsideViewController:(OutsideViewController *)controller didAddObject:(NSString *)object
{
if (object != nil)
{
[self.tableViewArray addObject:object];
}
[self dismissViewControllerAnimated:YES completion:nil];
}
The code above depends of if your tableViewArray object is mutable or not. If it's not, you can change the type of the object argument in the protocol method to an inmutable array object and just assign tableViewArray to the new array.
EDIT:
In the prepareForSegue: method don't forget to set the delegate:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"SEGUE_IDENTIFIER"]) {
OutsideViewController *outsideVC = (OutsideViewController *)[segue destinationViewController];
[outsideVC setDelegate:self];
}
}

First of all make sure you alloc, init your tableViewArray in HomeViewController
Second , In this line
HomeViewController *homeVC = [[HomeViewController alloc] init]
you are creating a new reference to your HomeViewController which is not correct, you need to pass correct reference, possibly creating HomeViewController variable in your OutsideViewController
Even though you correctly do first and second suggestion you will still see an empty tableview because you dont reload the tableview, somehow you need to fire [self.tableview reloadData]; method.
That means; you need to learn Delegate or NSNotifications pattern to communicate between child->parent scenarios
How do I set up a simple delegate to communicate between two view controllers?
http://mobile.tutsplus.com/tutorials/iphone/ios-sdk_nsnotificationcenter/
For your question just create a delegate in your Outside;
in your OutsideViewController.h
#import <UIKit/UIKit.h>
#protocol OutsideDelegate;
#interface{}//bunch of interface stuff
// Declare a property for the delegate
#property (weak) id <OutsideDelegate> delegate;
#end
// Protocol Header
#protocol OutsideDelegate <NSObject>
#optional
- (void)dismissPop:(NSMutableArray *)list;
#end
in your OutsideViewController.m
#synthesize delegate;
//run delegate method
[delegate dismissPop:temporaryArray];
[self dismissViewControllerAnimated:YES completion:^{ }];
in your HomeViewController.h
#import "OutsideViewController.h"
#interface OutsideViewController : UITableViewController<OutsideDelegate>
{}
#property (strong, nonatomic) OutsideViewController *pvc;
in your HomeViewController.m
#synthesize pvc;
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"your segue"]) {
pvc = [segue destinationViewController];
[pvc setDelegate:self];
}
}
// delegate callback function
- (void)dismissPop:(NSMutableArray *)list {
self.tableViewArray=list;
[self.tableView reloadData];
}
Another Solution Would be
Change your view stack to this:
Navigation Controller --> HomeViewController -(push segue)--> OutsideViewController
and apply rdurand's answer
and add this to your HomeViewController :
-(void)viewDidAppear:(BOOL)animated
{
[self.tableview reloadData];
}
In this solution since you are just push-pop viewcontrollers in a nabigation stack viewDidAppear will be called in HomeViewController everytime when you pop OutsideViewController.

Related

Custom Delegate is not responding

I have my first viewController "SFCrearMomentoViewController" and the second view is a tableViewController "MasterViewController". I need to select a row didSelectRowAtIndexPath and call the method didCelected which is implemented in my "first viewController" and close the view using dismissViewControllerAnimated.
The problem is that the method didCelected is never called. I've already tested this code in a test project using "two viewControllers" and it works but I don't know what's the problem in my current project.
SFCrearMomentoViewController.h
...
#import "MasterViewController.h"
#interface SFCrearMomentoViewController : UIViewController <UIImagePickerControllerDelegate, UINavigationControllerDelegate, UITextViewDelegate, MasterViewControllerDelegate>{
UIImagePickerController *picker;
}
...
SFCrearMomentoViewController.m
...
-(void)didSelected:(NSString *)nombre{
NSLog(#"didSelected method %#", nombre);
}
#end
MasterViewController.h
#import <UIKit/UIKit.h>
#protocol MasterViewControllerDelegate <NSObject>
#required
-(void) didSelected:(NSString *)nombre;
#end
#interface MasterViewController : UITableViewController
#property (weak, nonatomic) id <MasterViewControllerDelegate> delegate;
#end
MasterViewController.m
...
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self.delegate didSelected:#"row selected"];
[self dismissViewControllerAnimated:YES completion:nil];
}
...
Solution: I had to set the delegate self. I implemented it in the Segue.
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if ([segue.destinationViewController isKindOfClass:[MasterViewController class]]) {
MasterViewController *nextView = segue.destinationViewController;
nextView.delegate = self;
}
}
I had the same problem a few hours ago. In my case I set the delegate in prepareForSegue to the segue.destinationViewController but my destination view controller wasn't the View Controller with the delegate, it was a UINavigationController.
So I used this one:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"modalNeuerRaum"])
{
[(MyCustomModalViewController*)[(UINavigationController*)segue.destinationViewController viewControllers][0] setDelegate:self];
}
}
That worked for me, but I don't know if you have a UINavigationController..
You need to set the delegate in SFCrearMomentoViewController, you can do it in viewDidLoad, for example.
masterViewController.delegate = self;
I assume that you allocated and initialised new masterViewController object of MasterViewController type.

Accessing an instance variable from a different view controller

I have a publicly declared NSString, NSString *characterString in my mainViewController.m and just before I perform a [self performSegueWithIdentifier:#"segueToFinal" sender:self]; I update this characterString with the latest data. like so:
characterString=[NSString stringWithFormat:#"final string %#",truncatedString];
[self performSegueWithIdentifier:#"segueToBlueprints" sender:self];
When the new view controller finishes appearing, the user will press a button some time that will call a method that will want the characterString and possibly other publicly declared instance variables from that previous view controller. I was trying [[mainViewController alloc]getCharacterString] (with getCharacterString being a method implemented in mainViewController) but that of course creates a new instance of mainViewController and does not solve the issue.
How Can I access the data currently in 'characterString' and other variables from the old view controller?
Keep all you global variables in you AppDelegate:
YourAppDelegate.h
#interface BTAppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#property (strong, nonatomic) NSString * characterString;
#end
YourViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
YourAppDelegate* app = (YourAppDelegate*)[UIApplication sharedApplication].delegate;
app.characterString = #"Hello";
}
YourSecondViewController.m
- (IBAction)pressButton:(id)sender{
YourAppDelegate* app = (YourAppDelegate*)[UIApplication sharedApplication].delegate;
app.characterString = #"Hello2";
}
etc.
Try this out :
#interface NewVC :UIViewController
#property (nonatomic) NSString *newCharacterString;
#end
Now in the mainViewController import the NewViewController class :
#import "NewViewController.h"
.......
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NewViewController *destinationViewController = [segue destinationViewController];
destinationViewController.newCharacterString = self.characterString;
}
and you can access the value of the character string in your NewViewController through the newCharacterString property.
HTH :)
The only thing I can think of is passing the old data from the view controller along with the segue. Or, you could even pass the entire old view controller if you have multiple properties you want to keep.
In the new VC (the one being segued to), add a property that you want to keep from the old VC (the one being segued from) such as the NSString *.
#interface NewVC :UIViewController
#property (nonatomic) NSString *newCharacterString;
#end
Then, while you are segue - ing, just pass the value to the new VC.
characterString=[NSString stringWithFormat:#"final string %#",truncatedString];
newViewControllerIWantToPassAValueTo.newCharacterString = characterString;
// passes new string forward to View Controller
[self performSegueWithIdentifier:#"segueToBlueprints" sender:self];
Why you dont pass this characterString to the secondViewController
In SecondViewController:
#interface SecondViewController:UIViewController
#property (strong, nonatomic) NSString *newCharacterString;
#end
In MainViewController:
[self performSegueWithIdentifier:#"segueToBlueprints" sender:self];
And you have to add the preparePerform Segue to MainViewController
- (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"])
{
SecondViewController *vc = [segue destinationViewController];
// Pass any objects to the view controller here, like...
vc.newCharacterString = self.characterString;
}
}
And you could use newCharacterString in SecondViewController without callback to the MainViewController

Objective C block property is always nil

I'm trying to learn more about Objective C blocks and how they work. I've set up a simple project with two UIViewControllers embedded in a UINavigationController in Storyboard. I'm attempting to change the background color of the first ViewController's view from the second view controller. Here's some code:
ViewController.m
#implementation ViewController{
ColorBlock _colorBlock;
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([segue.identifier isEqualToString:#"theSegue"]){
SecondViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"SecondViewController"];
vc.colorBlock = _colorBlock;
}
}
- (IBAction)moveToSecondViewController:(id)sender {
__weak id weakSelf = self;
_colorBlock = ^{
[[weakSelf view] setBackgroundColor:[UIColor redColor]];
};
}
SecondViewController.h
typedef void (^ColorBlock)(void);
#interface SecondViewController : UIViewController
#property (readwrite, copy) ColorBlock colorBlock;
#end
SecondViewController.m
- (IBAction)buttonTapped:(id)sender {
if(self.colorBlock){
self.colorBlock();
}
}
The first ViewController's background color isn't being changed because in the buttonTapped: method of SecondViewController.m, self.colorBlock is nil, causing the block invocation not to be called. I thought I had successfully set the block in prepareForSegue:sender:. Why is my block property nil?
In your prepareForSegue, the destination has already been instantiated. So assuming that SecondViewController is the destination, you can do:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([segue.identifier isEqualToString:#"theSegue"]){
SecondViewController *vc = segue.destinationViewController;
NSAssert([vc isKindOfClass:[SecondViewController class]], #"destination is not SecondViewController class");
vc.colorBlock = _colorBlock;
}
}

Access text from UITextField in modal View Controller from Home View Controller

I have a ViewController1 with a UILabel that can present a ViewController2 with a Modal Segue. I have a UITextField in ViewController2 that I need to access from ViewController1 so I can set my UILabel with the collected text.
I've tried working with the prepareForSegue without success. What should I do?
EDIT:
I'm using a Delegate, but I'm doing something wrong. Here's the code I'm using in ViewController2.h:
#class ViewController2;
#protocol VCProtocol
-(void)setName:(NSString *)name;
#end
#interface ViewController2 : UIViewController
#property (nonatomic, weak) id<VCProtocol> delegate;
#property (strong, nonatomic) IBOutlet UITextField *nameField;
- (IBAction)setButton:(id)sender
#end
ViewController2.m
-(IBAction)setButton:(id)sender
{
[self.delegate setName:nameField.text];
}
I conform to VCProtocol in my ViewController1.h. Then, in my ViewController1.m, I have this code:
- (void)setName:(NSString *)name
{
self.firstSignatureNameLabel.text = name;
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqual:#"Sign"])
{
ViewController2 *VC = [segue destinationViewController];
VC.delegate = self;
}
}
You can create a protocol and set VC1 as delegate of VC2, and using prepareForSegue to set VC1 as VC2's delegate should work. I know that you said it didn't work, but I can't see why. Have a try with this :
Give an identifier to your segue (on storyboard), and implement prepareForSegue as shown below :
VC2Delegate.h :
#protocol VC2Delegate
- (void)updateLabel:(NSString *)text;
#end
VC1.h :
#import "VC2Delegate.h"
#interface VC1 : UIViewController <VC2Delegate>
// All your stuff
#end
VC1.m :
- (void)updateLabel:(NSString *)text {
[_label setText:text];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"YourSegueIdentifier"]) {
VC2 * vc = [segue destinationViewController];
[vc2 setDelegate:self];
}
}
VC2.h :
#import "VC2Delegate.h"
#interface VC2 : UIViewController
#property (weak, nonatomic) id<VC2Delegate>delegate;
#end
VC2.m
- (void)textWasUpdated { // or whatever method where you detect the text has been changed
if (_delegate)
[_delegate updateLabel:[_textView text]];
}
Tell me if it works. Else, is prepareForSegue even been called ?
EDIT : Updated my answer (wasn't exactly what you needed).
But as you say it doesn't work :
Is prepareForSegue called ?
If so, is the delegate method called ?
If the delegate method isn't called, check that delegate is not nil.
You might want to remove the segue, and present it modally by yourself, using presentViewController:animated:completion:, like that :
- (IBAction)buttonWasTapped {
static NSString * const idModalView = #"modalView";
static NSString * const storyBoardName = #"MainStoryBoard"
UIStoryboard * storyboard = [UIStoryboard storyboardWithName:storyBoardName bundle:nil];
VC2 * vc = [storyboard instantiateViewControllerWithIdentifier:idModalView];
[vc setDelegate:self];
[self.navigationController presentViewController:vc2 animated:YES completion:nil];
}
When I ran into this problem, I chose the singleton approach and it works.

Refreshing data in a UIViewController after dismissing its presented modal view controller via delegate

I have the delegate working as the data is being passed from the modal to the presenting view controller. But the presenting view controller isn't showing the data it receives from the modal. I looked at other posts and they say to use the delegate/protocol method, but don't explain how/why the presenting VC refreshes. I'm assuming my delegate is setup incorrectly. Otherwise, what is the method to refresh the data? I've checked and the viewWillAppear and viewDidAppear doesn't get called.
SCCustomerDetailVC.h (Presenting VC)
#import "SCCustomersVC.h"
#interface SCCustomerDetailVC : UIViewController <SCCustomersVCDelegate>
#property (atomic, strong) SCCustomer *customer;
#property (strong, nonatomic) IBOutlet UIButton *changeCustomerButton;
- (IBAction)changeCustomerButtonPress:(UIButton *)sender;
#end
SCCustomerDetailVC.m (Presenting VC)
- (IBAction)changeCustomerButtonPress:(UIButton *)sender
{
UINavigationController *customersNC = [self.storyboard instantiateViewControllerWithIdentifier:#"customersNC"];
SCCustomersVC *customersVC = (SCCustomersVC *)customersNC.topViewController;
customersVC.delegate = self;
[self presentViewController:customersNC animated:YES completion:nil];
}
//Protocol methods
- (void)passCustomer:(SCCustomer *)customer
{
self.customer = customer;
//At this point, self.customer has the correct reference
[self dismissViewControllerAnimated:YES completion:nil];
}
SCCustomersVC.h (Modal VC)
#import "SCCustomersVCDelegate.h"
#class SCCustomerDetailVC;
#interface SCCustomersVC : UIViewController <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate>
#property (weak, nonatomic) id <SCCustomersVCDelegate> delegate;
#end
SCCustomersVC.m (Modal VC)
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
SCCustomer *customer = [self customerAtIndexPath:indexPath];
[self.delegate passCustomer:customer];
}
SCCustomersVCDelegate.h
#class SCCustomer;
#protocol SCCustomersVCDelegate <NSObject>
#optional
- (void)passCustomer:(SCCustomer *)customer;
#end
I think you're nearly there. EDIT - just learned here that viewWillAppear behavior is different in iOS>5. You still want view will appear to update your view with your model state, since it needs to do that on initial presentation.
And it's fine to call it from either your modal vc or from within the delegate method. So add code to viewWillAppear that updates the view with the view controller's model state...
// SCCustomerDetailVC.m
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// making up an IBOutlet called someLabel
// making up a model method (description) that returns a string representing your model
self.someLabel.text = [self.customer description];
}
Then either from the presented vc or the delegate, call viewViewWillAppear:
- (void)passCustomer:(SCCustomer *)customer
{
self.customer = customer;
[self viewWillAppear:YES];
[self dismissViewControllerAnimated:YES completion:^{}];
}
You should reload your UI after setting the "customer"
- (void)passCustomer:(SCCustomer *)customer
{
self.customer = customer;
//At this point, self.customer has the correct reference
[self dismissViewControllerAnimated:YES completion:^{
// reload your UI here or call a method which will reload your ui
// e.g. for tableView it will be [tableView reload];
}];
}

Resources