Setting a delegate using blocks in iPhone - iphone

On a view controller I have multiple textfields, which all use the same delegate. Now in the delegate the code gets really ugly since I have to differentiate between all the textfields (bunch of if/else-if or a switch statement). I came a cross this article:
Blocks in textfield delegates
But from this I still don't understand how this solves the problem? Doesn't this basically call one method and pass it the text and the method has no idea what textfield gave the string? You would still need to differentiate between the textfields, but this time inside the block (with the usual if(textfield == bazTextField)...).

I don't know that it exactly solves the problem so much as shifts it (and into viewDidLoad, which usually gets a bit of mush-mash in it anyway).
However in that example the block itself was being passed in the textfield to run comparisons with and "remembers" the values of all the instance variables as well (if it refers to them), so that's how it knows what text and text field is being dealt with.
I don't see how that code exactly is supposed to help though, since it assigns one block to the single delegate class to be used with all text field delegates - unless perhaps you were supposed to have one per text field, each with a different block. Then you have way more code than you'd have had with the if statements!

The article doesn't make it clear, but I believe the idea is to create one of these blocks (and block delegate objects) for each UITextField that you wish to have respond to textFieldShouldReturn.

hm, maybe I didn't completely understand the article, but I don't see the advantage of using blocks instead of selectors in that concrete example.
you could achieve something similar like this
#interface AlternativeTextFieldDelegate : NSObject <UITextFieldDelegate>
{
SEL selectorToCall;
id objectToCall;
}
- (void) setObjectToCall:(id)obj selector:(SEL)selector;
#end
#implementation AlternativeTextFieldDelegate
- (void) setObjectToCall:(id)obj selector:(SEL)selector
{
objectToCall = obj;
selectorToCall = selector;
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[objectToCall performSelector:selectorToCall];
return YES;
}
#end
and the view controller
#interface ViewWithTextFieldsController : UIViewController
{
UITextField *tf1;
AlternativeTextFieldDelegate *delegateForTF1;
UITextField *tf2;
AlternativeTextFieldDelegate *delegateForTF2;
}
// ...IBOutlets and all that...
- (void) tf1ShouldReturn; // handles shouldReturn for tf1
- (void) tf2ShouldReturn; // handles shouldReturn for tf2
#end
#implementation ViewWithTextFieldsController
- (void) viewDidLoad // or wherever
{
delegateForTF1 = [[AlternativeTextFieldDelegate alloc] init];
[delegateForTF1 setObjectToCall:self selector:#selector(tf1ShouldReturn)];
tf1.delegate = delegateForTF1;
delegateForTF2 = [[AlternativeTextFieldDelegate alloc] init];
[delegateForTF2 setObjectToCall:self selector:#selector(tf2ShouldReturn)];
tf2.delegate = delegateForTF2;
}
// ...
#end
don't really know if that's any better than chaining if-elses though.
it seems to me that this complicates things more than the problem it solves.

Related

Way to ignore "UIViewController may not respond to [method]" warning

Is there a way to make the compiler ignore this specific warning?
Here's what I do:
UIViewController *firstViewController = AppDelegate.instance.viewController;
//open the view of the clicked subItem
if ([firstViewController respondsToSelector:#selector(openView:inView:)]) {
[firstViewController openView:subItem.itemText.text inView:activeScreen]; //warning on this line
}
I know one way that works is to change UIViewController to ViewController (Name of it's class). But this fix won't work in the future, so I'm just looking for a way to ignore this warning.
It won't work in the future because, I'll be doing something like this:
//.m
UIViewController *firstViewController;
//.h
if (someCondition) {
firstViewController = AppDelegate.instance.viewController;
}
else{
firstViewController = AppDelegate.instance.otherViewController;
}
if ([firstViewController respondsToSelector:#selector(openView:inView:)]) {
[firstViewController openView:subItem.itemText.text inView:activeScreen]; //warning on this line
}
You should cast the object to the correct type where appropriate. Note that you can 'cast' to a protocol if you like. This gives you the safety of knowing that required methods are implemented without having to know the concrete type.
If you want to just have the compiler not complain, it's possible by calling performSelector:. But then you won't get compile-time checking.
[object performSelector:#selector(doSomething)];
See discussion: Using -performSelector: vs. just calling the method
If you want to pass exactly one object to your selector, it's possible by using the variant performSelector:withObject:.
If you want to pass multiple objects, you'll have to wrap them up in a container object, as described at iOS - How to implement a performSelector with multiple arguments and with afterDelay?.
In this case, you can just issue an explicit type conversion (cast):
UIViewController *firstViewController;
// ...
[(FirstViewController *)firstViewController openView:subItem.itemText.text inView:activeScreen];
Make sure to import the FirstViewController.h, so that method is known to the compiler. Tweak your code a bit:
UIViewController *vc = AppDelegate.instance.viewController;
//open the view of the clicked subItem
if ([vc respondsToSelector:#selector(openView:inView:)]) {
FirstViewController *firstViewController = (FirstViewController *) vc;
[firstViewController openView:subItem.itemText.text inView:activeScreen];
}
That should do the trick.

objective-c: Delegate object argument getting overwritten when i create multiple instances of custom class

EDIT: I apologize for wasting time, the erorr had nothing to do with what I'm taking about but rather some logic in my code that made me believe this was the cause. I'm awarding Kevin with the correct answer since using his idea to pass the whole AuthorSelectionView, and his note on correcting the NSNumer mistake. Sorry about that.
I've been trying to figure this out for hours, and even left it alone for a day, and still can not figure it out...
My situation is as follows:
I've created a custom class that implements 'UIView' and made this class into a protocol as follows:
custom UIView h file
#protocol AuthorSelectionViewDelegate <NSObject>
-(void)AuthorSelected:(NSNumber *)sender;
#end
#import <UIKit/UIKit.h>
#interface AuthorSelectionView : UIView
#property (nonatomic,assign) id<AuthorSelectionViewDelegate> delegate;
#property (strong,retain) NSNumber *authorID;
- (id)initWithFrame:(CGRect)frame withImage:(UIImage *)img withLabel:(NSString *)lbl withID:(int)authorID ;
#end
the implementation...
- (id)initWithFrame:(CGRect)frame withImage:(UIImage *)img withLabel:(NSString *)lbl withID:(int)authorID
{
self = [super initWithFrame:frame];
if (self) {
self.authorID = [[NSNumber alloc] initWithInt:authorID]; //used to distinguish multiple instances of this class in a view.
...
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, FRAMEWIDTH, FRAMEHEIGHT)];
[button addTarget:self action:#selector(CUSTOMBUTTONCLICK) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:button];
}
return self;
}
- (void) CUSTOMBUTTONCLICK
{
[self.delegate performSelector:#selector(AuthorSelected:) withObject:self.authorID];
}
Now the method in my delegate object gets called just fine, but my major problem here is that something is going on with the object being pass through when i have multiple instances of the AuthorSelected class alloc'd.. (the NSNumber authorID). I'm getting some weird behavior with it. It seems almost random with the value being passed, but i'm detecting some pattern where the value passed through is coming up late..
thats confusing so ill try to explain:
I create two instances of the AuthorSelected view, one with authorID=1 and the other with authorID=2.
On the first press, lets say i press the first button, i'll get 1 as expected.
On the second press, if I press the 1st custom button, i'll get '1', but if i press the second i'll still get 1.
On the third go, either button will give me back '2'
I feel like this is some issue with pointers since that has always been a weak point for me, but any help would be greatly appreciated as I can not seem to figure this one out.
Thank you!
EDIT:
as requested here is how I create the AuthorSelectionView Objects...
AuthorSelectionView * asView01 = [[AuthorSelectionView alloc]
initWithFrame:CGRectMake(0, 0, FRAMEWIDTH, FRAMEHEIGHT)
withImage:userPic1
withLabel:randomUserName
withID:1];
asView01.delegate = self;
AuthorSelectionView * asView02 = [[AuthorSelectionView alloc]
initWithFrame:CGRectMake(0, 0, FRAMEWIDTH, FRAMEHEIGHT)
withImage:userPic2
withLabel:randomUserName2
withID:2];
asView02.delegate = self;
A detail that may be important:
As soon as i click on one of these custom views, my code is set to (for now) call the method that runs the above AuthorSelectionView alloc code, so that i can refresh the screen with the same layout, but with different userpic/userName. This is poor design, I know, but for now I just want the basic features to work, and will then worry about redrawing. I metion this tidbit, becuase I understand that objective-c 'layers' veiws on top of eachother like paint on a canvas, and had a thought that maybe when I click what I think may be my 2nd button, its really 'clicking' the layer beneath and pulling incorrect info.
Your description of the problem is a bit confusing, but this line in your init is very clearly wrong:
self.authorID = [self.authorID initWithInt:authorID];
In -init, your property self.authorID defaults to nil, so the expression [self.authorID initWithInt:authorID] is equivalent to [nil initWithInt:authorID], which evaluates back to nil. So you should actually be seeing nil in your action. You probably meant to say self.authorID = [NSNumber numberWithInt:authorID]
You're missing the alloc message, so this message:
self.authorID = [self.authorID initWithInt:authorID];
Is sent to a nil target, because self.authorID hasn't been allocated yet.
So first allocate it, then use the init method, or mix these two messages. A faster syntax allows to do it this way:
self.authorID= #(authorID);
EDIT
I don't see where you initialize the delegate, that method shouldn't even be called if you haven't initialized it. Show the code where you create the AuthorSelectionView objects and set the delegates.
instead of :
self.authorID = [self.authorID initWithInt:authorID];
put :
self.authorID = [NSNumber numberWithInt:authorID];
or
self.authorID = [[NSNumber alloc] initWithInt:authorID];
EDIT :
Don't you have errors or warnings in your code ? I can't see you returning self object in the init method ("return self;")

cast UITextView to UITextField

This may seem like a very simple question, however I haven't been able to discover a simple option yet...
I have a series of UITextFields followed by a UITextView. How can I incorporated the TextFields and the TextView in the same method below.
-(BOOL) textFieldDidBeginEditing:(UITextField *) textField{
textField = activeField;
if([self.textField1 isFirstResponder]){activeField = textField1;}
else if([self.textField2 isFirstResponder]){activeField = textField2;}
else if([self.textField3 isFirstResponder]){activeField = textField3;}
else if([self.textView1 isFirstResponder]){ activeField = textView1;}
[scrollView1 scrollRectToVisible:[activeField frame] animated:YES];
return NO;
}
The last line causes a warning of:
Incompatible pointer types assigning to 'UITextField *_strong' from 'UITextView *_strong'
This is due (I'm sure) to the obvious fact that the UITextField and the UITextView are different Objects... which is fine however is there a way to get around this as I wish to be able to advance through the textFields and TextViews with a next and previous button.
as per this method
-(void) nextTextField:(id)sender{
if([self.textField1 isFirstResponder]){activeField = textField2;}
else if([self.textField2 isFirstResponder]){activeField = textField3;}
else if([self.textField3 isFirstResponder]){activeField = textView1;}
else if([self.textView1 isFirstResponder]){ activeField = textField1;}
}
I was hoping for a casting sort of option however I am a little confused as to how to cast in objective C... This might sound dumb however
activeField = ((UITextField) textView1);
is how I'd have cast in Java but it seems I just can't seem to get he syntax right.
Should I cast to a UIView as they both inherit from that?
Thank you in advance
Okay, so you don't need to store activeField, for this method, so the only relevant code would be your nextTextField: method. Try changing it to:
-(void) nextTextField:(id)sender{
if([self.textField1 isFirstResponder]) {[textField2 becomeFirstResponder];}
else if([self.textField2 isFirstResponder]) {[textField3 becomeFirstResponder];}
else if([self.textField3 isFirstResponder]) {[textView1 becomeFirstResponder];}
else if([self.textView1 isFirstResponder]) {[textField1 becomeFirstResponder];}
}
You don't need to cast to a different type or anything.
I think you have a few issues with your thinking. You will need to go down two levels to get to a common super type. Both UITextField and UITextView are "visible" components so they inherit from UIView. They can both be cast to UIView but not to each other. But this won't help you with your question. I think you may be struggling with delegation also.
The method textFieldDidBeginEditing: is a delegate call and it only works with UITextField. This is why the class that contains your method above should implement UITextFieldDelegate. When you set focus to the UITextField, UITextField first checks to see that the delegate is not nil. If the delegate property holds a class then UITextField checks to see if it implements the textFieldDidBeginEditing: method explicitly. If the method is implemented in the delegate then the UITextField calls the method.
It is no different with the UITextView. However UITextView doesn't even know about the textFieldDidBeginEditing: method. It has its own delegate and its own method that performs the same general function as textFieldDidBeginEditing:. The UITextView version of this method is called textViewDidBeginEditing:. Like the UITextfield the UITextView checks that the delegate is not nil and that it implements textViewDidBeginEditing:. If these requirements are met then UITextView will textViewDidBeginEditing. However UITextView will never call textFieldDidBeginEditing:.
Finally objects cannot be cast into something they're not. They can only be cast as its own type or any of its ancestors.
You will need to set up both each components' method (textFieldDidBeginEditing and textViewDidBeginEditing) for this to work.
Hope this helps.

Problem with reinitializing of string in iPhone

I have an interface like this:
#interface MacCalculatorAppDelegate:NSObject
<UIApplicationDelegate> {
// ...
UIButton *operatorPressed;
NSString *waitingOperation;
}
And I am initializing waitingOperation variable in my implementation like this:
- (id)init {
if (self = [super init]) {
waitingOperation = #"not set";
}
return self;
}
And I want to reinitialize this variable in a function. This is calculator program and when user clicks on operators button the following function will be invoked:
- (IBAction)operatorPressed:(UIButton *)sender {
if([#"+" isEqual:operand]) {
waitingOperation = #"+";
}
}
But after the check in if statement, my program won't do anything and this happens when I am trying to reinitialize waitingOperation variable.
I am new to objective-c, please help me understand what's wrong here.
Thanks in advance.
There are several things to note here.
waitingOperation=#"not set";
As it stands, this will eventually crash your program. Objective C string literals are autoreleased instances of NSString, which means unless you assign it to a retained property or retain it manually, the memory will be deallocated, leaving a dangling pointer.
-(IBAction) operatorPressed:(UIButton *)sender {
Have you verified that this method is actually being called? You have to assign the IBAction in Interface Builder. Step through it in the debugger or use NSLog to verify that this method is being called.
if([#"+" isEqual:operand])
Where is operand coming from?
waitingOperation=#"+";
Same problem as above, this will get deallocated behind the scenes, leaving you with a dangling pointer.
Also, note that if you know that both variables are NSStrings, using isEqualToString: is faster than using isEqual:.
Finally, this stuff shouldn't be part of your app delegate. This is view controller logic.
If your operand is also a string, then check for isEqualToString instead of isEqual

delegates and multiple methods

I have a problem that I solved using delegates, but now I am thinking I may have made a mistake.
This is what I want to do.
I have a class that runs on a delay. When it is done it has a finished delegate that it calls.
Now I have the main class that creates two of these delay classes.
I don't want them to both be handled by the same isfinished method in the main class. I want to use two different ones.
However I believe with the protocol method of creating delegates that this will not work for me.
Is there a way around this?
delayclass setdelegates MainclassFunction1
delayclass setdelegates MainclassFunction2
If I understand you correctly, take a look at the NSTableViewDelegate protocol. There, each delegate method's first argument is the NSTableView instance sending the message.
You can solve your issue by changing your delegate methods to have your delegating object send itself as an argument. Then, in your delegate, you'd do something like this:
if (theDelegator == objectA)
{
// Do something
}
if (theDelegator == objectB)
{
// Do something else
}
This way, you've got one cleanly-implemented delegate method that can handle multiple objects delegating to it.
Using delegates doesn't seem like the correct approach to me; they're generally used for augmenting behavior. What sounds most appropriate here is the target/selector pattern, like NSTimer.
#interface MyObject : NSObject {
#private
id target;
SEL selector;
}
#property(assign) id target;
#property SEL selector; /* The selector must return void and accept one argument, which is the MyObject instance that invoked the method. */
#end
#implementation MyObject
- (void)notifyTarget {
[[self target] performSelector:[self selector] withObject:self];
}
#synthesize target;
#synthesize selector;
#end
This is generally the cleanest approach since the delegate callback doesn't need to disambiguate the sender. Using notifications seems like too much overhead for a problem in this domain.
As mentioned, commonly delegate methods would include the object initiating the callback, so you can differentiate that way. Alternately you could have the object post a notification instead, which will also make the originator available.
Why are you not just using NSTimer, adding different timers and having them call whatever selectors you like in the class you are using as a delegate now?
Something like:
NSTimer *timer1 = [NSTimer scheduledTimerWithTimeInterval:0.5f target:self selector:#selector(myMethod1:) userInfo:nil repeats:YES];
NSTimer *timer2 = [NSTimer scheduledTimerWithTimeInterval:0.5f target:self selector:#selector(myMethod2:) userInfo:nil repeats:YES];
Where your methods are:
- (void) myMethod1:(NSTimer*)theTimer
{
// do Something
}
- (void) myMethod2:(NSTimer*)theTimer
{
// do Something different
}
You want to save off and retain both timer1/timer2 references, so that you can stop the timers in dealloc ([timer1 invalidate]).
Short note: Generally, it's bad style to have "if" statements that switch on an object. We all do it occasionally for getting that second list w/o needing a new controller, but switching is what method calls do internally, so ideally you'd just let the ObjC runtime take care of doing the right thing. Several options:
-(void) tableViewSelectionDidChange: (NSTableView*)theView
{
SEL theAction = NSSelectorFromString( [NSString stringWithFormat: #"tableView%#SelectionDidChange:", [theView autosaveName]] );
[self performSelector: theAction withObject: theView];
}
-(void) tableViewUKSourceListSelectionDidChange: (NSTableView*)theView
{
// UKSourceList-table-specific stuff here.
}
-(void) tableViewUKUsersListSelectionDidChange: (NSTableView*)theView
{
// UKUsersList-table-specific stuff here.
}
This works best when you have a non-localized string label, like the autoSave name, but can also use the tag, although that makes the code less readable (which one is "table 1"?). Sometimes it's better to just write a subclass that has a special string for that purpose, or even has methods where you can specify selector names to forward the delegate methods to.
Caleb's suggestion is also good, it's also called "target/action" in case you want to google for it. I have several (Mac) classes that have a regular "action" for clicks, a "doubleAction" for double clicks etc.