Subclassing UIApplication to override sendEvent causes crash - iphone

I'm trying subclass UIApplication to catch all touch event, this is used to see if user is afk or not. Anyway it works great first time you launch the application. If you put it in background and open it again 2 times then it crashes. I have no idea what causes this. I'm getting EXEC_BAD_ACCESS on [super sendEvent:event];
My Subclass MyUI:
#implementation MyUI
- (void)sendEvent:(UIEvent *)event {
[super sendEvent:event]; // <-- EXEC_BAD_ACCESS
if (event.type == UIEventTypeTouches) {
UITouch *touch = [event allTouches].anyObject;
if (touch.phase == UITouchPhaseBegan) {
// Calling some methods
}
}
}
#end
Main.m
int main(int argc, char *argv[])
{
NSString* appClass = #"MyUI";
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, appClass, nil);
[pool release];
return retVal;
}

Is there a reason the super class method is being called first? If you are intercepting, you should be calling it last.
That might be your problem. I also override sendEvent to intercept all events to my application and have no issues.
If you call the super method first it will pass it to all UIResponders that may eventually eat your event resulting in the EXEC_BAD_ACCESS. Furthermore as DavidNeiss suggests eliminate the lines below the super call. If you still receive the bad access signal its likely another view or controller down the line causing it. You'll need to stack trace to find out where it is.

To get exact reason for EXEC_BAD_ACCESS use nszombieenabled in your application.
This link will guide you for using it.
http://iosdevelopertips.com/debugging/tracking-down-exc_bad_access-errors-with-nszombieenabled.html

Dont understand what "afk" means.
Another approach might be to create a custom gesture recognizer. They (gesture recognizers) are supposed to filter through touch events before they are forwarded to the view as part of the normal touches event processing.

Just for simplicity's sake, I would put the #"MyUI" directly into the UIApplicationMain call.
The string is most likely your problem. It's outside of the NSAutoreleasePool, and that is just not done.
In addition, the name #"MyUI" hints at a view controller, not an AppDelegate class. Look into that.

[super sendEvent:event]; // <-- EXEC_BAD_ACCESS
means error is in your class.
For testing block
/*
- (void)sendEvent:(UIEvent *)event {
[super sendEvent:event]; // <-- EXEC_BAD_ACCESS
if (event.type == UIEventTypeTouches) {
UITouch *touch = [event allTouches].anyObject;
if (touch.phase == UITouchPhaseBegan) {
// Calling some methods
}
}
}
*/
still you will get that error. Any time if you are getting error while override sendEvent
try to block all override method and check still you are getting that error or not specially for this line [super sendEvent:event];. I had also stuck with this error and figured out no mistake in this method. Mistake was found in applicationDidFinishLaunching. Always keep your code within try catch method
#try {
}
#catch (NSException *exception) {
NSLog(#"Exception => %#",exception);
}
This will help you figure out right problem.

Related

NIAttributedLabel - supporting links while keeping control tap functionality

I'm trying to use NIAttributedLabel to produce a label that has both text, links and supports this behavior:
Pressing a link will call - (void)attributedLabel:(NIAttributedLabel *)attributedLabel didSelectTextCheckingResult:(NSTextCheckingResult *)result atPoint:(CGPoint)point; - this works fine.
Pressing anywhere else will call another method - if implemented, the link functionality above is lost.
I don't have to use NIAttributedLabel so suggestions for a better control would also work.
Thank you for your help
I figured out this finally;
adding function to file NIAttributedLabel.m
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// never return self. always return the result of [super hitTest..].
// this takes userInteraction state, enabled, alpha values etc. into account
UIView *hitResult = [super hitTest:point withEvent:event];
// don't check for links if the event was handled by one of the subviews
if (hitResult != self) {
return hitResult;
}
if (self.explicitLinkLocations || self.detectedlinkLocations) {
BOOL didHitLink = ([self linkAtPoint:point] != nil);
if (!didHitLink) {
// not catch the touch if it didn't hit a link
return nil;
}
}
return hitResult;
}
remove all [super touch XXXX] functions in all touchXXX;
then, it works!

How to get performSelector to work with NSInvocation?

I need to pass the touches and event from touchesBegan to my own method called by a performSelector. I am using an NSInvocation to package up the arguments but I'm having problems with the target.
The reason that I do it this way is so I can handle other scroll events.
Here is my code:
- (void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event
{
UITouch *theTouch = [touches anyObject];
switch ([theTouch tapCount])
{
case 1:
NSInvocation *inv = [NSInvocation invocationWithMethodSignature: [self methodSignatureForSelector:#selector(handleTap: withEvent:)]];
[inv setArgument:&touches atIndex:2];
[inv setArgument:&event atIndex:3];
[inv performSelector:#selector(invokeWithTarget:) withObject:[self target] afterDelay:.5];
break;
}
}
Where handleTap is defined as:
-(IBAction)handleTap:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
}
My problem is that when I compile it I get a warning:
'CategoryButton' many not respond to '-target'
and when I run it, it crashes with a:
-[CategoryButton target]: unrecognized selector sent to instance 0x5b39280
I must admit I don't really understand what the target is trying to do here and how it is set.
Thanks for your help.
I think you need to take some time to read through your code carefully, line-by-line.
[inv performSelector:#selector(invokeWithTarget:) withObject:[self target] afterDelay:.5];
This isn't doing what you think it is. One half of one second after this method is executed, this will happen:
[inv invokeWithTarget:[self target]];
First, your class CategoryButton doesn't have a method called target. Second, why the delay? If you're using these touches for scrolling, a delay of 0.5 seconds is going to be extremely painful for users.
Why are you using the NSInvocation class at all? If you really need the delay, you can simply use the performSelector: method on your CategoryButton instance:
NSArray *params = [NSArray arrayWithObjects:touches, event, nil];
[self performSelector:#selector(handleTap:) withObject:params afterDelay:0.5];
Notice the performSelector: methods only support one argument, so you have to wrap them in an NSArray. (Alternatively, you can use an NSDictionary.)
You will have to update your handleTap: method to accept an NSArray/NSDictionary and snag the arguments as needed.
But again, if you don't need the delay, why not just call the method yourself:
- (void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event
{
UITouch *theTouch = [touches anyObject];
switch ([theTouch tapCount])
{
case 1:
[super touchesBegan:touches withEvent:event];
break;
}
}
Maybe I'm misunderstanding your intentions, but it seems you're making this way more complicated than it needs to be.
I must admit I don't really understand what the target is trying to do here and how it is set.
The target is the object which you want the invocation to be performed on. You're getting a crash because your chosen object - [self] - doesn't respond to the target message. I think you might just have got a bit confused over what you need to pass in.
With your current code you're asking your invocation to be performed on the target property of self. You probably don't want to do this - I would assume you want your invocation to be performed simply on self. In which case, use this:
[inv performSelector:#selector(invokeWithTarget:) withObject:self afterDelay:.5];

Deallocation while key value observers still registered (reverse geocoder)

When my view goes away I get the following message:
An instance 0x1c11e0 of class MKAnnotationView was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object. Set a breakpoint on NSKVODeallocateBreak to stop here in the debugger. Here's the current observation info:
(
Context: 0x0, Property: 0x1e98d0>
)
The code that defines and starts the reverse geocoding is:
geo=[[MKReverseGeocoder alloc] initWithCoordinate:droppedAt];
geo.delegate=self;
[geo start];
I have tried setting geo.delegate to nil right before the I dismiss the view. That would be too easy. I have also tried:
for (id <MKAnnotation> annotation in mvMap.annotations) {
[[mvMap viewForAnnotation:annotation] removeObserver:self forKeyPath:#"selected"];
}
Which throws an error that says:
* Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer for the key path "selected" from because it is not registered as an observer.
My view for annotation code is:
-(MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
MKAnnotationView *aView;
aView=(MKAnnotationView *) [mapView dequeueReusableAnnotationViewWithIdentifier:annotation.title];
if (aView==nil)
aView=[[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:annotation.title] autorelease];
else
aView.annotation=annotation;
[aView setImage:[UIImage imageNamed:selIcon]];
aView.canShowCallout=TRUE;
aView.draggable=YES;
return aView;
}
I'm sort of pushing buttons and flipping switches here while spinning in. Any idea of what I can do here?
You may have multiple issues, here. For every delegate you set up, you should clear it at dealloc. For every observer you set up, you should clear that, same with Notifications, etc.
So your dealloc should have (typed in web browser, you may have to adjust):
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
for (id <MKAnnotation> annotation in mvMap.annotations)
{
// remove all observers, delegates and other outward pointers.
[[mvMap viewForAnnotation:annotation] removeObserver: self forKeyPath: path];
}
gel.delegate = nil;
// etc., your other stuff
[super dealloc]; // Only in non-ARC legacy code. Modern stuff skips this.
}
Go through your setup (likely in viewDidLoad) and make sure that you un-do everything that you did in there. Specifically anything that triggers a callback.
I have got the same troubles with draggable pins. I have found that the following code solves them:
(void)mapView:(MKMapView *)theMapView annotationView:(MKAnnotationView *)annotationView didChangeDragState:(MKAnnotationViewDragState)newState fromOldState:(MKAnnotationViewDragState)oldState {
if (newState == MKAnnotationViewDragStateStarting) {
// some my code
} else if (newState == MKAnnotationViewDragStateCanceling) {
[annotationView removeObserver:theMapView forKeyPath:#"dragState"];
} else if (newState == MKAnnotationViewDragStateEnding) {
[annotationView removeObserver:theMapView forKeyPath:#"dragState"];
}
}

Delegate functions not being called

Long time lurker, first time poster.
I'm making a ServerConnection module to make it a whole lot modular and easier but am having trouble getting the delegate called. I've seen a few more questions like this but none of the answers fixed my problem.
ServerConnection is set up as a protocol. So a ServerConnection object is created in Login.m which makes the call to the server and then add delegate methods in Login to handle if there's an error or if it's done, these are called by ServerConnection like below.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if( [self.delegate respondsToSelector:#selector(connectionDidFinish:)]) {
NSLog(#"DOES RESPOND");
[self.delegate connectionDidFinish:self];
} else {
NSLog(#"DOES NOT RESPOND");
}
self.connection = nil;
self.receivedData = nil;
}
It always "does not respond". I've tried the CFRunLoop trick (below) but it still doesn't work.
- (IBAction)processLogin:(id)sender {
// Hide the keyboard
[sender resignFirstResponder];
// Start new thread
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Acutally call the server
[self authenticate];
// Prevent the thread from exploding before we've got the data
CFRunLoopRun();
// End thread
[pool release];
}
I copied the Apple URLCache demo pretty heavily and have compared them both many times but can't find any discrepancies.
Any help would be greatly appreciated.
Here are the questions to ask:
Does your delegate respond to connectionDidFinishLoading:?
Does the signature match, i.e. it takes another object?
Is the delegate set at all or is it nil? (Check this in that very method)
If any of those are "NO", you will see "doesn't respond"... and all equally likely to happen in your application, but all are easy to figure out.

action won't run on a CCSprite from within ccTouchBegan in cocos2d for iphone

I'm trying to basically scale up a button as soon as a touch is detected. Here's my Scene:
#implementation HomeScene
-(id) init
{
if((self = [super init])) {
...
// sp_btn_story is retained...
sp_btn_story = [[CCSprite spriteWithFile:#"main_menu_btn.png"] retain];
sp_btn_story.position = ccp(size.width - 146, 110);
[self addChild: sp_btn_story];
...
}
return self;
}
-(void) onEnter
{
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES];
}
-(void) onExit
{
[[CCTouchDispatcher sharedDispatcher] removeDelegate:self];
}
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
NSLog(#"tapped!");
sp_btn_story.scaleX = 2;
[sp_btn_story stopAllActions];
[sp_btn_story runAction: [CCScaleTo actionWithDuration:0.5f scale:1.2f]];
return YES;
}
...
#end
It scales the X just fine, as expected. (I threw that in there to test.) But the action is not running for some reason. :( Anyone have any ideas?
Edit: using cocos2d 0.99 btw.
The answer for this question (reposted from the asker's own comment to make it more visible):
It's quite important to call
[super onEnter];
if you overwrite this method in a subclass or strange things may happen. Cocos2D will not be happy.
This applies to other (if not all) methods as well, e. g. always call
[super onExit];
as well.
Not complete sure what could be going on. Looks like what you have should work fine but you may want to try applying a different action to sp_btn_story like fade in or fade out to see if at least any action will work. Failing that you could also try applying the action in a different part of your code. These are not solutions but they may provide evidence to indicate what exactly is going on.