I'm working in objective c/cocos2d. I have a scene named AllLevelAndModes which I add a CCScrollView named levelScroll of type class EasyLevelSelectScene. On this level scroller, I have a bunch of buttons for my game level. When someone touches one of the buttons, a popup comes up displaying some stats about the level. I add this popup by add childing it to EasyLevelSelectScene when a button is tapped. This child is of kind of class LevelSelectDropdown. I want to make a functionality such that if a user taps outside the popup (which is smaller than the whole screen), that the popup disappears.
Now I suppose I could get the size of the popup and the screen and if a user taps outside of some range of x and y, the popup disappears. But that is a little cumbersome. Is there a better way to do this? I was trying to basically get the name of the class that is tapped by running the following code once the popup comes.
-(void) touchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint touchLocationTemp = [[CCDirector sharedDirector] convertToGL: [touch locationInView:touch.view]];
CCResponderManager *responder = [[CCDirector sharedDirector] responderManager];
CCNode *node = [responder nodeAtPoint:touchLocationTemp];
CCLOG(#"class type %#", node); //Always reads 'EasyLevelSelectScene' no matter where I tap
if([node isKindOfClass:[EasyLevelSelectScene class]]) {
//go back to level select screen
}
}
Unfortunately this doesn't work though, since wherever I tap it logs EasyLevelSelectScene, even when I tap right on top of my popup! Why is it doing this, and is there a way to get the class name of the top-most child? Thanks for the help!
Related
I am making a simple game. The storyboard flow looks like:
[Title Screen] --> [Game Screen] --> [Game Over Screen]
From the Game Over screen, I'd like to jump back to the title screen directly, without seeing the Game Screen appear, when the user presses MENU.
I first attempted this in my GameOverViewController:
- (void) pressesBegan:(NSSet<UIPress*>*) presses withEvent:(UIPressesEvent*) event
{
for (UIPress* press in presses)
{
if (press.type == UIPressTypeMenu)
{
[self.navigationController popToRootViewControllerAnimated:NO];
return;
}
}
[super pressesBegan:presses withEvent:event];
}
It pops correctly, but the animation is not disabled. You can clearly see the game screen appear for a second, with a very visible fade in/out transition. The game view even gets -viewWillAppear: and -viewWillDisappear: messages.
So next, I tried replacing the pop an unwind segue.
[self performSegueWithIdentifier:#"Game Over to Title" sender:self];
This effect looks slightly different—the game screen only appears for an extremely brief duration for some reason—but it is visible for at least 1 or 2 frames before we get back to the title. I've disabled the "Animates" checkbox on the unwind segue in Xcode.
How should I go about this? I'm about ready to just dump storyboards entirely and try doing it another way. I feel like they're more complicated than managing the nibs myself. If I can't get the segues to go cleanly, the only other alternative I can think of is to introduce a dummy black screen into the hierarchy and figure out how to transition via a pop and a segue:
[Game Screen]
/
[Title Screen] --> [Dummy Black Screen]
\
[Game Over Screen]
Include all your "poppable" navigation in a new navigation controller, and pop the navigation controller itself whenever you need. I think it has to be modally presented but I'm unsure about that part. Give it a try in both cases, it's pretty much a one-liner.
Or (this isn't clean) you could first pop the parent controller without animation, that would be invisible to the user, and then popToRoot just like you did. With the intermediate view already popped, you'd be good, its just not really re-usable anywhere else and is bad practice.
I have made a graph with data in a UIView called HeartrateGraph. In a UIViewController named HRGraphInfo, I have a connected label that should output values when the graph is touched. The problem is, I don't know how to send a touched event using delegates from the UIView to the UIViewController.
Here is my touch assignment code in the UIView:
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
for (int i = 0; i < kNumberOfPoints; i++)
{
if (CGRectContainsPoint(touchAreas[i], point))
{
graphInfoRF.heartRateGraphString = [NSString stringWithFormat:#"Heart Rate reading #%d at %# bpm",i+1, dataArray[i]];
graphInfoRF.touched = YES;
break;
}
}
This segment of code is in a touchesBegan and properly stores the data value and number in the object graphInfoRF (I just did not show the declarations of dataArray, kNumberOfPoints, etc).
I am able to access graphInfoRF in the UIViewController using:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if (graphInfoRF.touched == YES) {
self.heartRateLabel.text = graphInfoRF.heartRateGraphString;
}
else {
self.heartRateLabel.text = #"No data got over to this file";}
}
The label will show the correct string, but only after the data point on the graph is touched AND the label is touched right after. How do I change the touchesBegan so that once I touch the data point on the graph it will fill the label with the data automatically without the need for a second and separate touch on the label?
All ViewControllers comes with a single view it manages once it's initialized. You should be familiar with this view, you see it whenever you use a ViewController in the Interface Builder and you can access it using self.view if you're modifying a subclass.
Since ViewControllers come with a view, it also receives touch events for that view. Implementing touchesBegan in the ViewController will then receive events for that view, and normally any subviews that view is managing. Since you've down your own implementation of 'touchesBegan' in your HeartRateGraph, and since HeartRateGraph is a subview of ViewControllers main view, HeartRateGraph will receive and handle the touch event first before the ViewController ever has a chance to receive and handle the event like it normally would (think of bubbling up).
So what's happening is, the code to change the label in ViewController is only called when the label is touched because the label is a subview of the ViewController's main view... and also label doesn't have its own touches implementation, so ViewController and is able to retrieve and handle the event the way you want only when you click somewhere outside the graph. If you understand then there are two ways to solve this.
Either pass the event up to your superview
[self.superview touchesBegan:touches withEvent:eventargs];
or the proper recommended way of doing it:
Protocols and Delegates where your View makes a delegate call to it ViewController letting it know the graph has been touched and the ViewController needs to update its contents
In Cocoa I developed app which is agent (runs only as icon on upper status bar).
It can display popover window which is basically subclass of NSWindow with NSView as it's content.
Into another NSView subclass (which represents icon on status bar) I'm adding
self.settingsPopoverTransiencyMonitor = [NSEvent addGlobalMonitorForEventsMatchingMask:NSLeftMouseDownMask|NSRightMouseDownMask handler:^(NSEvent *event) {
[selfReference hideSettingsPopover];
}];
So when user clicks outside popover windows it hides.
I want to implement similar behaviour when user swipes with four fingers up/down (so when Exposé or Mission Control are being launch).
I tried with lot of mask that are available in NSEvent.h but none of them helped.
First enable touch events in your NSView:
view.acceptsTouchEvents = YES
In your NSView subclass, override the touch event methods declared in the NSResponder superclass. If you want to recognize a 4-finger swipe gesture, you probably want -swipeWithEvent:. Since this will fire for swipe events that have any number of fingers, you would want to filter it to 4-finger gestures only. The method -[NSEvent touchesMatchingPhase:inView:] will return an array of NSTouch objects, one for each finger (ie. the count of the array is equivalent to the number of fingers).
To summarize, the implementation would look something like this:
- (void)swipeWithEvent:(NSEvent *)event
{
NSArray *touches = [event touchesMatchingPhase:NSTouchPhaseTouching inView:self];
if (touches.count == 4) {
// Handle event here
}
}
I have a pretty simple question for which I could not find a simple answer.
When using cocoa (osx, xcode) and a method called "mouseDown" which detects if mouse has clicked on a view, how to detect on which object mouse has clicked? I just need a class name so I can know if the user has clicked on, for example NSImageView, WebView, NSTextView or on a NSView it self? Or even better, if I have two NSImageViews on my NSView, how to detect on which one it was clicked?
Cheers.
In your view mouseDown method, you can call the hitTest: method to get the farthest descendant of the receiver in the view hierarchy that was clicked:
So in your view subclass, you could do something like:
- (void)mouseDown:(NSEvent *)theEvent
{
id clickedObject = [self hitTest:[theEvent locationInWindow]];
if ([clickedObject isKindOfClass:[NSImageView class]]) {
NSLog(#"Clicked an ImageView");
} else if ([clickedObject isKindOfClass:[WebView class]]) {
NSLog(#"Clicked a WebView");
}
}
Your question seems a bit odd though, because normally you don't need to do this hit testing yourself.
If you're trying to get a click event when a particular image is clicked, a better way would be to use a borderless button with an image set and then implementing an action method and connecting that to the button.
so i'm currently working on an app that draws something on the screen, like a graph and things like that. while testing it and it's functionality i came across this issue.
i have a button that when pressed goes to a settings scene. i initialize the scene like this
+(id) scenew
{
CCScene *scene = [CCScene node];
Settings *layer = [Settings node];
[scene addChild: layer];
return scene;
}
in that scene i have two more buttons, a done button and a what's new button.
done = [CCMenuItemImage itemFromNormalImage:#"done.png" selectedImage:#"done.png" target:self selector:#selector(done:)];
pressing the what's new button goes instantly but the done button has a 2-3 seconds delay.
the app has some console messages appearing and it seems that when it is pressed it recalculates the whole graph just like when starting the app.
all the buttons call the same CCDirector function which is replaceScene.
-(void) whatNew: (id)sender
{
[[CCDirector sharedDirector] replaceScene: [New scene]];
}
-(void) done: (id)sender
{
[[CCDirector sharedDirector] replaceScene: [Main scene]];
}
is there a way for me to optimize this a little...i mean...an efficient way to use the replaceScene, or maybe something else that doesn't force the recalculation of the graph? because whenever that button is pressed it practically jumps at the top of my graph class implementation which has 4500+ lines O.o
I assume the first button you mentioned above is inside the Main scene, going to the Setting scene.
To retain the Main scene in the memory while transferring the app flow to the Setting scene, all you have to do is to use pushScene: instead of replaceScene::
// in Main scene class
[[CCDirector sharedDirector] pushScene:[Setting scene]];
Then, from inside Setting scene, simply use popScene to return to the Main scene:
-(void) done: (id)sender
{
[[CCDirector sharedDirector] popScene];
}
Basically pushScene: simply push the new scene on top of the current scene while popScene pops it out, reverting the app flow back to the previous scene that still remains in the device memory.