I am successfully drawing annotations on a map using an array of annotations. I can even click on the annotation and change it’s colour or image. My problem arises when the use selects the second annotation and I want to dynamically change the colour or image of the first one back to a non-selected colour/image. I can get the array of all the annotations and work through the array but once I try to set the colour or image ot the array I get a similar error.
for (MKAnnotationView *ann in map.selectedAnnotations){
if ([ann isMemberOfClass:[Place class]]) {
place = (Place *)ann;
if (currentPlaceID != place.placeID) {
UIImage *i = [UIImage imageNamed:#"pin.png"];
ann.image = i;
}
}
the above code works ok until I get to ann.image = i; then it errors. The errors I get are:-
-[Place setImage:]: unrecognized selector sent to instance 0x4514370 Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '** -[Place setImage:]:
unrecognized selector sent to instance 0x4514370'
Please advise as I have been going around in circles on this one for 2 days now!!!!
Any ideas on how best to do this?
thanks in advance
Do you have a property on the class Place called image?
Something like... #property (nonatomic, retain) UIImage* image; and is it properly synthesized? #synthesize image;?
The error is pretty straight forward, some object is receiving a message that it doesn't respond to, namely 'setImage' which is invoked by the .image.
Here is your code:
1. for (MKAnnotationView *ann in map.selectedAnnotations) {
2. if ([ann isMemberOfClass:[Place class]]) {
3. place = (Place *)ann;
4. if (currentPlaceID != place.placeID) {
5. UIImage *i = [UIImage imageNamed:#"pin.png"];
6. ann.image = i;
7. }
8. }
9. }
What I can see:
ann is an MKAnnotationView (from map.selectedAnnotations)
you are typecasting your annotation to a place on line 3 (is this right? Does Place subclass MKAnnotationView?)
you are properly setting the image to the annotation
What this means:
If Place is indeed a subclass of MKAnnotationView, you hid the setImage (somehow) method
If Place is NOT a subclass of MKAnnotationView, you've added an invalid annotation to the annotations (sure) that you're trying to treat as an annotation.
I finally figured out how to do this. As usual it's not that hard once you know how. Just thought I would pass this on.
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
NSLog(#"here I am in set selected");
if (YES == selected)
{
NSLog(#"I am selected");
}
else
{
self.backgroundColor = [UIColor clearColor];
NSLog(#"not selected");
}
}
Related
I created UILabel category which would do extra work when font property is changed.
I have chosen category over sublassing so I do not have to change class for all labels in my all XIB files. I just add this category declaration to the prefix header and category is visible in the whole project scope.
Implementation file:
//
// UILabel+XIBCustomFonts.m
#import "UILabel+XIBCustomFonts.h"
#implementation UILabel (XIBCustomFonts)
BOOL comes_from_nib = NO;
-(id)initWithCoder:(NSCoder *)aDecoder_{
self = [super initWithCoder:aDecoder_];
if (self) {
comes_from_nib = YES;
}
return self;
}
-(void)setFont:(UIFont *)font_{
[super setFont:font_];
NSLog(#"Setting from for label from XIB for name:%# size: %.1f - do font theme replacement if needed", self.font.fontName, self.font.pointSize);
}
#end
The surprising thing is the crash with following log:
-[UIButtonLabel setFont:]: unrecognized selector sent to instance 0x9ad1b70
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIButtonLabel setFont:]: unrecognized selector sent to instance 0x9ad1b70'
Where did UIButtonLabel came form?
It happens even if I do extra Class checking in setFont: setter:
if ([self class] != [UILabel class] || !comes_from_nib) {
[super setFont:font_];
return;
}
Is there any way to override setFont: setter in UILabel without subclassing?
You cannot call super methods in categories! Never!
This is not possible when you use a category to augment a class' functionality, you are not extending the class, you are actually wholly overriding the existing method, you lose the original method completely. That's why your error appears.
So, I'm puzzled by a really weird crash occurring in my app.
Background: I have a class CustomButton overriding UIButton. Briefly, the interface looks like:
#interface CustomButton : UIButton
#property (nonatomic, getter = isLoading) BOOL loading;
#end
Implementation:
#implementation CustomButton
#synthesize loading = loading_;
- (id)init {
if ((self = [UIButton buttonWithType:UIButtonTypeCustom])) {
[self setTitle:NSLocalizedString(#"My Button", nil) forState:UIControlStateNormal];
[[self titleLabel] setShadowColor:[UIColor blackColor]];
[[self titleLabel] setShadowOffset:CGSizeMake(0, -1)];
[self setContentEdgeInsets:UIEdgeInsetsMake(0, 77, 0, 30)];
UIImage *backgroundImage = [[UIImage imageNamed:#"back.png"] stretchableImageWithLeftCapWidth:77 topCapHeight:0];
UIImage *highlightedBackgroundImage = [[UIImage imageNamed:#"back_highlighted.png"] stretchableImageWithLeftCapWidth:77 topCapHeight:0];
[self setBackgroundImage:backgroundImage forState:UIControlStateNormal];
[self setBackgroundImage:highlightedBackgroundImage forState:UIControlStateHighlighted];
[self sizeToFit];
}
return self;
}
- (void)setLoading:(BOOL)loading {
if (loading_ != loading) {
// Do fancy things
}
}
#end
Problem: I'm trying to use the button in a class, so I have property and so on:
...
#property(nonatomic, strong) CustomButton *customButton;
...
#syntesize customButton = customButton_;
...
- (void)aMethod {
self.customButton.loading = YES;
}
...
The button, of course, is initialized.
Then, upon executing the statement self.bookButton.loading = YES, I get this error
2012-08-28 12:54:25.091 MyApp[9465:15803] -[UIAccessibilityBundle
setLoading:]: unrecognized selector sent to instance 0x8650cc0
2012-08-28 12:54:25.091 MyApp[9465:15803] *** Terminating app due
to uncaught exception 'NSInvalidArgumentException', reason:
'-[UIAccessibilityBundle setLoading:]: unrecognized selector sent to
instance 0x8650cc0'
*** First throw call stack: (0x1ead022 0x203ecd6 0x1eaecbd 0x1e13ed0 0x1e13cb2 0x26bf5 0x257ce 0xfa938f 0xfa96a4 0xfa99a7 0xfb8aea
0x11600be 0x11603c7 0x1160436 0xf10e49 0xf10f34 0xc5bcaac 0xe04b54
0x272e509 0x1de4803 0x1de3d84 0x1de3c9b 0x27887d8 0x278888a 0xee0626
0x14cce 0x27b5 0x1) terminate called throwing an exception
I honestly have no idea about what to look for. It's either something really really stupid, or something really subdle.
I've been searching on google/stackoverflow already of course, but I didn't manage to get an answer.
Usually that error occurs, as far as I know, when the object doesn't "understand" the sent message, but this shouldn't be the case. I don't even get a warning from the compiler.
If you have even just an idea of what I could look for or you want more details, let me know, thanks.
EDIT: forgot to specify that I am using ARC.
UPDATE: In the answers below Phillip Mills said that it might have been a premature memory release issue. It's a good point, but Instrument didn't find any zombie. Moreover, I've put a log right before the crashy statement, where I try print information about the CustomButton object and few things related to a UIButton, like the title. Everything looks ok. The title is the one I've set, the frame is there and so on... so I believe the object (an actual UIButton) exists and it's also the expected one. The only thing is the "loading" property that messes everything up.
SOLUTION: the init method was the source of the problem. Instead of initializing an instance of the subclass of the button, it was initializing an instance of UIButton. So, conequentely, no "loading" properties could be found. Substituting init with initWithFrame and using the standard procedure to override the initializer, everything works great.
Still a mistery, though, the reason why I got UIAccessibilityBundle as the origin of the error and not a UIButton.
Are you casting anything to (CustomButton *) in your app? Double-check the casts - you might be incorrectly casting something that's not a CustomButton and storing the result in the customButton property.
The button is almost certainly being released prematurely and having its address re-used for a UIAccessibilityBundle object. Try turning on zombies (or run Instruments to look for zombies) and you should see better information if the memory management problem isn't obvious.
Try
#interface CustomButton : UIButton
#property (nonatomic, assign) BOOL loading;
#end
and
#implementation CustomButton
#synthesize loading = _loading;
- (id)init {
....
}
- (void)setLoading:(BOOL)loading {
if (self.loading != loading) {
// Do fancy things
}
}
#end
For all other instances of loading, use self.loading.
Without seeing the other parts of your code, this is my best guess.
I'm using TTLauncherView as a sort of home screen for my app and I only have one page's worth of icons. How can I make it so the TTLauncherView won't let you drag icons to "the next page"? I want to set a maximum number of pages (in this case one.)
(EDIT: long story short, I subclassed beginEditing, see the answer below.)
I see where why it adds an extra page when beginEditing is called, but I don't want to edit the framework code. (That makes it hard to update to newer versions.) I'd also prefer not to subclass and override that one method, if I have to rely on how it's implemented. (I'm not against subclassing or adding a category if it's clean.)
I tried setting scrollView.scrollEnabled to NO in the callback method launcherViewDidBeginEditing in my TTLauncherViewDelegate, but that doesn't work while it's in editing mode and I don't know why.
I tried adding a blocker UIView to the scrollview to intercept the touch events by setting userInteractionEnabled=NO, which works OK. I still have to disable the dragging of TTLauncherItems to the next page somehow.
I also tried setting the contentSize of the scrollview to it's bounds in launcherViewDidBeginEditing, but that didn't seem to work either.
Is there a better way?
Tried to block gestures:
- (void)setLauncherViewScrollEnabled:(BOOL)scrollEnabled {
if (scrollEnabled) {
[self.scrollViewTouchInterceptor removeFromSuperview];
self.scrollViewTouchInterceptor = nil;
} else {
// iter through the kids to get the scrollview, put a gesturerecognizer view in front of it
UIScrollView *scrollView = [launcherView scrollViewSubview];
self.scrollViewTouchInterceptor = [UIView viewWithFrame:scrollView.bounds]; // property retains it
UIView *blocker = self.scrollViewTouchInterceptor;
[scrollView addSubview:scrollViewTouchInterceptor];
[scrollView sendSubviewToBack:scrollViewTouchInterceptor];
scrollViewTouchInterceptor.userInteractionEnabled = NO;
}
}
For reference: TTLauncherView.m:
- (void)beginEditing {
_editing = YES;
_scrollView.delaysContentTouches = YES;
UIView* prompt = [self viewWithTag:kPromptTag];
[prompt removeFromSuperview];
for (NSArray* buttonPage in _buttons) {
for (TTLauncherButton* button in buttonPage) {
button.editing = YES;
[button.closeButton addTarget:self action:#selector(closeButtonTouchedUpInside:)
forControlEvents:UIControlEventTouchUpInside];
}
}
// Add a page at the end
[_pages addObject:[NSMutableArray array]];
[_buttons addObject:[NSMutableArray array]];
[self updateContentSize:_pages.count];
[self wobble];
if ([_delegate respondsToSelector:#selector(launcherViewDidBeginEditing:)]) {
[_delegate launcherViewDidBeginEditing:self];
}
}
I think overriding beginEditing in TTLauncherView is your best bet. Since you'd only really be touching one method (and only a few lines in that method), upgrading it when the time comes shouldn't be too bad. Since that method explicitly adds the extra page, I'm not sure how you'd get around it w/o editing that specific piece of code
As Andrew Flynn suggested in his answer, I was able to make it work by subclassing and overriding the beginEditing method to remove the extra page TTLauncherView adds when it goes into editing mode.
One problem I'm having is I can't figure out how to remove the warning I get for calling the (private) method updateContentSize on my subclass. I tried casting it to id, but that didn't remove the warning. Is it possible?
edit: I was able to remove the warning by using performSelector to send the message to the private class. (I had previously create a category method performSelector:withInt that wraps NSInvocation so that I can pass primitives via performSelector methods, which makes this very convenient.)
// MyLauncherView.h
#interface MyLauncherView : TTLauncherView {
NSInteger _maxPages;
}
#property (nonatomic) NSInteger maxPages;
#end
// MyLauncherView.m
#implementation MyLauncherView
#synthesize maxPages = _maxPages;
- (void)beginEditing {
[super beginEditing];
// ignore unset or negative number of pages
if (self.maxPages <= 0) {
return;
}
// if we've got the max number of pages, remove the extra "new page" that is added in [super beginEditing]
if ([_pages count] >= self.maxPages ) {
[_pages removeLastObject];
[self updateContentSize:_pages.count]; // this has a compiler warning
// I added this method to NSObject via a category so I can pass primitives with performSelector
// [self performSelector:#selector(updateContentSize:) withInt:_pages.count waitUntilDone:NO];
}
}
I've subclassed MKAnnotation so that i can assign objects to each annotation, this is so that when the rightCalloutAccessoryView is clicked i can push a navigation controller with the object passed to it and display the objects data in another view.
This all works great apart from one thing, i've extended upon initWithCoordinate like so:
-(id)initWithCoordinate:(CLLocationCoordinate2D)coord andObject:(NSManagedObject *)object {
[self setPlace:object];
coordinate = coord;
title = [place valueForKey:#"name"];
subtitle = [place valueForKey:#"address"];
return self;
}
Although everything is working great i'm recieving the warning:
NO '-initWithCoordinate:andObject:' method found
Which means i'm doing something wrong somewhere, what is the correct way to go about adding upon initWithCoorinate?
Put the prototype -(id)initWithCoordinate:(CLLocationCoordinate2D)coord andObject:(NSManagedObject *)object in .h file.
I am very new to XCode and iPhone development so please bear with me if this question is too simple. But I have a map and I have successfully added images (not pins) to it for my annotations. And I can change the image when the user selects one of the annotations.
I created a class that inherits from MKAnnotationView with the following methods:-
- (id)initWithAnnotation:
- (void)setAnnotation:
- (void)drawRect:
and I am using
- (void)touchesBegan
to know when an annotation has been selected. And in touchesBegan I am doing :-
UIImage *i = [UIImage imageNamed:#"A.png"];
self.image = i;
to change the image. But what I am really stumped on is how do I change the image back to it's original image when the users selects the next annotation. I have tried:-
NSArray *selectedAnnotations = map.selectedAnnotations;
for(id annotationView in selectedAnnotations) {
[map deselectAnnotation:[annotationView annotation] animated:NO];
}
but it errors
and I tried
for (MKAnnotationView *ann in map.selectedAnnotations){
if ([ann isMemberOfClass:[Place class]])
{
place = (Place *)ann;
NSLog(#"second = %#"#" %f"#" %f", place.title, place.longitude, place.latitude);
if (currentPlaceID == place.placeID) {
//UIImage *i = [UIImage imageNamed:#"A.png"];
//ann.image = i;
}
else {
UIImage *i = [UIImage imageNamed:#"pin.png"];
ann.image = i;
}
}
}
the above code works ok until I get to ann.image = i; then it errors. The errors I get are:-
*** -[Place setImage:]: unrecognized selector sent to instance 0x4514370
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[Place setImage:]: unrecognized selector sent to instance 0x4514370'
Yes I can see that my place object does not have an image so that's why it is going wrong. But if I create an image property on my place object - how will that change the annotations image which what I am trying to do.
Please advise as I have been going around in circles on this one for 2 days now!!!!
Thanks in advance
Cheryl
Cheryl,
I don't completely follow what you're trying to do, but here are some thoughts:
Here's what I would do to restore the original image:
In your subclass of MKAnnotationView, add two UIImage Properties,
firstImage and secondImage, set up to retain.
When you init the annotation view, set up both images. (At the point where you assign the image to your annotation view, also save it to your new firstImage property)
Then, you can say
self.image = firstImage;
or
self.image = secondImage.
That will swap the appropriate image into place, while keeping the other image around to restore.
Your code:
NSArray *selectedAnnotations = map.selectedAnnotations; for(id annotationView in selectedAnnotations) { [map
deselectAnnotation:[annotationView annotation] animated:NO]; }
is not right. It asks the map for an array of annotations, and then treats them as annotation VIEWs.
An annotation is a data model object. It contains the data describing an annotation.
An annotation VIEW object is a temporary display object used to display an annotation on the map if it is currently visible. There are not always annotation views for every annotation on the map.