UIPanGestureRecognizer crashes unexpectedly - objective-c

I'm not exactly sure what's going on and hope that I can provide enough relevant code to find an answer. I've set up my gesture recognizer in my appDelegate.m:
CCScene *scene = [HomeLayer scene];
HomeLayer *layer = (HomeLayer *) [scene.children objectAtIndex:0];
UIPanGestureRecognizer *gestureRecognizer = [[[UIPanGestureRecognizer alloc] initWithTarget:layer action:#selector(handlePanFrom:)] autorelease];
[director_.view addGestureRecognizer:gestureRecognizer];
m._gestureRecognizer = gestureRecognizer;
I've inserted some debugging messages to try to pinpoint at what point the app crashes:
- (void)handlePanFrom:(UIPanGestureRecognizer *)recognizer {
NSLog(#"Handle Pan From");
as well as some printouts for ccTouchBegan/Moved/Ended.
Every time the app crashes, it's while things are "moving", (ended never gets called), and handlePanFrom never gets called either.
Background info: My app has buttons that I use to switch between scenes, for example:
- (void) doSomethingThree: (CCMenuItem *) menuItem
{
NSLog(#"The third menu was called");
[[CCDirector sharedDirector] replaceScene:[CCTransitionFade transitionWithDuration:1.0 scene:[HomeLayer scene] ]];
}
If I start up my app and go directly to the HomeLayer scene, and try to drag, the app crashes instantly 100% of the time (ccMoved gets called 1-2 times before crash). Clicking does not cause the app to crash, only anything that would invoke handlePanFrom.
The strange thing is that if I drag around on any other scene, the app does not crash, and handlePanFrom successfully gets called. Then, when I go back to the HomeLayer scene and drag around, it won't crash for some time, and it seems directly related to how long I spend dragging around on a different scene.
Has anyone seen these symptoms before? I'm not sure if the information I provided is relevant or correct, I'm still trying to learn my way around iphone dev. I'll also be happy for any debugging tips (those assembly looking hex lines aren't particularly enlightening to me...)

I figured out the problem with the help of NSZombies, finding out that the program was crashing while trying to reference the deallocated method handlePanFrom.
The ultimate root of the problem was that HomeLayer was being instantiated twice, the first time in appDelegate.m, and the 2nd time when i was doing the replaceScene.
This resulted in the first layer eventually losing all of its references and being deallocated while the gestureRecognizer was still trying to reference [layer handlePanFrom], causing the crash.
The problem was fixed by moving the gestureRecognizer from the appDelegate.m to HomeLayer.m, and for anyone who needs gestures across multiple layers, here's a piece of code that will remove any existing references of the gestureRecognizer to the view, and then add a new one that targets a method in the layer:
+(CCScene *) scene
{
HomeLayer *layer = [HomeLayer node];
[scene addChild: layer];
for (UIGestureRecognizer *gr in [[CCDirector sharedDirector].view gestureRecognizers]) {
// note that sharedDirector is a singleton and therefore is the same CCDirector
// as the one used in appDelegate.m
[[CCDirector sharedDirector].view removeGestureRecognizer:gr];
}
UIPanGestureRecognizer *gestureRecognizer = [[[UIPanGestureRecognizer alloc] initWithTarget:layer action:#selector(handlePanFrom:)] autorelease];
[[CCDirector sharedDirector].view addGestureRecognizer:gestureRecognizer];
return scene;
}
Hopefully that may help someone in the future who is trying to work with multiple scenes/layers in a view =)

Related

iOS7 Sprite Kit how to reset/start new game with Sprite Kit?

Up until now I've been working on a Sprite Kit game, with a new game starting every time I hit the "Play" button in xCode. Now I'm trying to implement a "New Game" button within the game which will reset the game and start a new one.
What are the steps I need to take in order to ensure that my old game scenes are gone, and don't have any dangling references?
Do I need to do any kind of manual cleanup within a scene? (as in the old viewDidUnload) or will ARC take care of everything?
Do I need to explicitly remove any view controller views that I might've added from within a scene to it's superview?
Are there any built-in methods that can help me reset my game?
Here's what I was using until now.
-(void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
//set the view only once, because this method will be called again on rotation
if(!self.kkView.scene)
{
landscapeSize = self.kkView.bounds.size;
self.kkView.showsFPS = NO;
self.kkView.showsNodeCount = NO;
self.kkView.showsDrawCount = NO;
MyScene * gameScene = [MyScene sceneWithSize:landscapeSize];
gameScene.scaleMode = SKSceneScaleModeAspectFill;
IntroScene * scene = [IntroScene sceneWithSize:landscapeSize];
scene.gameScene = gameScene;
scene.scaleMode = SKSceneScaleModeAspectFill;
[self.kkView presentScene:scene];
}
}
ARC should take care of cleaning up object creation (like +sceneWithSize:). I also believe that if you're removing the entire SKScene that cleanup is done for all attached nodes and actions for you (possibly in the -willMoveFromView: method).
However, if you are looking to keep the scene and reset the nodes and actions inside the scene subclass itself, then you could do something like the following before the setup portion of your scene (either in the -didMoveToView: method or your own setup method):
[self removeAllChildren];
[self removeAllActions];
...
[self addChild:node];
etc

Cocos2d and concurrency: how to properly stop animations between scenes

I got a GameScene (here called ShooterScene) and I do exit to a MainMenu. I do use sprite sheets which I load in a texture cache. There are various animations going on (on background elements, enemies, player etc...). I had for months an issue where there was an assertion failure on the "setTexture" method for the Enemies. I solved this by pausing the shared CCDirector before replacing the scene. This has the effect of stopping the animations. However I solved this only partially. The game doesn't crash anymore on the Enemy setTexture but now crashes on the AnimatedBackgroundElement setTexture method.
The background elements are added to a sprite batch node that is child of the Background class that is added as child to the GameScene. I do wonder if, for some hierarchy reasons, there is some delay in the propagation of the pause call or if there are some concurrency problems between the "pause" thread and the CCAnimate->setTexture thread of the background element.
Any suggestions/ideas?
PS: I checked the "[[CCDirector sharedDirector] stopAnimation]" and it says:
CCLOG(#"cocos2d: Director#stopAnimation. Override me");
Am I meant to "override" this method to solve issues described above? I guess that this would be a good place to really stop all the animation of all the children with a recursive call but again, I am not sure on the latency due to the potential concurrency with the subsequent "replaceScene" method. I could solve this by using a sequence of callbacks (CCSequence with action1 = call stop animation and action2= replace scene) but again I am not 100% sure on the fact that the call of action2 will happen only after the end of the call to stop animation (action1). I could pheraphs write a recursive for (all children) and place that before the replaceScene.. or callback the replaceScene with a delay (which would not be an elegant solution as it would be semi-arbitrary)..
-(void) restartLevel
{
[self resetSharedData];
[[CCDirector sharedDirector] pause];
LevelName currentLevel = [GameController sharedGameController].currentlyPlayedLevelName;
[[CCDirector sharedDirector] replaceScene:[ShooterScene sceneWithLevelName:currentLevel]];
}
-(void) exitToMainMenu
{
[self resetSharedData];
[[CCDirector sharedDirector] pause];
[[CCDirector sharedDirector] replaceScene:[MainMenu scene]];
}
PPS: I see that there are various related posts on trying to stop a scene. I guess other people might have similar issues so there is probably a state-of-the art solution/design pattern for this.
EDIT: I have tried implementing this but it still fails.
-(void) restartLevel
{
[self resetSharedData];
[[CCDirector sharedDirector] pause];
LevelName currentLevel = [GameController sharedGameController].currentlyPlayedLevelName;
[self stopAllActions]; //Does this actually stopAllActions and Animations for ALL child nodes?
[self unscheduleAllSelectors];
//Do I need something else here?
[[CCDirector sharedDirector] replaceScene:[ShooterScene sceneWithLevelName:currentLevel]];
}
[self stopAllActions]; not really stop all actions added to layer's child node. You need to manually call stopAllActions on particular node.
-(void)onExit
{
[self.gameHeroSprite stopAllActions]; //call this for all animated sprite
[self stopAllActions];
[super onExit];
}
Stupid question, why is [self stopAllAnimations]; not working? I call that and [self unscheduleAllSelectors]; before a scene change, it whacks every animated object and timer in the entire layer, which should make it safe to switch scenes without assertion failures.
With regards to the pause thing, I KNOW!! seriously, the whole pause thing is really fidgity, it seems to pause stuff but it doesn't always pause stuff. As far as I can tell, when you pause: timers stop (and the dt doesn't increase), the accelerometer and touches still work, animated objects and moving objects stop moving, and menu buttons still work and ALL the code inside the function called by a menu button still runs. Additionally it finishes all the code inside the function that calls the pause, so if you do a pause and then cancel timers and change scenes all in one function, you'll still get an allocation error.
Here's what I do, more specifically:
- Stop all animations
- Stop all timers
- set a variable "gameRunning" to false, and on the first line of gameLogic if(!gameRunning) return;
- Set a timer for 0.1 seconds to only fire once to call a makeTransition function
- Make the actual scene change inside the timer.

Cocos2d not dealloc-ing the scene (again)

this is my first post so don't go too rough on me.
I have a problem with cocos2d. Im making a game with a HUD layer and a game layer. When I call replace main menu scene with [ClassicGameLayer scene] my HUD and game layer get init-ed this way:
+ (CCScene*)scene
{
CCScene *scene = [CCScene node];
HudLayer *hud = [[[HudLayer alloc] initWithMode:1] autorelease];
ClassicGameLayer *layer = [[[ClassicGameLayer alloc] initWithHUD:hud] autorelease];
[scene addChild:hud z:hudZ];
[scene addChild:layer z:layerZ];
return scene;
}
and when the user fails the game HUD layer calls
[[CCDirector sharedDirector] replaceScene:[GameOverLayer sceneWithMode:integer andScore:points]]];
dealloc of the HUD layer gets called but dealloc of the ClassicGameLayer is never called. I googled almost everything I could think of but still no luck.
Does anybody know whats causing me this problem? And if so how can I fix it? Every other scene is being released properly i think :)
If your autorelease pool never gets to the place where it deallocates stuff, it will never release any memory. I had this issue once with a Mac app I was writing. It won't show up as a memory leak in Instruments, either.
I recommend not using autorelease if you are having this problem.

UIButton touches up IBAction causing EXC_BAD_ACCESS with ARC

There have been a few questions on StackOverflow where users have experienced the same problem as I am having. However, none of their solutions fit my case. (See here, here, here and here for some of the SO questions I've read but have not found helpful.)
In my case, I have a NIB that has a couple UIButtons, with an associated Controller View. The view is relatively old to my project and I've been able to use these buttons without any trouble until today. After making a few code changes that were not related to the button behavior, I've run into an error that crashes the app, breaks the code at the main() function and gives me an EXC_BAD_ACCESS error message whenever I touch any of the buttons on my View.
How or why might this happen? I've actually commented out almost all functional code, especially that which I modified earlier today and I still can't stop the error from occurring.
My project is using Automatic Reference Counting and I haven't seen this error before. Furthermore, I did not modify the NIB, nor the IBAction associated with the buttons so I don't see what would cause this. The only way to stop the error is to unlink my UIButtons in my NIB to the IBActionmethods defined in my Controller View header file.
The only "unique" aspect of my use-case is that I load either one or two instances of this view, within another sub view controller. The number of instances of the broken view that are loaded is contingent on the number of objects in an array. Below is the code that I use to instantiate and load these views as subviews of another view.
//Called else where, this starts the process by creating a view that
//will load the problematic view as a sub-view either once or twice.
- (id)initWithPrimarySystemView:(SystemViewController *)svc
{
//First we create our parent, container view.
self = [super initWithNibName:#"ContainerForViewInstaniatedFromArrayObjs" bundle:nil];
if (self)
{
//Assign parent DataModel to local instance
[self setDataModel:((DataModelClass*)svc.DataModel)];
for (AnotherModel* d in DataModel.ArrayOfAnotherModels)
{
//Instantiate the SubViewController.
SubViewController* subsvc = [[SubViewController alloc]
initWithNibName:#"Subview"
bundle:nil
subviewPosition:d.Position ];
//Add the SubViewControllers view to this view.
[subsvc.view setFrame:CGRectMake((d.Position-1)*315, 0, 315, 400)];
[self.view addSubview:subsvc.view];
}
[self setDefaultFrame: CGRectMake(0, 0, 640, 400)];
}
return self;
}
This works perfectly and, previously, hadn't even caused any trouble with the buttons that were on the associated view, however, now all UIButtons crash the app when tapped.
The initialization function for SubViewController, as well as the viewDidLoad method contain nothing other than the standard, auto-generated code that is added when you create a new ViewController.
What can I do to either fix or diagnose this problem?
See my comments in your code:
{
SubViewController* subsvc = [[SubViewController alloc] initWithNibName:#"Subview" bundle:nil subviewPosition:d.Position ];
//!i: By default, subsvc is a __strong pointer, so your subview has a +1 retain count
// subsvc owns subsvc.view, so subsvc.view has a +1 retain count as well
//Add the SubViewControllers view to this view.
[subsvc.view setFrame:CGRectMake((d.Position-1)*315, 0, 315, 400)];
[self.view addSubview:subsvc.view];
//!i: This bumps subsvc.view to +2, as self.view strong-references it
//!i: subsvc is going out of scope, so the reference count on subsvc will drop
// to 0 and it is dealloc'd. subsvc.view's retain count drops to +1, as it
// is still referenced by self.view
//
// Most likely, in -[SubViewController dealloc], you were not doing a
// setTarget:nil, setAction:nil on the button. Thus, the button now
// has a dangling pointer and will crash when hit
}
To fix this, add each SubViewController instance to an array owned by the master view controler. That will keep the SubViewController instances around to receive the button taps.
Make sure in your dealloc you call:
[button removeTarget:nil
action:NULL
forControlEvents:UIControlEventAllEvents];
Even though you though you didn't need "dealloc's" in ARC, you do because of what iccir explained.

UINavigationController stops pushing view

We use a navigation controller and a view controller to display a question to the user. Everything has been working fine but we made some UI adjustments so we can port the application to iPad, the only changes were to make the frame of the table view dynamic to be either on iphone or ipad. However now when we get to the 187 question out of 335 it doesn't push the new question anymore... it pushes a blank screen and the "viewDidLoad" method of the pushed view controller is never called, as it has been the past 187 times. We have setup break points to make sure the navigation controller and view controller are still be allocated in memory and they are.
Here is the viewDidLoad of the view controller that gets called every new push...
- (void)viewDidLoad {
_tableView = [[QuestionTableView alloc] initWithFrame:self.view.frame style:UITableViewStyleGrouped];
_tableView.center = CGPointMake(self.view.center.x, self.view.frame.size.height/2);
[_tableView setDataSource:self];
[_tableView setQuestionDelegate:self];
[_tableView setDelegate:self];
_tableView.scrollEnabled = YES;
[_tableView setBackgroundView:[[[UIView alloc] init] autorelease]];
_tableView.directionalLockEnabled = YES;
_tableView.delaysContentTouches = NO;
_tableView.backgroundColor = [UIColor clearColor];
_tableView.opaque = NO;
[self.view addSubview:_tableView];
}
We push the the view controller by...
[questionsNavigationController pushViewController:viewController animated:YES];
Thanks in advanced! :)
If you make the tableview smaller you can get through the entire set of 335 questions ? Are you creating a ViewController per question ?
You could run the project with instruments to check for a memory leak.
Do you really need all questions on the stack? How about a popViewControllerAnimated:NO before pushing the next one, also with animated:NO?
It works on the simulator because it's memory is the PC's memory. Put an NSLog into your -didReceiveMemoryWarning method to see it running out of memory.
Probably you shouldn't use NavigationController this way. It's.. ugly.
I would do one of the following:
"pop" ViewControllers that are 5-10 views behind (using setViewController). In other words - maintain 5-10 views behind, the others will be freed (and the result saved). Once the user decides to get back (and there are 3-4 views in the stack), reconstruct few more.
implement the NavigationController behaviour yourself - just replace the views instead of stacking them. Once the user gets back, reload the view with the needed data.
If you realy think that your implementation is ok - try to free as much possible memory from your view, once things get hot.