Minimize creation of instances of UIVIewController - ios

I guess this is a Beginners Question, so thank you for your patience:
In viewControllerA, I call a lot of methods from viewControllerB.
Currently I'm using
viewcontrollerB * vcB= [[viewcontrollerB alloc] init];
[vcB doSomething];
I do that over and over again in my ViewControllerA, and that feels stupid.
So what would be the best approach here?
Can I make viewControllerB a property of viewControllerA?
Should I use delegate methods, and if so, how?
Looking forward to your insights

Well, there might actually be some issues related to your design approach. While you didn't specify what is the actual relation between those 2 view controllers, but even in this situation, I would say yes, you can have your second view controller as a property of the first one.
If you would have described better what kind of actions you perform and what is the relation between those 2 view controllers you could expect a more detailed and extended answer.
Seems that your second view controller is actually not being presented on screen and you just reuse some of its business methods. This is not the way it should be. You should decouple those methods in some other classes, which encapsulate that particular shared business behavior.
I would also suggest you get acknowledged with MVVM design patter which solves these and more other kind of issues: https://www.objc.io/issues/13-architecture/mvvm/

You can use delegate.
In ViewControllerB.h:
#protocol ViewControllerBDelegate <NSObject>
-(void)doSomething;
#end
#interface ViewControllerB : UIViewController
#property (nonatomic, weak) id <ViewControllerBDelegate> delegate;
#end
In ViewControllerB.m
-(void)someMethod {
if ([self.delegate respondsToSelector:#selector(doSomething)]) {
[self.delegate doSomething]; // will call delegate implement in delegate object.
}
}
In ViewControllerA:
ViewControllerB *vcB = [[ViewControllerB alloc] init];
vcB.delegate = self;
#pragma mark - ViewControllerBDelegate
-(void)doSomething {
//...
}
You should call [self.delegate doSomething] in ViewControllerB when you want do something in ViewControllerA;

Related

self.delegate is nil and does not get called

I've been struggling with a problem lately, and believe me I wouldn't post here if the solution was online somewhere.
Here is my problem :
I have a parentVC with 3 childVC linked to it. I have delegates for the 3 of them to report to the parentVC some actions, and it's working for 2 of them and not the 3rd one even if I declared the same thing for the 3 of use.
Here is my code:
In parentVC.m
// Initializing the clientsListViewController
self.clientsListViewController = [[UIStoryboard storyboardWithName:#"AGZClientsList" bundle:nil] instantiateInitialViewController];
[self addChildViewController:self.clientsListViewController];
[self.clientsListViewController didMoveToParentViewController:self];
self.clientsListViewController.delegate = self;
In my childVC.h
#protocol AGZClientsListViewControllerDelegate <NSObject>
- (void)didClickLeftBarButtonInClientsView:(UIButton*)button;
#end
#interface AGZClientsListViewController : AGZBaseViewController
#property (nonatomic, weak) id <AGZClientsListViewControllerDelegate> delegate;
#end
In my childVC.m
- (void)didClickLeftBarButton:(UIButton*)button
{
if ([self.delegate respondsToSelector:#selector(didClickLeftBarButtonInClientsView:)])
[self.delegate didClickLeftBarButtonInClientsView:button];
}
Please help me!
Is parentVC released before your delegate is invoked? You set the delegate weak, so if the object is free, it will be set to nil.
May be your parentVC is not conforming your childVC's protocol properly. When you the compiler encounters self.delegate, it is trying to find the Controller class who will should conforms to your protocol; basically it is trying to find the delegate class that should implement the methods that you have enlisted inside the #protocol ....#endpart of your childVC class. May be for some reason, it failed.
Let's try a check list-
Have you declared your custom delegate inside the protocol list in the ParentVC. In the parentVC.h class at the interface declaration do
#interface parentVC: UINavigationController
PS: I assumed your parentVc is a navigation controller. It doesn't matter whether it is Navigation controller or just a view controller.
If you have, try moving
self.clientsListViewController.delegate = self;
right after you instantiate the controller, meaning after
self.clientsListViewController = [[UIStoryboard storyboardWithName:#"AGZClientsList" bundle:nil] instantiateInitialViewController];
If still doesn't work then, put a break point after
self.clientsListViewController = [[UIStoryboard storyboardWithName:#"AGZClientsList" bundle:nil] instantiateInitialViewController];
and try printing self.clientsListViewController to see whether you have the instance of your childVc. If it prints nil then there is your problem- your parentVC doesn't have the proper instance of your childVc, so does the delegate.
In that case, try to instantiate the childVC correctly.

UITableViewController used to select an object

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

Understanding the mechanism when passing data back from a second view controller to main view controller

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.

iOS Delegate setup w/ storyboarding

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;
}

iPhone simple question: How to call a method from the main-class?

I have following code:
LoginViewController *lvc = [[LoginViewController alloc] initWithNibName:#"LoginViewController" bundle:nil];
[self presentModalViewController:lvc animated:false];
[lvc release];
That is called from my MainViewController.
Now, when the LoginViewController will be dismissed (of course this only happens when the login is correct) I must call a method in my MainViewController to load the initial data for my app.
I read a lot about delegate and tried it, but don't get it to work.
Could someone help me please?
(if possible, please with a few lines of code)
Any help is welcome!
I read a lot about delegate and tried it, but don't get it to work.
What have you tried really? Your LoginViewController must define a simple delegate protocol, and your MainViewController must conform to it.
All you need to do is add something like this in LoginViewController.h above #interface:
#protocol LoginViewControllerDelegate
- (void)loginViewControllerDidFinish;
#end
Which declares a protocol with one method. Then add this between #interface and #end:
#property (nonatomic, assign) id <LoginViewControllerDelegate> delegate;
Which means your login view controller will have a property called delegate which will point to an instance of any class (that's what id means) that conforms to it's delegate protocol (the thing between < and >). Don't forget to #synthesize delegate; inside .m file.
Now what you need to do is inside MainViewController.h add to #interface line like this:
#interface MainViewController : UIViewController <LoginViewControllerDelegate>
Which tells the compiler your MainViewController class conforms to this LoginViewControllerDelegate delegate protocol. Now implement the - (void)loginViewControllerDidFinish; method inside MainViewController.m and before presenting the login view controller modally set it's delegate to self (login.delegate = self;). When you are done inside your login view controller, before you dismiss it, call the delegate method on your delegate:
[self.delegate loginViewControllerDidFinish];
And that's it. Any more questions?
Try this:
1) when pushing login view, set some flag in MainViewController
2) in method viewWillAppear in MainViewController check that flag from 1). If it is set then load the initial data and unset flag. Otherwise push LoginView.
You've got an UIApplicationDelegate, and it should have an instance variable that points to the MainViewController. Expose this instance variable via a property, say mainViewController (on your UIApplicationDelegate), and then you can access it like this:
[(MyUIApplicationDelegate *)[[UIApplication sharedApplication] delegate] mainViewController]

Resources