Xcode (storyboards and segues): why delegates instead of references? - objective-c

I have read the following tutorial regarding storyboard.
Basically the sample App created in this tutorial let the user navigate between various views and it's created using segue.
In order to navigate between views the tutorial say to create two UITableViewController and when "going" from one to another to specify a delegate:
First controller:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"AddPlayer"])
{
UINavigationController *navigationController = segue.destinationViewController;
PlayerDetailsViewController *playerDetailsViewController = [[navigationController viewControllers] objectAtIndex:0];
playerDetailsViewController.delegate = self;
}
}
Second controller:
#protocol PlayerDetailsViewControllerDelegate <NSObject>
- (void)playerDetailsViewControllerDidCancel: (PlayerDetailsViewController *)controller;
- (void)playerDetailsViewController: (PlayerDetailsViewController *)controller didAddPlayer:(Player *)player;
#end
#interface PlayerDetailsViewController : UITableViewController
#property (nonatomic, weak) id <PlayerDetailsViewControllerDelegate> delegate;
When "going back":
- (IBAction)cancel:(id)sender
{
[self.delegate playerDetailsViewControllerDidCancel:self];
}
My simple question is why this complication? Why use delegates and protocols?
I have changed the code using a "Java" style and now I'm passing to the second controller a reference to the first one, everything is working.
First controller:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
...
playerDetailsViewController.playerViewController = self;
}
Second controller:
#property (strong, readwrite) PlayerViewController *playerViewController;
So, what are the benefits to use delegates instead of simply passing references between ViewControllers?
Thanks!
Guido

Several reasons:
As Leonardo says, using references you couple the two view controllers together unnecessarily. You should just pass the data that's required and not the whole class
This is just how Objective-C apps tend to be constructed. By using a different method you'd be making your app harder to understand by seasoned developers
(You don't always need the delegate in your second class -- for example when it's display only -- so your example code is more complex than is often the case)
Related to the last point, your code is already harder than it needs to be. UIStoryboardSegue has properties pointing to the source and destination view controllers; there's no need to mess with the navigation controller.

To put in a Java manner, if you use a strong type you are tied to a single class.
Instead the delegate is in the end a class conform to a protocol.
So you could pass many class as delegates to playerDetail, as long as they are conform to the #protocol.
Is like casting and passing interface instead of concrete class in java.
You may well know the List interface and all the ArrayList, LinkedList... etc concrete implementations.
One thing I don't understand is why they get destination controller by passing trough the navigation. I always used:
MyDestinationViewController *dvc = [segue destinationViewController];
so that you can use in many situation where you do not have a navigation controller.

Related

Adding functionality to all subclasses of UIViewController

Here is my situation and I'm confused as to the best way to do this (SubClass,Category,Extension, just write it in all the classes I want). I want to basically add a managedObjectContext to a bunch of my view controllers and have it passed around. I've had to add the following code to a majority of my controllers and I realize there is probably an easier way to do this:
Header Files:
#interface
#property(weak, nonatomic) NSManagedObjectContext *managedObjectContext;
#end
.m files:
// Synthesize the variable
#synthesize managedObjectContext;
and
// Pass on managedObjectContext
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// If the destination VC is able to take teh setManagedObjectContext method the current objectContext will be passed along.
if ([segue.destinationViewController respondsToSelector:#selector(setManagedObjectContext:)]) {
[segue.destinationViewController performSelector:#selector(setManagedObjectContext:)
withObject:self.managedObjectContext];
} else {
NSLog(#"Segue to controller [%#] that does not support passing managedObjectContext", [segue destinationViewController]);
}
}
#end
So here is where the question is: I have
UIViewControllers
UITableViewControllers
Probably-another-uiController
What is the best method of adding these functions to my class?
As a side not, however, I do want my class to be able to add additional methods to the prepareForSegue call which makes me think perhaps subClassing things would be best. That way in my individual class implementation i can call [super prepareForSegue...]. That begin said, however, I assume I'd have to make a single class per view controller because you can't do multiple inheritance or have I managed to confuse myself?
-- EDIT --
Various Comments have suggested a Category is the right way to go, however, if I make a category for prepareForSegue am I still able to make a call somehow equivalent to [categoryVersion prepareForSegue] I realize I could probably use a different function name but I do want to modify the default functionality of prepareForSegue

Push to UIViewController dynamically

I am a newbie in iPhone application development.
I am developing an iPad application. It contains a menu bar on top, clicking on which retrieves a sub view. The sub view consists of UIPickerView. Upon selecting a row from UIPickerView, navigates to another UIViewController.
The UIPickerView methods are written in a separate class (As this functionality comes throughout the app, I made it a general one). So,
[self.navigationController pushViewController:controller animated:YES];
will not work for me!
I was able to get the name of the class to be pushed (It changes according to the selection made). Is there any way I can do it?
Thanks In Advance :-)
I guess what you really want is to create an object from a classname
The simple answer is
[[NSClassFromString(className) alloc] init...]
For a more thorough answer you should look at Create object from NSString of class name in Objective-C
You can use delegate method (delegate methods allows communication between objects) to implement this scenario
For example in your UIPicker(.h) class define a delegate protocol as follows
#protocol pickerProtocol;
#interface MyPicker : NSObject {
id <pickerProtocol> pickerDelegate;
}
#property(nonatomic,retain) id <pickerProtocol> pickerDelegate;
#end
#protocol pickerProtocol
- (void) pushViewController;
#end
And call this delegate method when selecting a row from UIPickerView
[pickerDelegate pushViewController];
Then in all view controller that uses picker write the implementation of the delegate method
- (void) pushViewController {
[self.navigationController pushViewController:controller animated:YES];
}
dont for get to set the delegate as follows
MyPicker *picker = [MyPicker alloc]init];
picker.pickerDelegate = self;

Dismissing view using delegation not working

I am trying to use delegation, which I am new at, to dismiss a modally presented view. I am trying to get it to work along the lines of the apple documentation seen here. So far my code is as follows:
Put both views on storyboard, connect first to second view with modal segue. (the segue to view2 works fine)
create delegate inside second viewcontroller/create method to call when returned:
//inside of view2ViewController.h
#class view2ViewController;
#protocol view2ViewControllerDelegate <NSObject>
-(void)goBack:(OptionsViewController *)controller;
#end
#interface OptionsViewController : UIViewController
#property (nonatomic, weak) id <view2ViewControllerDelegate>delegate;
- (IBAction)return:(id)sender;//connected to button
#end
implement delegate in view1ViewController #interface view1ViewController : UIViewController <view2ViewControllerDelegate>
write code for delegate method goBack in view1Controller.m
-(void)goBack:(view2ViewController *)controller{
[self dismissViewControllerAnimated:YES completion:nil];}
finish by writing code for return method in view2ViewController.m
- (IBAction)return:(id)sender {
[self.delegate goBack:self];}
I'm not sure where this code is going wrong. The return method is called, but then goBack isn't. I did read the developer documentation, and thought I understood, but I guess not...
PS I change the names of all of my class/variable names on StackOverflow to be more generic, so if there is a slight discrepancy between variable name spellings, it's probably because i typed one wrong.
The best shot I can try -
Make sure you assigned the SplashViewController as the delegate of the view2ViewController.
By code you can do it like that (in the SplashViewController m file):
view2ViewController.delegate = self;
Or you can do to on Story board.
BTW
I a not sure calling your function "return" is a good idea.

Simple Passing of variables between classes in Xcode

I am trying to do an ios app but I am stuck at passing data between classes .
This is my second app . The first one was done whit a global class , but now I need
multiple classes . I tried many tutorials , but did not work or the passed value was always zero . Can someone please write me a simple app to demonstrate passing of variables in IOS 5 .
Nothing special , storyboard whith 2 view controllers , one variable .
Thank you for your help .
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Navigation logic may go here. Create and push another view controller.
FirstViewController *fv;
fv.value = indexPath.row;
NSLog(#"The current %d", fv.value);
FirstViewController *detail =[self.storyboard instantiateViewControllerWithIdentifier:#"Detail"];
[self.navigationController pushViewController:detail animated:YES];
}
here is the code from my main view and i need to send the indexPath.row or the index of the cell that i pressed to the next view
There are several things to do. Depending on the app, you could either add a variable to the AppDelegate class, making it availible to all classes through a shared instance. The most common thing (I think) is to make a singleton. For that to work, you can make a class, say StoreVars, and a static method that returns the object, which makes the class "global". Within the method, you initialize all your variables, like you always would. Then you can always reach them from wherever.
#interface StoreVars : NSObject
#property (nonatomic) NSArray * mySharedArray;
+ (StoreVars*) sharedInstance;
#implementation StoreVars
#synthesize mySharedArray;
+ (StoreVars*) sharedInstance {
static StoreVars *myInstance = nil;
if (myInstance == nil) {
myInstance = [[[self class] alloc] init];
myInstance.mySharedArray = [NSArray arrayWithObject:#"Test"];
}
return myInstance;
}
This will make a singleton. If you remember to import "StoreVars.h" in your two viewControllers, you can access the now shared array like this;
[StoreVars sharedInstance].mySharedArray;
^
This is a method returning a StoreVars object. Within the StoreVars class, you can implement any object and initialize it in the static method. Just always remember to initialize it, or else, all your object will be 0/nil.
If you are not a fan of the UINavigationController and would rather use segues, it's a lot easier, but can make your app rather "messy" imo. There is a method implemented in UIViewController you should overload:
- (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"])
{
// Get reference to the destination view controller
YourViewController *vc = [segue destinationViewController];
// Pass any objects to the view controller here, like...
[vc setMyObjectHere:object];
}
}
source: How to pass prepareForSegue: an object
Do some research before asking questions like this. Read some tutorials, and try out yourself, and then ask questions related to what you are really looking for. It's not everyday people want to do all the work for you, but sometimes you're lucky. Like today.
Cheers.
if you use segue between 2 controllers you must overload prepareToSegue method
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// check if it's the good segue with the identifier
if([[segue identifier] isEqualToString:#"blablabla"])
{
// permit you to get the destination segue
[segue destinationViewController];
// then you can set what you want in your destination controller
}
}
The problem you face is quite perplexing for beginners. "Solving" it wrong way can result in learning a ton of bad habits.
Please have a look at Ole Begemann's excellent tutorial on Passing Data Between View Controllers - it's really worth reading.

Xcode classes can't "see" one another

I was working on an Xcode project and everything was going great, until two of my classes stopped recognizing each other. Here is an excerpt:
#import "JBXViewController.h"
#interface ViewController2 : UIViewController {
JBXViewController *jbx;
}
For some reason I get the error "Unknown type name 'JBXViewController'; did you mean 'UIViewController'?" I don't understand how this is possible, since I'm importing the other class just a few lines above. Any insights would be much appreciated!
When you say they "can't see each other", I assume you mean you are also importing ViewController2.h in your JBXViewController.h. So you have one header importing another header, which imports the first header, which imports the second, which imports the first again...
Instead, use a forward reference to JBXViewController:
#class JBXViewController;
#interface ViewController2 : UIViewController {
JBXViewController *jbx;
}
And then #import "JBXViewController.h" in your implementation instead (in your ViewController2.m)
Firoze Lafeer's answer is correct, but this problem is probably a symptom of poor design in your code.
I assume that, in your app, JBXViewController is a parent view controller, and it sometimes shows a ViewController2 for some specific function. (For example, JBXViewController shows a list of records, while ViewController2 edits one of the records.) In a situation like this, ViewController2 should not know the details of JBXViewController. Instead JBXViewController should give ViewController2 the data it needs via ViewController2's properties, and if ViewController2 has to call methods on JBXViewController, they should be part of a delegate protocol.
For example, suppose JBXViewController currently has the following property, and ViewController2 accesses it:
#property (strong) JBXObject * currentObject;
You should instead have a currentObject property on ViewController2, and JBXViewController should set it before showing the view controller:
self.myViewController2.currentObject = self.currentObject;
[self.navigationController pushViewController:self.myViewController2 animated:YES];
This works for one-way communication—JBXViewController can give ViewController2 data. If data needs to flow back up to JBXViewController (other than by changing currentObject's properties), you should set up a delegate for ViewController2. For example:
#protocol ViewController2Delegate; // forward declaration
#interface ViewController2 : UIViewController
#property (weak) id <ViewController2Delegate> delegate;
...
#end
#protocol ViewController2Delegate <NSObject>
- (void)viewController2ShouldSave:(ViewController2*)viewController2;
- (BOOL)viewController2:(ViewController2*)viewController2 shouldAddSomoflange:(JBXSomoflange*)aSomoflange;
#end
Then have JBXViewController conform to the protocol:
#interface JBXViewController : UIViewController <ViewController2Delegate>
Set the delegate, either in Interface Builder or in code like so:
self.myViewController2.delegate = self;
self.myViewController2.currentObject = self.currentObject;
[self.navigationController pushViewController:self.myViewController2 animated:YES];
And implement all the methods listed in ViewController2Delegate.
Together, these changes mean three things:
ViewController2 does not need specific knowledge of how JBXViewController works. This means you no longer have to import JBXViewController.h in ViewController2.h, which solves your immediate problem.
JBXViewController is now more flexible. As long as it sets the appropriate properties in ViewController2 and implements any necessary delegate methods, you can change anything you want in JBXViewController and ViewController2 will never know or care about it.
ViewController2 is now more flexible too. You can use it from other parts of the app, or move it to another app. You can insert a screen between JBXViewController and ViewController2.
These changes aren't necessary to get the app running on your device and functioning the way you intend. But you'll have an easier time down the road if you start adopting these sorts of designs.