How to implement delegation the right way? - iphone

I'm trying to implement delegation for a class which should call it's delegate (if any), when special things happen.
From Wikipedia I have this code example:
#implementation TCScrollView
-(void)scrollToPoint:(NSPoint)to;
{
BOOL shouldScroll = YES;
// If we have a delegate, and that delegate indeed does implement our delegate method,
if(delegate && [delegate respondsToSelector:#selector(scrollView:shouldScrollToPoint:)])
shouldScroll = [delegate scrollView:self shouldScrollToPoint:to]; // ask it if it's okay to scroll to this point.
if(!shouldScroll) return; // If not, ignore the scroll request.
/// Scrolling code omitted.
}
#end
If I try this on my own, I get a warning that the method I am calling on the delegate was not found. Of course it was not, because the delegate is just referenced by id. It could be anything. Sure at runtime that will work fine because I check if it responds to selector. But I don't want the warning in Xcode. Are there better patterns?

You could let the delegate be of the id type that implements the SomeClassDelegate protocol. For this, you could in the header of your SomeClass (in your case TCScrollView), do something like this:
#protocol TCScrollViewDelegate; // forward declaration of the protocol
#interface TCScrollView {
// ...
id <TCScrollViewDelegate> delegate;
}
#property (assign) id<TCScrollViewDelegate> delegate;
#end
#protocol TCScrollViewDelegate
- (BOOL) scrollView:(TCScrollView *)tcScrollView shouldScrollToPoint:(CGPoint)to;
#end
Then you can from your implementation, just call the method on the delegate:
#implementation TCScrollView
-(void)scrollToPoint:(NSPoint)to;
{
BOOL shouldScroll = YES;
shouldScroll = [delegate scrollView:self shouldScrollToPoint:to]; // ask it if it's okay to scroll to this point.
if(!shouldScroll) return; // If not, ignore the scroll request.
/// Scrolling code omitted.
}
#end

Following up on the sample code in drvdijk's answer, there could be a problem if there is any chance that delegate could be nil when you call the delegate method.
The return value of a message sent to nil is nil (aka 0.0 aka 0 aka NO), so if delegate is nil,
[delegate scrollView:self shouldScrollToPoint:to]
will return NO, which might not be the desired behavior in your case. It's safer to check first:
if (delegate != nil) {
shouldScroll = [delegate scrollView:self shouldScrollToPoint:to]
}
Also, if you don't want to see a compiler warning when sending messages declared by NSObject to your delegate (such as respondsToSelector:), include the NSObject protocol in your protocol declaration:
#protocol TScrollViewDelegate <NSObject>
- (BOOL) scrollView:(TCScrollView *)tcScrollView shouldScrollToPoint:(CGPoint)to;
#end

Use [NSObject performSelector:]
[delegate performSelector:#selector(scrollView:shouldScrollToPoint:) withObject:self withObject:to];
You won't get the compiler warnings anymore.
Alternatively create a prototcol and declare MyProtocol *delegate in header file.

Related

Delegate Method Protocol Not Working - Objective C

I am working on a splitView application for my iPad. I have implemented a UIButton called as Upload. On clicking on it, a UITableView appears inside a UIPoverController. On clicking on any of the contents, I want to display some respective site in my UIwebView in UIDetailView. For this I have implemented a delegate method protocol. I have used the following lines of code in UploadTableViewController.h file::
#protocol UploadTableViewDelegate <NSObject>
#required
- (void)selected:(NSString *)his;
#end
#interface UploadSpaceTableViewController : UITableViewController{
id<UploadSpaceTableViewDelegate> delegate;
}
#property (retain) id delegate;
#end
In the corresponding .m file I have used the following lines of code ::
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (delegate != nil) {
NSString *hisSelected = [keys objectAtIndex:indexPath.row];
NSLog(#"%# lolwa", hisSelected);
[delegate selected:hisSelected];
}
}
in the .m file of class where I have implemented the function Selected, the code is ::
- (void)selected:(NSString *)Key {
NSLog(#"hello");
[self.UploadSpaceTableViewPopover dismissPopoverAnimated:YES];
}
-(IBAction)uploadpressed:(id)sender{
Upload.delegate = self;
self.Upload = [[UploadSpaceTableViewController alloc]
initWithStyle:UITableViewStylePlain];
self.UploadTableViewPopover = [[UIPopoverController alloc]
initWithContentViewController:UploadSpace];
[self.UploadTableViewPopover presentPopoverFromBarButtonItem:sender
permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
However, I am unable to get hello (written in the function Selected) NSLogged in gdb. This is the first time that I am using this delegate method protocol. I am unable to sort this out. Can someone help me out ? Thanks and regards.
[delegate keySelected:hisKeySelected]; is your first problem. You don't declare a delegate method named -keySelected:, you declare a delegate method named -Selected:.
Your second problem is the fact that you are most definitely not the delegate of your table view. In order for a delegate method like -didSelectRowAtIndexPath: to be called, you must be the table's delegate.
PS, don't begin instances, or method names, with an uppercase letter. In ObjC, uppercase letters indicate a class.
EDIT: this is what your UploadSpaceTableViewController header should look like:
#protocol UploadTableViewDelegate <NSObject>
#required
- (void)selected:(NSString *)his;
#end
#interface UploadSpaceTableViewController : UITableViewController<UITableViewDataSource, UITableViewDelegate>
#property (nonatomic, assign) id <UploadSpaceTableViewDelegate>delegate; //delegates are weak!!!
#end
And the .m, I will skip a lot of the unnecessary stuff:
-(void)viewDidLoad {
[self.tableView setDelegate:self];
[self.tableView setDataSource:self];
}
//other code
Furthermore, your delegate is declared retain, which is an absolutel No-No in ObjC. Declare is weak if using ARC, or assign if not.
You are also producing a nil delegate in in your -uploadPressed: method by setting it before you explicitly own or initialize the object. Here's how it should look:
self.Upload = [[UploadSpaceTableViewController alloc]initWithStyle:UITableViewStylePlain];
Upload.delegate = self;
Delegation works like this
declare a protocol - you have done this
declare a delegate property - you have done this
In the class which you want to be the delegate say it conforms to the protocoll
#interface MyClass : MySuperClass <UploadTableViewDelegate>
set the delegate property so the delegate class can get the delegate messages
uploadSpaceTVC.delegate = self;
call the delegate methods in your non delegate class (UploadSpaceTableViewController)
[self.delegate selected:#"test"];

protocol method is not being called by the delegate object

Another case of protocol method not being called - NO idea what am I doing wrong here...
Here is the code, omitting unnecessary info...
first header file: AccessNumber.h
#protocol AddItemDelegate <NSObject>
#required
- (void) processSuccessful: (BOOL)success;
#end
#interface AccessNumber : UIViewController <UITableViewDelegate, UITableViewDataSource, ABPeoplePickerNavigationControllerDelegate, UIAlertViewDelegate> {
id <AddItemDelegate> delegate;
}
#property (retain) id <AddItemDelegate> delegate;
#end
first .m file: AccessNumber.m - I am calling the protocol method from viewdidload just for testing purposes, it should basically get called from another method, but same thing for the sake of this convo (tried both of course)
#import "AccessNumber.h"
#import "History.h"
#synthesize delegate;
- (void)viewDidLoad
{
[super viewDidLoad];
....
[[self delegate] processSuccessful:YES];
}
second file header: History.h
#interface History : UIViewController <UITableViewDelegate, UITableViewDataSource, AddItemDelegate> {
....
}
method implementation in history.m
- (void) processSuccessful: (BOOL)success {
NSLog(#"SUCCESS");
}
Appreciate any help. Thanks!
In the code i don't see something like:
theAccessNumber.delegate = self;
You must set the History instance as the delegate property of the AccessNumber instance.
Regards.
The code seems a little bit funny but you are not telling your "AccessNumber" class who is his delegate, even though you are making the "History" class implement the protocol established by yourself (otherwise you would get a warning).
You have to set the delegate for the "AccessNumber" class like this when setting it up from within "AccessNumber.m":
self.delegate = historyInstance;
or like this when setting it up from within "History.m":
accessNumberInstance.delegate = self;
There are very vew situations where you should retain a delegate, normally your delagate will outlive the object. So change your property from retain to assign. And be sure you are setting the delegate. Where are you doing it? If your object really depends on it you should be passing it in the constructor (iniWithDelagate). Try doing a NSLog before calling the delagate method just to see if it isn't nil.

How does respondsToSelector behave when there is a delegate present?

I recently tried to subclass UITextField and set the delegate to myself (found this trying ti solve my problem: http://www.cocoabuilder.com/archive/cocoa/241465-iphone-why-can-a-uitextfield-be-its-own-delegate.html)
#interface MyObject :UITextField <UITextFieldDelegate>
#end
#implementation MyObject
-(id) initWithFrame:(CGRect) frame
{
if((self=[super initWithFrame:frame]))
{
self.delegate=self;
}
return self;
}
-(BOOL) respondsToSelector:(SEL)selector
{
NSLog(#"responds to selector");
return [super respondsToSelector:selector];
}
// Implement all the missing methods
#end
Calling a method defined on the interface results in an infinite recursion. I don't see anything in the Apple docs that defines how respondsToSelector is supposed to behave in the presence of a delegate.
The docs for respondsToSelector states the following:
You cannot test whether an object
inherits a method from its superclass
by sending respondsToSelector: to the
object using the super keyword. [..]
Therefore, sending respondsToSelector:
to super is equivalent to sending it
to self. Instead, you must invoke the
NSObject class method
instancesRespondToSelector: directly
on the object’s superclass
It seems that this could be the cause for your recursion problem. I don't know if the delegate stuff is even related. Just a guess though.

how to extend a protocol for a delegate in objective C, then subclass an object to require a conforming delegate

I want to subclass UITextView, and send a new message to the delegate. So, I want to extend the delegate protocol. What's the correct way to do this?
I started out with this:
interface:
#import <Foundation/Foundation.h>
#class MySubClass;
#protocol MySubClassDelegate <UITextViewDelegate>
- (void) MySubClassMessage: (MySubClass *) subclass;
#end
#interface MySubClass : UITextView {
}
#end
implementation:
#import "MySubClass.h"
#implementation MySubClass
- (void) SomeMethod; {
if ([self.delegate respondsToSelector: #selector (MySubClassMessage:)]) {
[self.delegate MySubClassMessage: self];
}
}
#end
however with that I get the warning: '-MySubClassMessage:' not found in protocol(s).
I had one way working where I created my own ivar to store the delegate, then also stored the delegate using [super setDelegate] but that seemed wrong. perhaps it's not.
I know I can just pass id's around and get by, but My goal is to make sure that the compiler checks that any delegate supplied to MySubClass conforms to MySubClassDelegate protocol.
To further clairfy:
#interface MySubClassTester : NSObject {
}
#implementation MySubClassTester
- (void) one {
MySubClass *subclass = [[MySubClass alloc] init];
subclass.delegate = self;
}
#end
will produce the warning: class 'MySubClassTester' does not implement the 'UITextViewDelegate' protocol
I want it to produce the warning about not implementing 'MySubClassDelegate' protocol instead.
The UITextView defines its delegate as
#property(nonatomic, assign) id<UITextViewDelegate> delegate
meaning it conforms to UITextViewDelegate, and that's what compiler checks. If you want to use the new protocol, you need to redefine delegate to conform to your protocol:
#interface MySubClass : UITextView {
}
#property(nonatomic, assign) id<MySubClassDelegate> delegate
#end
The compiler shouldn't give any more warnings.
[Update by fess]
... With this the compiler will warn that the accessors need to be implemented... [I implemented this:]
-(void) setDelegate:(id<MySubClassDelegate>) delegate {
[super setDelegate: delegate];
}
- (id) delegate {
return [super delegate];
}
"
[My update]
I believe it should work if you only make a #dynamic declaration instead of reimplementing the method, as the implementation is already there:
#dynamic delegate;
For anyone still interested, this can be done quite simply like this (for sake of the example, I subclass UIScrollView):
#protocol MySubclassProtocol <UIScrollViewDelegate>
#required
-(void)myProtocolMethod;
#end
#interface MySubClass : UIScrollView
#property (nonatomic, weak) id <MySubclassProtocol> delegate;
The most important detail here is the part between the <> after your protocol's name which, put in a simple manner, signals you're extending that protocol.
In your implementation, all you need to do then is:
#synthesize delegate;
And you're done.
You need to extend the super protocol:
#protocol MYClassProtocol <SuperClassProtocol>
-(void)foo;
#end
after that DON'T (!!!) create the #property for the delegate otherwise you override the original delegate object, but simply override the method:
- (id<MYClassProtocol>)delegate
{
return (id<MYClassProtocol>)[super delegate];
}
now you can use the delegate in the classic way:
[self.delegate foo];
[self.delegate someSuperClassDelegateMethod];
Given that MySubClassMessage: is optional, you should be able to simple do a simple:
- (void) SomeMethod {
SEL delegateSelector = #selector(MySubClassMessage:);
if ([self.delegate respondsToSelector:delegateSelector]) {
[self.delegate performSelector:delegateSelector withObject:self];
}
}
The complier should still check that the implementing class conforms to your protocol (or at least claim to in the header) and you won't get the error you described.

warnings in iphone sdk

i'm getting this two messages:
warning: 'MainView' may not respond to '-switchToNoGridView'
Messages without a matching method signature will be assumed to return 'id' and accept '...' as arguments
1st here:
//GridView.m
#import "GridView.h"
#import "MainView.h"
#implementation GridView
-(IBAction)switchToNoGridView {
[mainView switchToNoGridView];
}
#end
2nd here:
warning: 'MainView' may not respond to '-goBack'
Messages without a matching method signature will be assumed to return 'id' and accept '...' as arguments
in this:
//NoGridView.m
#import "NoGridView.h"
#import "MainView.h"
#implementation NoGridView
-(IBAction)goBack {
[mainView goBack];
}
#end
how to avoid these warnings?
Have you declared switchToNoGridView and goBack in the class interface for MainView?
This warning means that the method signatures could not be found in the class of the instance that you are calling the method on; since message dispatch in Objective-C is done at runtime this is allowed, however a warning is shown.
I'm not quite sure from your code, but I think you're encountering one of two errors. Either:
You haven't declared the methods switchToNoGridView or goBack in the MainView class declaration. For Xcode to know that an object responds to a method, you have to include its definition in the class header file, like this:
#interface MainView : ParentObject {
// instance variables
}
// properties
- (void)switchToNoGridView;
- (void)goBack;
#end
This assumes you actually want to declare them as (void), of course - they can have return values, but since you don't do anything with the result of the call in your code, I'm assuming they're void. Or:
You meant to call MainView the class, not mainView the object. Since we can't see your property or instance variable definitions for GridView.h, nor can we see the current method declarations in MainView.h, it's possible that you have a static method +(void)switchToNoGridView and +(void)goBack declared on MainView, but you're calling it on an instance mainView of MainView. For example:
#interface AClass : NSObject { }
+ (void)doSomething;
#end
#implementation AClass
+ (void)doSomething {
NSLog(#"Doing something");
}
#end
#import "AClass.h"
#interface AnotherClass : NSObject {
AClass *aClass;
}
#property(nonatomic,retain) AClass *aClass;
- (void)doSomethingElse;
#end
#implementation AnotherClass
- (void)doSomethingElse {
[aClass doSomething]; // This will break at runtime
[AClass doSomething]; // but this won't
}
#end
Basically, it's possible you've confused class methods with object methods. Either way, you should check your MainView header file for the appropriate method definitions.