Cocos2d v3 CCButton conflicting with UIGestureRecognizers - objective-c

I've added a UIGestureRecognizer to a class which hinerits from NSObject, and I'm handling the gestures with the UIGestureRecognizers, but I need to add an upper layer and add some CCButton on it, so to conceptualize my hierarchy would be pretty much this:
- CCScene
-- CCNode added to the scene (with the gesture recognizers)
-- another CCNode added with a few CCButton
the UIGestureRecognizers are working well, but the CCButton is never called because the touch is handled by the gestures, if I remove the gestures, the CCButton is called
they are in conflict and I don't know why, I've read that the CCNodes touches are swallowed starting from the first (in term of hierarchy, where the first is the bigger z order I think) until the last, so since both my CCNode are added to the scene, and since the second ccnode (added with a bigger z order) is the first I think it should get the touch BEFORE the UIGestureRecognizer of the other ...
what I'm missing?
here's how I've added the gestures:
UITapGestureRecognizer *myGesture = [[UITapGestureRecognizer alloc] initWithTarget:self.myClassImplementation action:#selector(gesture:)];
[tapGesture setDelegate:self.myClassImplementation];
[[[CCDirector sharedDirector] view] addGestureRecognizer:myGesture];
I tried to simply add the CCButton directly into the CCScene but isn't called, can someone explain to me why they are in conflict? so to find I way to make it works

Gesturerecognizers get all the standard responder touch events. So you should handle it in gesturerecognizer's delegate method or in gesture action.
Here is how you can do it in delegate method:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
CGPoint touchLocation = [[CCDirector sharedDirector] convertToGL: [touch locationInView:touch.view]];
CCResponderManager *responder = [[CCDirector sharedDirector] responderManager];
CCNode *node = [responder nodeAtPoint:touchLocation];
if([node isKindOfClass:[CCButton class]])
{
NSLog(#"button");
return NO;
}
else
{
NSLog(#"not button");
return YES;
}
}

Related

Touch not detected - subview

I have been trying to figure this out for some time now. I am using auto layout with a scrollview. Inside the scrollview is a UIView, another UIView and then the subview within that. I can't get touch to work with the subview. I have tried touchesbegan along with uitapgesture with no success. Any suggestions? Here is the layout hierarchy.
ScrollView
---- UIView
------- UIView
------ UIView - Need to detect touch here.
EDIT:
Sorry guys, was at work. Here is some code. I have subclassed my scrollview to receive touches as it seems to be working best so far getting down to the subview level of other view subviews.
The following code detects that the view is that class without the pointInside. I am currently having issues receiving the right point for the view.
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
UIView *relatedVidsView = [[[touches allObjects] firstObject] view];
for (int i = 0; i < relatedVidsView.subviews.count; i++) {
UIView *view = [relatedVidsView.subviews objectAtIndex:i];
CGPoint touchPoint = [[[touches allObjects] firstObject] locationInView:self];
if ([view isKindOfClass:[RecipeVideoView class]] && [view pointInside:touchPoint withEvent:nil]) {
NSLog(#"TEST");
}
}
[super touchesBegan:touches withEvent:event]; }
EDIT 2:
So I was able to figure this out. I subclassed the view that was containing the subviews that i needed touch events for. What I did was passed the events from the parent to the subviews using touchesBegan. I will post my code later, thanks for the help anyway.

How to remove a CCnode with a touch?

I need help I have tried to remove a ccnode that constantly is respawning at different locations and adding them to an array to get control of which sprites are on screen, but the thing is that I can't get to remove them. It detects the touches but doesn't get removed any ideas? Here is the code I'm using to get rid of the node.
- (void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
CGPoint location = [touch locationInView:[touch view]];
location = [[CCDirector sharedDirector] convertToUI:location];
for (CCNode *sprite in _spritesOnScreen) {
if (CGPointEqualToPoint(sprite.position, location)) {
[_spritesOnScreen removeObject:sprite];
[self removeChild:sprite cleanup:YES];
}
}
}
Allow me to offer you a slightly different approach. Subclass CCNode to CCAppleNode and within the CCAppleNode.m file detect touches and call removeFromParent on touchBegan. This way the CCAppleNode class is taking up the responsibility of removing itself from parent when its touched, taking away this responsibility from your main game scene.
-(void) touchBegan:(UITouch *)touch withEvent:(UIEvent *)event{
[self removeFromParentAndCleanup:YES];
[super touchBegan:touch withEvent:event];
}

UISlider inside UIPageViewController

I have a PageViewController which is initialized like this:
self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
On one of the pages, there's a UISlider.
My problem is that when I have transitionstyle set to UIPageViewControllerTransitionStyleScroll, it takes 150-200 ms before beginTrackingWithTouch is invoked on the slider.
This behavior is not seen when I use UIPageViewControllerTransitionStylePageCurl, where the UISlider is selected instantly.
This means that unless the user waits a bit before dragging the slider (a video progress), the page will turn instead, which is far from ideal.
The Page curl animation does not meet the demands of the app, so any explanation or workaround is appreciated.
Since with UIPageViewControllerTransitionStyleScroll gesture recognizers isn't available, you can use this:
for (UIView *view in pageViewController.view.subviews) {
if ([view isKindOfClass:[UIScrollView class]]) {
UIScrollView *scrollView = (UIScrollView *)view;
scrollView.delaysContentTouches = NO;
}
}
I solved this issue by add a pan gesture on UISlider and set:
self.sliderGesture.cancelsTouchesInView = NO; // make touch always triggered
and implement delegate method like:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return otherGestureRecognizer.view.superview == self.parentViewController.view;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
// only receive touch in slider
CGPoint touchLocation = [touch locationInView:self.view];
return CGRectContainsPoint(self.slider.frame, touchLocation);
}
You can try to set the delegate of the page view controller gestures to the root view controller:
for (UIGestureRecognizer* gestureRecognizer in self.pageViewController.gestureRecognizers) {
gestureRecognizer.delegate = self;
}
And then prevent the touch of the gestures if it appears inside UISlider which is a subclass of UIControl:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
return ([touch.view isKindOfClass:[UIControl class]] == NO);
}
What helped me was to add pan-gesture-recognizer to UIView which holds UISlider, so in the end I have
UIPageViewController->UIScrollView->...->MyView->UISlider
The 'MyView' thing had pan gesture registered to it which did nothing, but served just to NOT propagate events to scroll view.

Recognizing UIScrollView movement by pixel

I need to change UIScrollView subviews according to their place on the screen, so that they will get smaller while moving up and bigger while moving down.
Is there any way to know the contentOffset with the change of every pixel?
I catch the scrollViewDidScroll: method, but whenever the movement is fast there might be some 200pxls change between two calls.
Any ideas?
You have basically two approaches:
subclass UIScrollView and override touchesBegan/Moved/Ended;
add you own UIPanGestureRecognizer to your current UIScrollView.
set a timer, and each time it fires, update your view reading _scrollview.contentOffset.x;
In the first case, you would do for the touch handling methods:
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
UITouch* touch = [touches anyObject];
_initialLocation = [touch locationInView:self.view];
_initialTime = touch.timestamp;
<more processing here>
//-- this will make the touch be processed as if your own logics were not there
[super touchesBegan:touches withEvent:event];
}
I am pretty sure you need to do that for touchesMoved; don't know if you also need to so something specific when the gesture starts or ends; in that case also override touchesMoved: and touchesEnded:. Also think about touchesCancelled:.
In the second case, you would do something like:
//-- add somewhere the gesture recognizer to the scroll view
UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panView:)];
panRecognizer.delegate = self;
[scrollView addGestureRecognizer:panRecognizer];
//-- define this delegate method inside the same class to make both your gesture
//-- recognizer and UIScrollView's own work together
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return TRUE;
}
The third case is pretty trivial to be implemented. Not sure if it will give better results that the other two.

Proper cocos2d scene restart?

How do I restart cocos2d scene and clean memory after the previous one?
[director replaceScene:s] is displaying pink screen. Can't get it to work.
The work-around I made is below, however after closing scene this way app runs very slow and slowly reacts to touch. MainViewController.m:
- (IBAction)startScene {
[CCTexture2D setDefaultAlphaPixelFormat:kTexture2DPixelFormat_RGBA8888];
if ([director runningScene]) {
[director end];
works = NO;
}
if (!works) {
EAGLView *glview = [EAGLView viewWithFrame:CGRectMake(0,0, 768, 1024)];
[self.view addSubview:glview];
[director setOpenGLView:glview];
CCLayer *layer = [PlayLayer node];
CCScene *s = [CCScene node];
[s addChild: layer];
[director runWithScene:s];
}
}
Inside PlayLayer.m I go back from scene to view using:
CCDirector *d = [CCDirector sharedDirector];
EAGLView *v = [d openGLView];
[v removeFromSuperview];
Application is displaying images (1280x960x32, imageview inside scrollview) and when user presses the button he can start cocos2d scene. User can then leave from scene back to view (.xib) and continue browsing through images till he finds a new one to start scene with.
I believe this is not a good way to leave scene but everything else I tried is causing app to crash or I keep getting this pink screen 2nd time I start scene. How do I restart it ?
I finally could get rid of pink screen while replacing scene. This is how I am doing it. While adding scene, I check if it's running first time or subsequent times.
MyTestAppDelegate * appDelegate = (MyTestAppDelegate *)[[UIApplication sharedApplication] delegate];
EAGLView *glView;
//check if scene is running first time then create OpenGLView, add to director and run the scene
if ([[CCDirector sharedDirector] runningScene] == NULL) {
if( ! [CCDirector setDirectorType:kCCDirectorTypeDisplayLink] )
[CCDirector setDirectorType:kCCDirectorTypeDefault];
CCDirector *director = [CCDirector sharedDirector];
glView = [EAGLView viewWithFrame:[appDelegate.window bounds]
pixelFormat:kEAGLColorFormatRGB565 // kEAGLColorFormatRGBA8
depthFormat:0 // GL_DEPTH_COMPONENT16_OES
];
[director setOpenGLView:glView];
[director setDeviceOrientation:kCCDeviceOrientationPortrait];
[director setAnimationInterval:1.0/60];
[director setDisplayFPS:YES];
[appDelegate.window addSubview:glView];
[[CCDirector sharedDirector] runWithScene: [MyScene node]];
}
//if scene is to be reloaded, add back the openGLView which was removed when removing the scene
else {
[appDelegate.window addSubview:[[CCDirector sharedDirector] openGLView]];
[[CCDirector sharedDirector] replaceScene:[MyScene node]];
}
For removing currently running scene:
[[CCDirector sharedDirector] replaceScene:[MySceneTwoLayer scene]];
[[CCDirector sharedDirector].openGLView removeFromSuperview];
Adding MySceneTwoLayer is optional. It is an empty scene. I am using it to make sure previous scene was properly removed. And I checked, previous scene's dealloc is properly called.
I hope it helps.
I came across a simliar problem when using Storyboards and when returning to m original UI view which contained my CCGLView from another view.
//when leaving the scene containing the CCGLView.
[CCDirector director] popScene];
then I noticed in viewDIdLoad for that viewcontroller, it would go through this when reinstantiated bt the segue, where you can re add the scene again.
// in viewDidLoad
[CCDirector director] pushScene: theSceneIRemovedEtc];
Admititedly this mightnot work for all cases but certainly did for me..
Ok, found a solution that closes the scene from inside and works well.
While starting scene I add the notification observer in my viewcontroller:
(...)
[director runWithScene:s];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(mySceneEnd:) name:#"scene_ended" object:nil];
That is connected to:
- (void) mySceneEnd:(NSNotification *)notif {
if ([director runningScene])
[director end];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
Then in the scene ending method I'm sending the notification to my viewcontroller:
EAGLView *v = [[CCDirector sharedDirector] openGLView];
[v removeFromSuperview];
[[NSNotificationCenter defaultCenter] postNotificationName:#"scene_ended"
object:nil userInfo:nil];
Works really nice, app runs fast after closing scene, but it's an old question/answer and there is surely a better way to do it now.