How to stop warning for UIView may not respond to selector - objective-c

I have a class that has a UIView as a property. Sometimes I pass in a UILabel; sometimes a UITextField. No matter which I pass in, I want the class to set the text. Currently I am doing this, which works:
if ([self.viewToUpdate respondsToSelector:#selector(setText:)] && !self.newAnswer)
[self.viewToUpdate setText:[[self.choices objectAtIndex:indexPath.row] text]];
The problem is, this gives a warning, because even though I'm checking respondsToSelector, Xcode doesn't know that my UIView will respond to setText:. How can I remove this warning?
I know that I can specifically check to see if it's a TextField or a Label, and then cast to a TextField or a Label, respectively, but this would be a pain, and if I ever have more types of views, I'd have to add a few more lines of code for each one.
I thought about creating my own protocol, and then having my class have id as the type for viewToUpdate... but of course UITextField and UILabel wouldn't conform to that protocol...

try just casting it as an id:
if ([self.viewToUpdate respondsToSelector:#selector(setText:)] && !self.newAnswer)
[(id)self.viewToUpdate setText:[[self.choices objectAtIndex:indexPath.row] text]];

Related

Cocoa Outlets acting wierd, won't recognize selector

I'm getting some weird behavior, I Set a Label in Interface Builder, then I connect the label to a file as an Referencing Outlet.
#property (weak) IBOutlet NSTextField *TitleLabel;
When I access that label in the file (cell.TitleLabel.stringValue = title) and run the application, it doesn't recognize it.I get this:
-[NSApplication TitleLabel]: unrecognized selector sent to instance 0x608000101680
The weird thing is that it doesn't always do this, sometimes it works and displays correctly, other times it doesn't.
I've just started messing with IB so I'm probably missing something. Any help?
Is the property really on your NSApplication subclass? or is it on you application delegate class? It's not impossible for it to be on the application object, but it would be a pretty uncommon (and arguably ill-advised) pattern.
In short, I suspect you're probably connecting it to the wrong object.
EDIT: Ah. I see. You're trying to access things via the topLevelObjects array, but in practice, you can't count on the order of topLevelObjects. What you need to rely on the owner's outlets getting populated, but you're passing nil for the owner. topLevelObjects only exists to give the caller "ownership" (in the reference counting sense) of the top level objects in the xib for memory-mangement purposes, it's not really meant to be "used" directly like you're doing here. (In fairness, I can imagine situations where you might need to introspect that array, but this hardly rises to that level.)
The canonical way to do this would be to use an NSViewController subclass as the owner. In Xcode, if you add a subclass of NSViewController to your project, it will give you the option to create a xib file at the same time that will have everything hooked up. Then you just initialize the NSViewController subclass at runtime and the view outlet property of that class will be filled with the root view. You can obviously add more outlets and plug in whatever you like.
This post appears to cover the basics, if your looking for more details. Apple's docs on xib files and how they work are here.
The problem was that the View would sometimes get assigned to NSApplication. I'm not sure if the way that I am initiating the view is the common way of doing it but the problem was within this block of code:
NSArray * views;
[[NSBundle mainBundle] loadNibNamed:#"CollapseClickViewController" owner:nil topLevelObjects:&views];
CollapseClickCell * cell = [[CollapseClickCell alloc] initWithFrame:CGRectMake(0,0,320,50)];
cell = [views objectAtIndex:0];
the problem was that [views objectAtIndex:0] would sometimes return NSApplication. To fix it I just checked the class against itself and returned that object via:
-(CollapseClickCell*)assignCell:(CollapseClickCell*)cell withArray:(NSArray*)array{
for ( int i = 0; i< [array count]; i++) {
if ([[array objectAtIndex:i] class] == [CollapseClickCell class]) {
return [array objectAtIndex:i];
}
}
return nil;
}
I then assign that to the object:
cell = [cell assignCell:cell withArray:views];
It may not be the conventional way of doing it but it works. If there is a better technique or a more common approach please enlighten me! :)

A bit of annoying warnings that still let the app work but would like to remove

I tried out an app to test bluetooth communication. It is a simple app that just sends a message in text form from one iDevice to another. Originally, this app had about 6 warnings but I fixed all but two. They are the same but deal with different delegates. One is for the GKPeerPickerControllerDelegate and the other for the GKSessionDelegate. Say the Picker error is for the GKPeerPickerController named picker, when you type (more complete example to follow):
picker.delegate = self;
the compiler says:
Passing '*const___strong' to parameter of incompatible type 'id'.
For the GKSession named session, typing
session.delegate = self;
makes the compiler say:
Sending '*const___strong' to parameter of incompatible type 'id'.
These only pop in the button to send and peerPickerController. I know that these warnings do not impede on the app's ability to function but I would like to completely update this for Xcode 4.2. This app was originally written for Xcode back when iOS 3.0 was new. Yes, I am a bit picky when it comes to writing or practicing code, it must not contain any errors/warnings whenever possible.
These are the code blocks where the warning occur:
-(IBAction)btnConnect:(id)sender{
picker = [[GKPeerPickerController alloc] init];
picker.delegate = self; //Warning here
picker.connectionTypesMask = GKPeerPickerConnectionTypeNearby;
[connect setHidden:YES];
[disconnect setHidden:NO];
[picker show];
}
-(void)peerPickerController:(GKPeerPickerController *)PCpicker didConnectPeer:(NSString *)peerID toSession:(GKSession *)session{
self.currentSession = session;
session.delegate = self; //Warning here
[session setDataReceiveHandler:self withContext:nil];
PCpicker.delegate = nil;
[PCpicker dismiss];
}
Edit:
The header has this:
#interface BTViewController : UIViewController{
GKSession *currentSession;
IBOutlet UITextField *txtMessage;
IBOutlet UIButton *connect;
IBOutlet UIButton *disconnect;
GKPeerPickerController *picker;
}
I believe whatever class self is may not be adopting the GKPeerPickerControllerDelegate and GKSessionDelegate formal protocols. Can you post your interface header?
EDIT
Casting to id will clear the warnings, but you really didn't "fix" anything...looking at the class header, it is not adopting the protocols that the delegates are expecting.
Modify your interface to adopt those protocols:
#interface BTViewController : UIViewController <GKPeerPickerControllerDelegate, GKSessionDelegate> {
What about session.delegate = (id)self. Maybe you just need to cast self as ID instead of const____strong.
EDIT: At the bequest of the OP, an explanation is in order. Type id is necessary for the protocol, because the protocol itself is literally typecast to id itself (id<GKSessionDelegate> etc.). My theory (because I am not using ARC in any of my projects) Is that the compiler gets very exacting so it can guarantee that your class is safe for release. You probably initialized your class in a non-id way... Of course I have no idea how, if anyone knows; I'd be happy to let them edit this answer.
EDIT 2: as Teddy said, adopting the protocols in your header file also silences this warning. I apologize for thinking it was implied that you had adopted the protocols.

Objective-C Declaring object as a totally different class than what I declared

In a method I wrote, I'm declaring an instance of class 'A' and calling a method on it. At run time, when the method runs, my app crashes. It says an unrecognized selector was sent to an instance of class 'B' even though I declared an instance of 'A'. I read somewhere that I may not be managing my memory correctly so it's sending the method to another class, but I'm using ARC so that shouldn't even be a problem. Help would be much appreciated!
The error I'm getting:
2011-08-27 01:25:49.859 Intelligenda[49385:bc03] PVC: <HomeViewController: 0x59359e0>
2011-08-27 01:25:49.945 Intelligenda[49385:bc03] -[HomeViewController addNewClass:]: unrecognized selector sent to instance 0x59359e0
2011-08-27 01:25:49.947 Intelligenda[49385:bc03] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[HomeViewController addNewClass:]: unrecognized selector sent to instance 0x59359e0'
and the method that's being called:
-(IBAction)done:(id)sender{
[teacherName resignFirstResponder];
[className resignFirstResponder];
IntelligendaAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
UINavigationController *nav = delegate.navController;
[nav popViewControllerAnimated:YES];
ClassesViewController *classesParentViewController = (ClassesViewController *) nav.topViewController;
ClassIG *theNewClass = [[ClassIG alloc] init];
theNewClass.className = className.text;
theNewClass.teacherName = teacherName.text;
NSIndexPath *indexPath;
theNewClass.subject = [tableView cellForRowAtIndexPath:indexPath].textLabel.text;
// add reminder to array
NSLog(#"PVC: %#", [classesParentViewController description]);
[classesParentViewController addNewClass:theNewClass];
}
Declaring, say, a pointer NSArray* fred does nothing (other than a sprinkling of compile-time warning messages) to assure that fred is an NSArray. It's what you assign to fred that counts. If you assign an NSDateFormatter, then that's what fred is.
Your assumption that the application delegate's navigation controller has a ClassesViewController on top is clearly flawed. It has a HomeViewController on top. Your structure looks like it may be a little complicated, given that you have a button somewhere wired up to an action in some other part of your controller classes, so there could be a bunch of causative reasons.
First things to check: if you're instantiating buttons programmatically then did you accidentally add the button to the wrong controller? If you're designing everything using the graphical interface designer but keeping it in a single NIB, is it possible you copied and pasted a button and forgot to change what it's wired to?
If you can end up with multiple navigation controllers, are you sure the one held by the application delegate is currently on screen?
the short answer is that typecasting and type conversions are an exercise for the programmer in objc.
read my answer here:
Passing NSNumber* to NSString* expected-parameter does not cause compiler warning / error
...for some more information as to how declaring/casting a variable of an object differs from other languages, and how you can detect those cases.

Typecasting return value of methods when returned value is parent class of the typecast?

I have code similar to this.
MySubclassOfUIView *view = [aUIPickerView viewForRow:4 forComponent:0];
The viewForRow:forComponent method of UIPickerView returns a UIView. MySubclassOfUIView is exactly that: a subclass of UIView.
The UIPickerView delegate (not mentioned here) uses an array of MySubclassOfUIView objects to populate the rows of the UIPickerView components. Thus, I know the viewForRow:forComponent method is really going to be returning a pointer to an object of type MySubclassOfUIView.
Xcode gives me this warning.
Incompatible pointer types initializing 'MySubclassOfUIView*' with an expression of type 'UIView*'.
So I figure that I'll typecast it to fix the warning, which gives me this code.
MySubclassOfUIView *view = (MySubclassOfUIView*)[aUIPickerView viewForRow:4 forComponent:0];
And the warning goes away.
Please forgive my shaky C and Objective-C skills, but am I doing the right thing (as far as the context given so far)? Is there some other better way to handle this situation?
If you are absolutely sure that it will return a MySubclassOfUIView, then it is OK to do this. If there is any chance that it could return something else (such as you made a mistake and added the wrong thing to the array), then you should check the type and use a temporary variable.
UIView *temp = [aUIPickerView viewForRow:4 forComponent:0];
NSAssert([temp isMemberOfClass:[MySubclassOfUIView class]],[NSString stringWIthFormat:#"aUIPickerView returned the wrong class (%#)",[temp class]]);
MySubclassOfUIView *theView = (MySubclassOfUIView*)temp;
What you can do is:
MySubclass* subFoo = [[MySubclass alloc] init];
MySuperclass* superFoo = subFoo;
What you shouldn't do is:
MySuperclass* superFoo = [[MySuperclass alloc] init];
MySubclass* subFoo = superFoo;
This is, because your Subclass will have all properties, selectors, etc from the Superclass. But the Superclass won't have all (..) of the Subclass.
For the rest, see ughoavgfhw's answer.

Subclassing and Casting in Objective C

I came across a strange problem today. I created a subclass of UIView and added only 1 method to the template code provided by xcode.
#interface FloatView : UIView {
}
- (void)floatTest:(CGFloat)x;
#end
- (void)floatTest:(CGFloat)x {
NSLog(#"float was %f", x);
}
Then in my appDelegate I had code like this:
UIView *floatView = [[FloatView alloc] init];
[floatView floatTest:10.0f];
Pretty simple, right? What should this print out? I thought it would something like "10.0000", but no, it prints out "0.000000".
I wrestled with this for hours, trying to figure out what I was doing wrong, and then I changed the code in my appDelegate to
FloatView *floatView = [[FloatView alloc] init];
[floatView floatTest:10.0f];
Only then, did it print out the expected "10.0000". Why is this so? I've declared FloatView as a subclass of UIView, shouldn't I be able to assign a FloatView object to a UIView pointer without problems?
Even though floatView was declared a pointer to a UIView, it's really a floatView and it should be able to handle the floatTest message? Am I totally off base here?
Actually, polymorphism is working as expected. If it didn't work, nothing would have been printed (in your example, 0.0000 is being printed). The thing is, while your instance actually responds to testFloat:10.0f message, since the compiler can't statically see the method declaration (as UIView class doesn't declare such a method), it assumes that your method takes ... as argument and returns id.
When CGFloat is passed to a method that expects variable number of arguments (...), it's promoted to double. Thus, the receiving method is passed a double argument and thinks it's a float and it doesn't get printed correctly.
You can verify this behavior by changing NSLog line to:
NSLog(#"%f", *(double*)&x);
When the compiler sends the message to FloatView* rather than a UIView*, it can find the exact signature of the method. It can see it really expects CGFloat and doesn't promote the argument to double. As a result, it works correctly.
Additionally, if UIView* contained the method declaration that took a CGFloat, the compiler would call the method appropriately. To summarize, this is not a polymorphism issue; it's a missing method signature issue.