Recently read this:
Passing Data between View Controllers
Which outlines delegate setup between two controllers. The problem I am running into is that I do not have a segue between the controllers. And I am not sure I wan ta segue between those two controllers, ie I do not want to change the view when a value updates, behind the scenes I just want another controller to be aware that the value did indeed change.
This is the step I am stumbling on from the link above:
The last thing we need to do is tell ViewControllerB that
ViewControllerA is its delegate before we push ViewControllerB on to
nav stack.
ViewControllerB *viewControllerB = [[ViewControllerB alloc]
initWithNib:#"ViewControllerB" bundle:nil]; viewControllerB.delegate =
self [[self navigationController] pushViewController:viewControllerB
animated:YES];
I am not using nibs, nor do I think a prepare for segue is the correct place to wire the delegate up. Is there some other place I am supposed to wire up the delegate?
here is my code:
SettingsVeiwController, want to let another controller know when the user updates the refresh rate field.
#import <UIKit/UIKit.h>
#protocol SettingsViewControllerDelegate <NSObject>
-(void) didUpdateRefreshRate:(NSString *)refreshRate;
#end
#interface SettingsViewController : UITableViewController <UITextFieldDelegate>
#property (weak, nonatomic) IBOutlet UITextField *refreshRateTextField;
#property (copy, nonatomic) NSNumber *refreshRate;
#property (weak, nonatomic) id <SettingsViewControllerDelegate> delegate;
#end
MainViewController, want to get updates when refreshRate changes,
#interface MainViewController ()
#end
#implementation MainViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void) didUpdateRefreshRate:(NSString *)refreshRate {
NSLog(#"This was returned from SettingsViewController %#",refreshRate);
}
#end
I think everything is setup correctly to work except telling SettingsViewController that the MainViewController is its delegate.
Probably most important is that this is a tabbed application. MainViewController is one tab, SettingsViewController is another. Might be a good way to set this up when using a tabbed application, ie how do I pass info/data between tabs. I assume it via delegates still just need to know where to wire them together.
A good place would be MainViewController viewDidLoad (assuming that MainViewController has nothing to do with the presentation of SettingsViewController). You just need to get the instance of SettingsViewController and set its delegate to self.
That said, I'm going to assume that SettingsViewController is presented after MainViewController, so no instance of SettingsViewController exists when MainViewControllers view is loaded. In that case, whichever controller has the responsibility of creating and presenting an instance of SettingsViewController needs to do something to tell MainViewController that it has done so. A good solution to this would be notifications as you want to keep cross coupling low and not teach this class about the requirements of MainViewController.
Define your own notification name:
#define SettingsViewControllerWasCreatedNotification #"SettingsViewControllerWasCreatedNotification"
Setup MainViewController as an observer:
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(settingViewControllerShown:)
name:SettingsViewControllerWasCreatedNotification
object:nil];
}
After SettingsViewController is created, post the notification
[[NSNotificationCenter defaultCenter] postNotificationName:SettingsViewControllerWasCreatedNotification
object:settingsViewController];
The new settings view controller is the object. Now, your MainViewController will receive the callback and can add itself as the delegate:
- (void)settingViewControllerShown:(NSNotification *)note
{
SettingsViewController *settingsViewController = (SettingsViewController *)[note object];
settingsViewController.delegate = self;
}
Related
I am trying to inject a dependency (to a data store) into all the viewControllers in my tabbed app, from AppDelegate, rather than access the datastore by reaching back into the appDelegate. I am using a storyboard.
I do this in application:didFinishLaunchingWithOptions:, and the code executes without errors.
However, when any of the viewControllers is presented, the property into which I have injected the datastore contains nil. I was expecting it to have a reference to the datastore.
I thought maybe my datastore went out of scope after application:didFinishLaunchingWithOptions returns and caused the datastore to become nil. But to my knowledge ARC should prevent that.
I started to suspect that maybe the VCs might go out of existence after application:didFinishLaunchingWithOptions: finishes running. So I added a dealloc method to the view controllers to see if it gets called, and lo-and-behold, it did. That explains why the dependency I injected previously is no longer there.
Now I am stuck, as I don't know how else to inject the dependency into the view controllers. The only idea I have left is to add properties to my AppDelegate and use them to retain the view controllers, but that feels a bit dangerous cause I'm now interfering with iOS management of view controllers.
Here is the code in AppDelegate:
//AppDelegate.h
#import <UIKit/UIKit.h>
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#end
//AppDelegate.m
#import "AppDelegate.h"
#import "InjectedViewController.h"
#import "InjectedDataStore.h"
#interface AppDelegate ()
#property (strong, nonatomic) InjectedDataStore *myDataStore;
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
UIStoryboard *storyBoard;
storyBoard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *initViewController = [storyBoard instantiateInitialViewController];
[self.window setRootViewController:initViewController];
if (!_myDataStore) {
self.myDataStore = [[InjectedDataStore alloc]init];
NSLog(#"alloc inited %#", self.myDataStore);
}
UITabBarController *tabBarController = (UITabBarController *)initViewController;
for (InjectedViewController *ivc in tabBarController.viewControllers) {
ivc.dataStore = self.myDataStore;
NSLog(#"dataStore injected into ivc: %#", ivc.dataStore);
}
NSLog(#"application:didFinishLaunching... done");
return YES;
}
#end
here is my view controller subclass with the property into which I want to inject:
//InjectedViewController.h
#import <UIKit/UIKit.h>
#import "InjectedDataStore.h"
NS_ASSUME_NONNULL_BEGIN
#interface InjectedViewController : UIViewController
#property (strong, nonatomic) InjectedDataStore *dataStore;
#end
NS_ASSUME_NONNULL_END
InjectedViewController.m is boilerplate and otherwise empty.
InjectedDataStore.m and .h are a boilerplate Cocoa Touch class without any properties or methods.
and here is one of the viewcontrollers - it is embedded in a tab view. (The other view controller for the other tab is identical.
//FirstViewController.h
#import <UIKit/UIKit.h>
#import "InjectedViewController.h"
#interface FirstViewController : InjectedViewController
#end
//FirstViewController.m
#import "FirstViewController.h"
#interface FirstViewController ()
#end
#implementation FirstViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(#"data store for FirstVC: %#", self.dataStore);
}
- (void) dealloc {
NSLog(#"First VC dealloc called");
}
#end
and finally, the console output:
alloc inited <InjectedDataStore: 0x600001d483a0>
dataStore injected into ivc: <InjectedDataStore: 0x600001d483a0>
dataStore injected into ivc: <InjectedDataStore: 0x600001d483a0>
application:didFinishLaunching... done
First VC dealloc called ///this is what causes the injected element to disappear.
Second VC dealloc called ///causes the injected element to disappear.
data store for FirstVC: (null)
data store for SecondVC: (null)
(I would eventually implement a protocol, but for now since I am stuck at getting the injection to work I have left that out.)
It seems to me what I have done here is very similar to #juanignaciosi 's answer to this question:
Appreciate any feedback, I am a relative newbie to iOS.
Your's window property is nil.
Since iOS 13 system is using window property from scene delegate.
Just remove scene delegate and UIApplicationSceneManifest from plist if you don't need it.
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 searched for answers like Get to UIViewController from UIView? and couple of other answers but was not successful.
My issue is that I have a button in UIView lets say class1 and when I click on that button I want to load another view class2 which is UIViewController, and as I don't get navigationController in class1 I am unable to load the class2 view.
Please help me with this.
Thanks,
In Advance.
In general UIViews should not contain any logic that triggers the flow of app. This is the job of UIViewControllers. It's just a way of making the design of your code better and more organized.
One way I often use is to use a delegate pattern in my custom UIViews. Here is s simple setup:
In your MyCustomView .h file:
#class MyCustomView;
#protocol MyCustomViewDelegate <NSObject>
#optional
- (void)myViewDidTapOnButton:(MyCustomView)myCustomView;
#end
#interface MyCustomView : UIView
#property (weak, nonatomic) id <MyCustomViewDelegate> delegate;
#end
In your MyCustomView .m file:
- (IBAction)didTapMyButton:(id)sender {
if ([self.delegate respondsToSelector:#selector(myViewDidTapOnButton:)]) {
[self.delegate myViewDidTapOnButton:self];
}
}
Then in your viewcontroller, which is presenting your view:
interface:
#interface MyViewController ()<MyCustomViewDelegate>
#property (weak, nonatomic) IBOutlet UIView *myCustomView;
and implementation:
- (void)viewDidLoad {
[super viewDidLoad];
self.myCustomView.delegate = self;
}
- (void)myViewDidTapOnButton:(MyCustomView)myCustomView {
... code for presenting viewcontroller ...
}
Note:
Even if you dont use the parameter myCustomView which is sent in this method, its a common pattern and good habit to always send the sender of the delegate as the first parameter.
This is also used a lot by Apple, e.g. in
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
Two cases :
If you are using storyboard then give your NavigationController a
storyboard id. And create an object of navigationController in your
custom UIView class.
If you have customized the app launching from AppDelegate create a
public property of your navigationController. From your UIView class create an object of appDelegate with [UIApplication sharedApplication].delegate. From this object access the navigationController property
When you have the navigationController object you can push your viewcontroller with:
[navigationController pushViewController:ViewController animated:YES];
First fill storyboard ID with "MyViewController", which is a String field that you can use to create a new ViewController based on that storyboard ViewController. And later access that view controller like this:
- (IBAction)buttonPressed:(id)sender{
MyCustomViewController *newvc = [self.storyboard instantiateViewControllerWithIdentifier:#"MyViewController"];
[self presentViewController:newvc animated:YES completion:nil];
}
When you click your button,you can do this:
YouViewController *yourViewController = [YouViewController new];
[self.view addSubView:yourViewController.view];
Hope to help you.
I am passing textfield data between two view controllers. i have two view controllers Firstviewcontroller, Secondviewcontroller. i have embed them in navigation but when i click the button on first view controller, second view controller is not showing only black screen is showing.below is my code.
First view controller.h
#import <UIKit/UIKit.h>
#import "secondViewController.h"
#interface FirstViewController : UIViewController<SecondViewControllerDelegate>
#property(nonatomic) secondViewController *secondview;
- (IBAction)btnclick:(id)sender;
#property (strong, nonatomic) IBOutlet UITextField *textfield1;
#end
First view controller.m
- (IBAction)btnclick:(id)sender {
secondViewController *SecondViewController= [[secondViewController alloc]init];
SecondViewController.data=self.textfield1.text;
[self.navigationController pushViewController:SecondViewController animated:YES];
}
- (void)dataFromController:(NSString *)data
{
[self.textfield1 setText:[NSString stringWithFormat:#"%#",data]];
}
Seconviewcontroller.h
#protocol SecondViewControllerDelegate <NSObject>
#required
- (void)dataFromController:(NSString *)data;
#end
#interface secondViewController : UIViewController
#property (nonatomic, retain) NSString *data;
#property(nonatomic,weak)id<SecondViewControllerDelegate> _delegate;
#property (strong, nonatomic) IBOutlet UITextField *textfield2;
#end
Seconviewcontroller.m
- (void)viewDidLoad {
[super viewDidLoad];
self.textfield2.text=[NSString stringWithFormat:#"%#",_data];
// Do any additional setup after loading the view.
}
whats the reason ??
You have to either use storyboard and let it intanciate the view controller for you:
[self.storyboard instantiateViewControllerWithIdentifier:#"SecondViewController"]
Or you can use xib files and tell the view controller which one to load:
[[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil]
second view controller is not showing only black screen is showing
Typically, this means that the view controller has no view. Your code does not explain where secondViewController is expected to get its view from. Is there a secondViewController.xib? If there is, is it correctly configured with a view outlet? If not, what does this view controller do in order to get its view?
Note that you have done a very odd thing with capitalization: you have given your class a small letter (secondViewController), while your instance has a capital latter (SecondViewController). This is backwards and a big mistake, and you should correct it immediately. Perhaps the problem is just a capitalization problem; if you call your class secondViewController and your xib file SecondViewController, that is not a match and they won't find each other.
I see "IBOutlet" in your code, it means you created the user interface of secondviewcontroller on storyboard. I guess [[secondViewController alloc]init] does not get UI element from storyboard then the black screen is shown. You have to create instance of secondViewController by loading from storyboard file.
Basically:
you can get storyboard via storyboard file name
on storyboard, Secondviewcontroller has an storyboard id which is set in Identity Inspector and you can use it to get instance of Secondviewcontroller
You can refer this link for more detail
loading iOS viewcontroller from storyboard
Hope this can help!
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.