Objective C terminology: outlets & delegates - iphone

I'm having issues understanding the concept of outlets how the iPhone deals with events. Help! Delegates confuse me too. Would someone care to explain, please?

Outlets (in Interface Builder) are member variables in a class where objects in the designer are assigned when they are loaded at runtime. The IBOutlet macro (which is an empty #define) signals Interface Builder to recognise it as an outlet to show in the designer.
For example, if I drag out a button, then connect it to the aButton outlet (defined in my interface .h file), the loading of the NIB file at runtime will assign aButton the pointer to that UIButton instantiated by the NIB.
#interface MyViewController : UIViewController {
UIButton *aButton;
}
#property(nonatomic, retain) IBOutlet UIButton *aButton;
#end
Then in the implementation:
#implementation MyViewController
#synthesize aButton; // Generate -aButton and -setAButton: messages
-(void)viewDidAppear {
[aButton setText:#"Do Not Push. No, seriously!"];
}
#end
This eliminates the need to write code to instantiate and assign the GUI objects at runtime.
As for Delegates, they are event receiving objects used by another object (usually a generalised API class such as a table view). There's nothing inherently special about them. It's more of a design pattern. The delegate class may define several of the expected messages such as:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
...and the API object calls this message on the delegate when it wants to notify it of the event. For example:
-(void)update:(double)time {
if (completed) {
[delegate process:self didComplete:totalTimeTaken];
}
}
And the delegate defines the message:
-(void)process:(Process *)process didComplete:(double)totalTimeTaken {
NSString *log = [NSString stringWithFormat:#"Process completed in %2.2f seconds.", totalTimeTaken];
NSLog(log);
}
Such use might be:
Process *proc = [Process new];
[proc setDelegate:taskLogger];
[proc performTask:someTask];
// Output:
// "Process completed in 21.23 seconds."

A delegate is a object that another object can forward messages to. In other words, it's like when your mom told you to clean your room and you pawned it off on your little brother. Your little bro knows how to do the job (since you were too lazy to ever learn) and so he does it for you.

Related

Message Passing with UIView as delegate

I have written a third party framework that displays a UIView on top of the existing view of the calling app, i.e., the app calls the framework/SDK and the SDK shows a UIView on top of the view that is currently on screen.
This is done by the use of delegates. The app sets the current view on screen as the delegate to the framework. The framework then uses this delegates and adds its own UIView as a subview using the code:
[[self delegate] addSubview:myVc.view];
where myVc is the ViewController in the framework.
Now I need to pass a method back to the calling app saying the view was shown on screen. Since, the delegate is a UIView, how do I pass a message to the calling class?
The reason why I have asked for a UIView delegate is because my UIView takes only a part of screen and I need the other part of the screen to show the remaining part of the app and be active. When I used ViewController as a delegate, it resulted in the other part of the screen being black instead of transparent.
So my question is how do I pass a message to the calling app, which calls the SDK and sets its view as a delegate. Thanks
You can post a notification that the caller listens for or you can accept a block as a parameter from the caller and execute that when the view is shown.
If I understood your question correctly, then you simply need to define protocol :
.h
#protocol YourFrameWorkViewDelegate <NSObject>
- (void)yourFrameWorkViewDidAppear;
#end
#interface YouFrameWorkView : UIView
#property (weak, nonatomic) id <YourFrameWorkViewDelegate> delegate;
#end
.m
#implementation YouFrameWorkView
- (void)didMoveToWindow
{
if ([self.delegate respondsToSelector:#selector(yourFrameWorkViewDidAppear)]) {
[self.delegate yourFrameWorkViewDidAppear];
}
}
#end
or you could call delegate from your VC like
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if ([self.view.delegate respondsToSelector:#selector(yourFrameWorkViewDidAppear)]) {
[self.view.delegate yourFrameWorkViewDidAppear];
}
Of course any app that wants to use you frame work needs to implement that protocol. This would probably be rootVCs view would have to be subclass and implement your protocol. This is standard practice. Hope it helped.
Follow up after your comment:
Masure that view implements your protocol
.h
#interface ViewThatUsesYourFrameWork : UIView <YourFrameWorkViewDelegate>
#end
.m
#implementation ViewThatUsesYourFrameWork
- (void)yourFrameWorkViewDidAppear
{
}
#end
and also confirm that delegate is indeed set.
before you call respondsToSelector
if(!self.delegate)NSLog(#"Delegate is not set")

Where do I put delegate methods and how do I invoke them?

As a new iOS developer, I have finally stumbled across delegates. I'm trying to follow a tutorial: http://gabriel-tips.blogspot.com/2011/05/input-accessory-view-how-to-add-extra.html, But I'm having difficulty understanding where I am supposed to put the actual delegate methods.
Secondly, would anyone mind providing a dumbed down explanation of how a delegate method is invoked?
Thanks!
A delegate is simply a class that agrees to do work for another class. The delegate methods are invoked by the delegating class. The delegate must therefore, provide an implementation of the appropriate method. Let's make a simple view controller with a table view.
// MyViewController.h
#interface MyViewController : UIViewController <UITableViewDelegate>
#property (nonatomic, retain) UITableView *myTableView;
#end
Here in the MyViewController.h file I have declared my view controller to be a delegate of type UITableViewDelegate (it really means it implements the UITableViewDelegate protocol. More on this later). I have thus agreed to respond to requests to my view controller. The requests will come from the table view called myTableView. However, simply stating that I adhere to UITableViewDelegate does not make my view controller a delegate of anything. I must specify that directly:
// MyViewController.m
#import "MyViewController.h"
#implementation MyViewController
- (void)loadView
{
myTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 320, 460)];
myTableView.delegate = self;
self.view = myTableView;
}
#end
Here I specifically set MyViewController to be the delegate of myTableView. Now whenever the table view wants to ask its delegate to do something, it will send that message to my view controller. Thus, MyViewController MUST provide implementations of the appropriate delegate methods:
// MyViewController.m
#import "MyViewController.h"
#implementation MyViewController
- (void)loadView
{
myTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 320, 460)];
myTableView.delegate = self;
self.view = myTableView;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"Selected section:%i row:%i", indexPath.section, indexPath.row);
}
#end
Here I have provided an implementation for the delegate method tableView:didSelectRowAtIndexPath: which will be called by myTableView when it is appropriate (the user selects a row).
Here you can find all the delegate methods defined in UITableViewDelegate. Some are required and others are optional:
http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UITableViewDelegate_Protocol/Reference/Reference.html
In order to be a delegate of a class you should know which methods you are required to provide implementations for.
If you wanted to create your own delegate definitions, you would create a new protocol. You do not retain your delegates (see the property declaration), as this creates a retain cycle:
// MyViewController.h
#class MyViewController;
#protocol MyViewControllerDelegate
- (void)viewController:(MyViewController *)viewController didChangeSelection:(NSIndexPath *)newIndexPath;
#end
#interface MyViewController : UIViewController <UITableViewDelegate>
#property (nonatomic, retain) UITableView *myTableView;
#property (nonatomic, assign) id<MyViewControllerDelegate> delegate;
#end
Here we have created a new protocol. Any class that wants to be respond to the viewController:didChangeSelection: message could now do so. Just like with the table view above it would set the delegate to itself and then implement the method. Now that you have a delegate, you can invoke the method at an appropriate time.
// MyViewController.m
#import "MyViewController.h"
#implementation MyViewController
- (void)loadView
{
myTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 320, 460)];
myTableView.delegate = self;
self.view = myTableView;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"Selected section:%i row:%i", indexPath.section, indexPath.row);
indexPath = [NSIndexPath indexPathForRow:1 inSection:0];
[self.delegate viewController:self didChangeSelection:indexPath];
}
#end
Now the delegate can receive the message and do what it wants knowing that my view controller changed the selection.
A delegate pattern is a convenient way to allow for the communication between independent controller that allows for loose coupling. So let's say you have a pattern like this:
A
/ \
B C
Where A instantiates B and C. Communicating between A to B and A to C are easy but how would you communicate between B and C? B to A? C to A? There are a couple different ways you could do so such as Key-Value Observing or Block Callbacks. Delegation, though, are still most frequently used although Blocks are coming on strong.
In this example, object A instantiates object B to create an object and fill it with info. How would object B pass the new object back to A as you want to keep things loose? Well, with these 9 easy steps, you can do it too! It may not make sense but we'll start with ClassB…
// ClassB.h
#protocol ClassBDelegate; //(1)
#interface ClassB : NSObject
#property (nonatomic, weak) id<ClassBDelegate>bDelegate; //(2)
-(void)makeNewObjectAndSendBack;
#end
#protocol ClassBDelegate : NSObject //(3)
-(void) classB:(Class B *)theClassB finishedWithObject:(id)finishedObject; //(4)
#end
ClassB.m
#implementation
#synthesize bDelegate = _bDelegate; //(5)
-(void)makeNewObjectAndSendBack {
//something something something
[self.bDelegate classB:self finishedWithObject:newObject]; //(6)
}
#end
define the protocol that will be established later
set an instance of an object that will conform to that protocol
set up the protocol
set up the method call that you'll use to send the finishedObject back to A.
synthesize the delegate
after you do what you need to do, you send it back using the method
you defined in 4
// ClassA.h
#interface ClassA : NSObject <ClassBDelegate> //(7)
#property (nonatomic, strong) ClassB theClassB;
-(void)yourMethodToDoSomething;
#end
ClassA.m
#implementation
#synthesize theClassB = _theClassB;
-(void)randomMethod {
self.theClassB = [ClassB new];
self.theClassB.bDelegate = self; //(8)
[self.theClassB makeNewObjectAndSendBack];
}
-(void) classB:(Class B *)theClassB finishedWithObject:(id)finishedObject { //(9)
[self doSomethingWithFinishedObject:finishedObject]; //ta-da!
}
#end
7.Conform to the ClassBDelegate protocol. This basically says that you
will implement the methods defined in the protocol definition.
8.Set the classB object's delegate object as self! This is crucial and
often skipped.
9.Implement the delegate method for when you get the new object back.
So the process, in short is: A instanciate B. A sets B's delegate as self. A tells B to do something. B does something and sends object back via delegate method. A gets it back.
For more information, including what you can do with protocols, check out:
Big Nerd Ranch talk about Protocols
Part 1
Part 2
Part 3
Good luck!
It seems you might be a little confused as to what delegates are and how they are used. Here are two links to Apple documentation you might find useful: A conceptual overview and a more in depth explaination.

Multiple delegates per one object?

I have a UIScrollView that I need to subclass and within the subclass I need to attach the UIScrollViewDelegate so I can implement the viewForZoomingInScrollView method.
Then I have a UIViewController where I need to instantiate an object of this UIScrollView subclass that I created, and I would also like to make the UIViewController a UIScrollViewDelegate for this object so I can implement scrollViewDidZoom in this UIViewController class.
How is it possible to make one object have two delegates? (I know I could easily just have one delegate and just implement both methods there, but for design purposes I'd like to do it the way that I'm mentioning).
Sometimes it makes sense to attach several delegates to a scroll view. In that case you can build a simple delegation splitter:
// Public interface
#interface CCDelegateSplitter : NSObject
- (void) addDelegate: (id) delegate;
- (void) addDelegates: (NSArray*) delegates;
#end
// Private interface
#interface CCDelegateSplitter ()
#property(strong) NSMutableSet *delegates;
#end
#implementation CCDelegateSplitter
- (id) init
{
self = [super init];
_delegates = [NSMutableSet set];
return self;
}
- (void) addDelegate: (id) delegate
{
[_delegates addObject:delegate];
}
- (void) addDelegates: (NSArray*) delegates
{
[_delegates addObjectsFromArray:delegates];
}
- (void) forwardInvocation: (NSInvocation*) invocation
{
for (id delegate in _delegates) {
[invocation invokeWithTarget:delegate];
}
}
- (NSMethodSignature*) methodSignatureForSelector: (SEL) selector
{
NSMethodSignature *our = [super methodSignatureForSelector:selector];
NSMethodSignature *delegated = [(NSObject *)[_delegates anyObject] methodSignatureForSelector:selector];
return our ? our : delegated;
}
- (BOOL) respondsToSelector: (SEL) selector
{
return [[_delegates anyObject] respondsToSelector:selector];
}
#end
Then simply set an instance of this splitter as a delegate of the scroll view and attach any number of delegates to the splitter. All of them will receive the delegation events. Some caveats apply, for example all the delegates are assumed to be of the same type, otherwise you’ll have trouble with the naive respondsToSelector implementation. This is not a big problem, it’s easy to change the implementation to only send delegation events to those who support them.
You don't want an object with 2 delegates. You want to keep your customScrollView keep the responsibility of its own UIScrollViewDelegate functions.
To make your parentVC respond to the delegate methods of UIScrollView as well you will have to make a custom delegate inside your customScrollView.
At the moment a UIScrollViewDelegate function gets called you will also call one of your delegate functions from your custom delegate. This way your parentVC will respond at the moment you want it to.
It will look somewhat like this.
CustomScrollView.h
#protocol CustomDelegate <NSObject>
//custom delegate methods
-(void)myCustomDelegateMethod;
#end
#interface CustomScrollView : UIScrollView <UIScrollViewDelegate>
{
id<CustomDelegate> delegate
//the rest of the stuff
CustomScrollView.m
-(void) viewForZoomingInScrollView
{
[self.delegate myCustomDelegateMethod];
//rest of viewForZoomingInScrollView code
ParentVC.h
#interface CustomScrollView : UIViewController <CustomDelegate>
{
//stuff
ParentVC.m
-(void)makeCustomScrollView
{
CustomScrollView *csv = [[CustomScrollView alloc] init];
csv.delegate = self;
//other stuff
}
-(void)myCustomDelegateMethod
{
//respond to viewForZoomingInScrollView
}
I hope this fully covers your problem.
Good luck.
Short answer: you don't. Delegates are typically a weak one-to-one relationship:
#property (nonatomic, weak /*or assign*/) id<MyViewDelegate> delegate;
Sometimes you will see a "listener" design pattern, which is the one-to-many form of delegates:
- (void) addListener:(id<MyViewListener>)listener;
- (void) removeListener:(id<MyViewListener>)listener;
In your case, there doesn't appear to be a nice public override point in UIScrollView that allows subclasses to specify the viewForZoomingInScrollView. I would avoid making the UIScrollView its own delegate, if possible. You could make the UIViewController the UIScrollViewDelegate and have it provide the viewForZooming. Or you could make an intermediate view subclass which uses UIScrollView, provides the viewForZooming, and forwards the other delegate methods up.
I don't think you can have two UIScrollViewDelegate delegates directly connected to the same object.
What you can do is having the two delegates chain-connected. I.e., you connect one delegate to the other, then have the former forward messages to the latter when it cannot handle them itself directly.
In any case, I think I am missing a bit to fully suggest a solution, namely the reason why you do need a second delegate and cannot do always through one single delegate. In other words, what I think is that there might be alternative designs that would avoid needing two delegates.
Here's another potential problem with what you're trying to do...
Let's say you have two instances of a UIScrollView and one delegate object. In the delegate object, you override scrollViewDidScroll(UIScrollView *): method of the UIScrollViewDelegate protocol.
Inside the method, you want to access the value of the contentOffset property of both scroll views because, perhaps, you have two adjacent collections views, and you're trying to get the index path of the item at the center of the collection view to get the values of properties associated with those two items (think UIDatePicker).
In that case, how do you different between scroll views? The scrollView property only refers to one scroll view; but, even if it referred to both, how do you get the value of their respective contentOffset properties?
Now, you might say, "I can create an IBOutlet for both, and use their assigned references instead of the scrollView property in the delegate method, such as self.collectionViewFirst.contentOffset and self.collectionViewSecond.contentOffset, and ignore the scrollView property of the delegate method.
The problem is this: that property isn't stored. It's only available when the delegate method is called. Why? Because there's only one delegate object, and only one contentOffset property. By scrolling another scroll view, the value of the contentOffset property would change, and not reflect the content offset of any other scroll view except the last one scrolled.
It's bad practice to do what you're trying to do, even if the case (or a case like it) as I described doesn't apply to your situation. Remember: writing code is about sharing code. Incorrect code sends a message to others that diminishes your reputation.

Can I avoid explicitly casting objects with a common subclass?

I have an iPodLibraryGroup object and Artist and Album both inherit from it.
When it comes to my view controllers though I find that I'm duplicating lots of code, for example I have an ArtistListViewController and and AlbumListViewController even though they're both doing basically the same thing.
The reason I've ended up duplicating the code is because these view controllers each refer to either an Artist object or al Album object and I'm not sure how to set it up so that one view controller could handle both — these view controllers are mainly accessing methods that that the objects have in common from iPodLibraryGroup.
As an example, to hopefully make this clearer consider this code in AlbumListViewController:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
Album *album = nil;
album = [self albumForRowAtIndexPath:indexPath inTableView:tableView];
…
if (!album.thumbnail)
{
[self startThumbnailDownload:album forIndexPath:indexPath inTableView:tableView];
cell.imageView.image = [UIImage imageNamed:#"Placeholder.png"];
}
else
{
cell.imageView.image = album.thumbnail;
}
return cell;
}
This is essentially completely repeated (along with a hell of a lot more repeated code) in ArtistListViewController just so that I can typecast the local variable as an Artist instead of an Album.
Is there a way to not explicitly need to set Artist or Album here so that the same code could work for any object that is a child of iPodLibraryGroup?
Refactor your code so you have a generic ListViewController that operates on iPodLibraryGroup objects, and have ArtistListViewController & AlbumListViewController both inherit from it.
Push all the common parts up to the generic ListViewController, and have your Artist/Album controllers only implement/override the methods that need to behave differently.
Based on a suggestion received on twitter I created a protocol on iPodLibraryGroup as follows:
#protocol iPodLibraryGroup <NSObject>
#required
#property (nonatomic, retain) NSString *name;
#property (nonatomic, retain, readonly) NSString *sort_name;
…
- (void)downloadImageFromURL:(NSURL *)image_url;
- (void)cancelImageDownload;
- (void)downloadImageCannotStart;
#end
#interface iPodLibraryGroup : NSObject <iPodLibraryGroup> {
…
}
Then within my view controllers, instead of declaring pointers to declared Artists or Albums I use the syntax:
id <iPodLibraryGroup> source;
The only issue I encountered was when calling NSObject methods, I would get a compiler warning:
This issue is discussed in How to typecast an id to a concrete class dynamically at runtime? and I resolved it by casting my "source" reference as an (iPodLibraryGroup *) before calling NSObject methods, for example:
[(iPodLibraryGroup *)source addObserver:self forKeyPath:#"pendingResponseForDetailedInfo" options:0 context:nil];

Problem setting Value for UILabel (iPhone SDK)

I'm reasonably new at the iPhone SDK, so please forgive me if this answer's ridiculously obvious.
I have a ViewController connected to a .xib file (they are called FirstViewController)
I also have a custom class called MyCustomClass. In this class, I have a method like this:
- (void)setTheText {
[myLabel setText:#"foo"];
}
I want to call this method to set the text in a label in FirstViewController.xib
So far, I have done this:
Dragged in an "Object" into Interface Builder, and set the class to: MyCustomClass
Connected up the IBOutlet 'myLabel' from MyCustomClass to a UILabel in the view.
However, when I run the program, and press the button to set the label (which is in FirstViewController.m), something like:
- (IBAction)doSomething:(id)sender {
MyCustomClass *customClass = [MyCustomClass alloc] init];
[customClass setTheText];
}
This doesn't set though. NSLog(#"%#",[myLabel text]); returns (null)
Xcode shows no errors or warnings, what could be wrong?
Thanks,
Michael
Additional Info:
Interface to MyCustomClass.h:
#import <UIKit/UIKit.h>
#interface MyCustomClass : NSObject {
UILabel *myLabel;
}
#property (nonatomic, retain) IBOutlet UILabel *myLabel;
- (void)setTheText;
#end
You don't want to create a new instance of your custom class in your action method.
There are a couple of ways to do what you want.
Option 1 is to give your view controller a reference to your custom object. To do this, create an outlet of type MyCustomClass * in your view controller, connect that outlet to the new object that you created in your XIB file, and then get rid of the allocation in your action method:
- (IBAction)doSomething:(id)sender {
[customClass setTheText];
}
Option 2 is to let your CustomClass handle both the label and the action method. To do this, you can simplify things even further. Put an outlet for the UILabel into your CustomClass, and then simply convert your setTheText method into an action:
- (IBAction)setTheText:(id)sender {
[myLabel setText:#"foo"];
}
Now, connect that action up to your button, and everything should work like a charm.
Note: You should probably use a method name that does not start with "set", since those are commonly used for property setters as part of Cocoa's KVC/KVO system. Instead, I would call it something like changeLabel, or equivalent.
Your instance of customClass here is completely unrelated to the NIB. You've instantiated a new object using +alloc. If you want to modify the specific MyCustomClass in your NIB, then FirstViewController needs an IBOutlet that points to it.
UILabel also have a property called text to set the value, you might find that easier.
myObject.myLabel.text = #"Your text";
well you called your label two things, first a non-ibaction then an ibaction in the property.