I am building a Sprite Kit game where the player shoots a particle whenever the screen is pressed. How can I limit the touch inputs (let's say 2 recognized touch inputs per second) so that the player can't just quickly tap the screen to get unlimited shots?
Another solution:
Create a BOOL (I prefer to work with properties myself, so):
#property (nonatomic, assign) BOOL touchEnabled;
Set it to YES in the scene's init. Then it is fairly simple from thereon:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.touchEnabled){
self.touchEnabled = NO;
[self shootParticle];
[self performSelector:#selector(enableTouch) withObject:nil afterDelay:2.0];
}
...
- (void)shootParticle{
// whatever...
}
- (void)enableTouch{
self.touchEnabled = YES;
}
One of many possibilities:
#interface YourSceneName (){
int _amountBullets; //increase every time you shot and just shoot when _fireStop = NO
BOOL _fireStop; // init as NO at start
BOOL _needStartTime; // init as YES at start
CFTimeInterval _startTime;
}
#end
-(void)update:(CFTimeInterval)currentTime {
//set starttime
if(_needStartTime){
_startTime = currentTime;
_needStartTime = NO;
}
//timeinterval if 2 seconds, renew everything
if(currentTime - _startTime > 2){
_startTime = currentTime;
_amountBullets = 0
_fireStop = NO;
}
//set firestop to yes, method should be executed
if(_amountBullets = 2){
_fireStop = YES;
}
}
I am new in SpriteKit but this should work. I bet there are better possibilities. Also i haven´t test the code. It shell show you the logic of how you could do it, hope I could help.
Here is a great Tutorial for working with time in SpriteKit.
Hi Im making a cocos2d game that has a sprite flying and falling, Im trying to have a first tap touch event for example when the user touches the screen for the first time it'll start the game animation and start the physics engine. Whats happening is that when the user starts the game the sprite falls down right away, can anyone give me a hand with this?
right now Im using something like this but Im not sure how to get the physics engine to wait until the user touches the screen for the first time.
CCSprite *_pixie
CCNode *_start;
BOOL *_firstTap;
CCPhysicsNode *_physicsNode;
-(void)didLoadFromCCB{
_physicsNode.collisionDelegate = self;
_firstTap = True;
}
- (void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
if(_firstTap == TRUE){
_start.visible = FALSE;
_firstTap = False;
}
//flying sounds & so on
if (!_gameOver) {
[[OALSimpleAudio sharedInstance] playEffect:MAGIC volume:0.4 pitch:1 pan:0 loop:NO];
[_pixie.physicsBody applyImpulse:ccp(0, 420.f)];
[_pixie.physicsBody applyAngularImpulse:11000.f];
_sinceTouch = 0.f;
}
}
- (void)update:(CCTime)delta {
if(_firstTap == FALSE){
float yVelocity = clampf(_pixie.physicsBody.velocity.y, -1 * MAXFLOAT, 200.f);
if ((_sinceTouch > .5f)) {
[_pixie.physicsBody applyAngularImpulse:-40000.f*delta];
}
}
}
Change
BOOL *_firstTap;
to
BOOL _firstTap; //No asterisk
And also make sure that you set _firsttap = YES in viewDidLoad functions
- (void)viewDidLoad
{
[super viewDidLoad];
_firstTap = YES;
}
Looks to me like the first touch Boolean value may not be defined until after the update code is called. You are also mixing BOOL values with bool values.
Objective-C : BOOL vs bool
I have a CCNode that contains multiple CCSprite children.
I would like to receive touch events in my parent CCNode if any of the children have been touched.
This behaviour seems like it should be supported, I may be missing something.
My solution is to setUserInteractionEnabled = YES on all children and bubble the event up to the parent.
I do this by subclassing the CCSprite class overriding their method :
- (void) touchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
[super touchBegan:touch withEvent:event];
}
I am wondering if there is a more elegant, simple and generic way of accomplishing the same behaviour ?
You could override hitTestWithWorldPos: of your 'containing' node, either calling hitTestWithWorldPos on specific children or, iterating through all children as you see fit. Perhaps something like this:
-(BOOL) hitTestWithWorldPos:(CGPoint)pos
{
BOOL hit = NO;
hit = [super hitTestWithWorldPos:pos];
for(CCNode *child in self.children)
{
hit |= [child hitTestWithWorldPos:pos];
}
return hit;
}
edit: just to be clear, then you would only need to setUserInteractionEnabled for the container, and only process the touch using the touch events of the containing node.
edit2:
so, I thought about it for a bit more and here's a quick category you can add that will enable a quick hit test for all children of a node recursively.
CCNode+CCNode_RecursiveTouch.h
#import "CCNode.h"
#interface CCNode (CCNode_RecursiveTouch)
{
}
-(BOOL) hitTestWithWorldPos:(CGPoint)worldPos forNodeTree:(id)parentNode shouldIncludeParentNode:(BOOL)includeParent;
#end
CCNode+CCNode_RecursiveTouch.m
#import "CCNode+CCNode_RecursiveTouch.h"
#implementation CCNode (CCNode_RecursiveTouch)
-(BOOL) hitTestWithWorldPos:(CGPoint)worldPos forNodeTree:(id)parentNode shouldIncludeParentNode:(BOOL)includeParent
{
BOOL hit = NO;
if(includeParent) {hit |= [parentNode hitTestWithWorldPos:worldPos];}
for( CCNode *cnode in [parentNode children] )
{
hit |= [cnode hitTestWithWorldPos:worldPos];
(cnode.children.count)?(hit |= [self hitTestWithWorldPos:worldPos forNodeTree:cnode shouldIncludeParentNode:NO]):NO; // on recurse, don't process parent again
}
return hit;
}
#end
usage would just be .. in the containing class, override hitTestWithWorldPos like this:
-(BOOL) hitTestWithWorldPos:(CGPoint)pos
{
BOOL hit = NO;
hit = [self hitTestWithWorldPos:pos forNodeTree:self shouldIncludeParentNode:NO];
return hit;
}
and of course, don't forget to include the category header.
-(void) touchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
//Do whatever you like...
//Bubble the event up to the next responder...
[[[CCDirector sharedDirector] responderManager] discardCurrentEvent];
}
I want to hide/unhide grey colored view(Which has button) and it is on top of cream/biege colored webView - please see attached pic
I have used an page based application template available in xcode.
Approach 1:Hide/unhide inside controller
i have tried to hide/unhide in same controller but the problem is every time new instance of this controller created and bool values for hide/unhide are lost
Approach 2:Protocol & Delegates
I have also tried to use delegate/protocol to maintain its status from its parent controller but it never gets inside if block -
if ([self.delegateReadingToolbar........ block is never called.
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if(touch)
{
CGPoint location = [touch locationInView: [touch view]];
if (CGRectContainsPoint(webViewTouch, location))
{
//do whatever
NSLog(#"webView Touched");
if (self.showReadingToolBar)
{
self.showReadingToolBar = NO; // approach 1
self.viewReadingToolBar.hidden = NO;
// approach 2
if ([self.delegateReadingToolbar respondsToSelector:#selector(contentViewDidFinish:showStatus:)])
{ // this block is never called
[self.delegateReadingToolbar contentViewDidFinish:self showStatus:NO];
}
}
else
{
self.showReadingToolBar = YES;
self.viewReadingToolBar.hidden = YES;
[self.delegateReadingToolbar contentViewDidFinish:self showStatus:YES];
}
}
}
}
For Approach 2 Coding:
ChildController.h
#class ChildController;
#protocol ReadingToolbarShowDelegate <NSObject>
-(void)contentViewDidFinish:(contentView *)controller showStatus:(BOOL)show;
#end
#property (nonatomic,weak)id<ReadingToolbarShowDelegate>delegateReadingToolbar;
ParentController.h
#import "ChildController.h"
#interface ParentController : UIViewController<UIPageViewControllerDataSource,UIPageViewControllerDelegate,ReadingToolbarShowDelegate>
ParentController.m
-(void)ChildControllerDidFinish:(contentView *)controller showStatus:(BOOL)show
{
showReadingToolbar = show;
}
If you alloc/init a new controller the previously set values will be gone. You are essentially creating a new object. I see a couple of different ways you may be able to do this.
Place your controllers inside a navigation controller and simply push the others on or off the stack. This way you don't have to instantiate a new object.
You could save the desired display configuration In an NSUserDefaults key/value pair. Whenever you need to create a new object of this type it looks up the value in NSUserDefault.
Let's say we have a view controller with one sub view. the subview takes up the center of the screen with 100 px margins on all sides. We then add a bunch of little stuff to click on inside that subview. We are only using the subview to take advantage of the new frame ( x=0, y=0 inside the subview is actually 100,100 in the parent view).
Then, imagine that we have something behind the subview, like a menu. I want the user to be able to select any of the "little stuff" in the subview, but if there is nothing there, I want touches to pass through it (since the background is clear anyway) to the buttons behind it.
How can I do this? It looks like touchesBegan goes through, but buttons don't work.
Create a custom view for your container and override the pointInside: message to return false when the point isn't within an eligible child view, like this:
Swift:
class PassThroughView: UIView {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
for subview in subviews {
if !subview.isHidden && subview.isUserInteractionEnabled && subview.point(inside: convert(point, to: subview), with: event) {
return true
}
}
return false
}
}
Objective C:
#interface PassthroughView : UIView
#end
#implementation PassthroughView
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
for (UIView *view in self.subviews) {
if (!view.hidden && view.userInteractionEnabled && [view pointInside:[self convertPoint:point toView:view] withEvent:event])
return YES;
}
return NO;
}
#end
Using this view as a container will allow any of its children to receive touches but the view itself will be transparent to events.
I also use
myView.userInteractionEnabled = NO;
No need to subclass. Works fine.
From Apple:
Event forwarding is a technique used by some applications. You forward touch events by invoking the event-handling methods of another responder object. Although this can be an effective technique, you should use it with caution. The classes of the UIKit framework are not designed to receive touches that are not bound to them .... If you want to conditionally forward touches to other responders in your application, all of these responders should be instances of your own subclasses of UIView.
Apples Best Practise:
Do not explicitly send events up the responder chain (via nextResponder); instead, invoke the superclass implementation and let the UIKit handle responder-chain traversal.
instead you can override:
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
in your UIView subclass and return NO if you want that touch to be sent up the responder chain (I.E. to views behind your view with nothing in it).
A far simpler way is to "Un-Check" User Interaction Enabled in the interface builder. "If you are using a storyboard"
Lately I wrote a class that will help me with just that. Using it as a custom class for a UIButton or UIView will pass touch events that were executed on a transparent pixel.
This solution is a somewhat better than the accepted answer because you can still click a UIButton that is under a semi transparent UIView while the non transparent part of the UIView will still respond to touch events.
As you can see in the GIF, the Giraffe button is a simple rectangle but touch events on transparent areas are passed on to the yellow UIButton underneath.
Link to class
Top voted solution was not fully working for me, I guess it was because I had a TabBarController into the hierarchy (as one of the comments points out) it was in fact passing along touches to some parts of the UI but it was messing with my tableView's ability to intercept touch events, what finally did it was overriding hitTest in the view I want to ignore touches and let the subviews of that view handle them
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
UIView *view = [super hitTest:point withEvent:event];
if (view == self) {
return nil; //avoid delivering touch events to the container view (self)
}
else{
return view; //the subviews will still receive touch events
}
}
Building on what John posted, here is an example that will allow touch events to pass through all subviews of a view except for buttons:
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
// Allow buttons to receive press events. All other views will get ignored
for( id foundView in self.subviews )
{
if( [foundView isKindOfClass:[UIButton class]] )
{
UIButton *foundButton = foundView;
if( foundButton.isEnabled && !foundButton.hidden && [foundButton pointInside:[self convertPoint:point toView:foundButton] withEvent:event] )
return YES;
}
}
return NO;
}
Swift 3
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
for subview in subviews {
if subview.frame.contains(point) {
return true
}
}
return false
}
According to the 'iPhone Application Programming Guide':
Turning off delivery of touch events.
By default, a view receives touch
events, but you can set its userInteractionEnabled property to NO
to turn off delivery of events. A view also does not receive events if it’s hidden
or if it’s transparent.
http://developer.apple.com/iphone/library/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/EventHandling/EventHandling.html
Updated: Removed example - reread the question...
Do you have any gesture processing on the views that may be processing the taps before the button gets it? Does the button work when you don't have the transparent view over it?
Any code samples of non-working code?
As far as I know, you are supposed to be able to do this by overriding the hitTest: method. I did try it but could not get it to work properly.
In the end I created a series of transparent views around the touchable object so that they did not cover it. Bit of a hack for my issue this worked fine.
Taking tips from the other answers and reading up on Apple's documentation, I created this simple library for solving your problem:
https://github.com/natrosoft/NATouchThroughView
It makes it easy to draw views in Interface Builder that should pass touches through to an underlying view.
I think method swizzling is overkill and very dangerous to do in production code because you are directly messing with Apple's base implementation and making an application-wide change that could cause unintended consequences.
There is a demo project and hopefully the README does a good job explaining what to do. To address the OP, you would change the clear UIView that contains the buttons to class NATouchThroughView in Interface Builder. Then find the clear UIView that overlays the menu that you want to be tap-able. Change that UIView to class NARootTouchThroughView in Interface Builder. It can even be the root UIView of your view controller if you intend those touches to pass through to the underlying view controller. Check out the demo project to see how it works. It's really quite simple, safe, and non-invasive
I created a category to do this.
a little method swizzling and the view is golden.
The header
//UIView+PassthroughParent.h
#interface UIView (PassthroughParent)
- (BOOL) passthroughParent;
- (void) setPassthroughParent:(BOOL) passthroughParent;
#end
The implementation file
#import "UIView+PassthroughParent.h"
#implementation UIView (PassthroughParent)
+ (void)load{
Swizz([UIView class], #selector(pointInside:withEvent:), #selector(passthroughPointInside:withEvent:));
}
- (BOOL)passthroughParent{
NSNumber *passthrough = [self propertyValueForKey:#"passthroughParent"];
if (passthrough) return passthrough.boolValue;
return NO;
}
- (void)setPassthroughParent:(BOOL)passthroughParent{
[self setPropertyValue:[NSNumber numberWithBool:passthroughParent] forKey:#"passthroughParent"];
}
- (BOOL)passthroughPointInside:(CGPoint)point withEvent:(UIEvent *)event{
// Allow buttons to receive press events. All other views will get ignored
if (self.passthroughParent){
if (self.alpha != 0 && !self.isHidden){
for( id foundView in self.subviews )
{
if ([foundView alpha] != 0 && ![foundView isHidden] && [foundView pointInside:[self convertPoint:point toView:foundView] withEvent:event])
return YES;
}
}
return NO;
}
else {
return [self passthroughPointInside:point withEvent:event];// Swizzled
}
}
#end
You will need to add my Swizz.h and Swizz.m
located Here
After that, you just Import the UIView+PassthroughParent.h in your {Project}-Prefix.pch file, and every view will have this ability.
every view will take points, but none of the blank space will.
I also recommend using a clear background.
myView.passthroughParent = YES;
myView.backgroundColor = [UIColor clearColor];
EDIT
I created my own property bag, and that was not included previously.
Header file
// NSObject+PropertyBag.h
#import <Foundation/Foundation.h>
#interface NSObject (PropertyBag)
- (id) propertyValueForKey:(NSString*) key;
- (void) setPropertyValue:(id) value forKey:(NSString*) key;
#end
Implementation File
// NSObject+PropertyBag.m
#import "NSObject+PropertyBag.h"
#implementation NSObject (PropertyBag)
+ (void) load{
[self loadPropertyBag];
}
+ (void) loadPropertyBag{
#autoreleasepool {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Swizz([NSObject class], NSSelectorFromString(#"dealloc"), #selector(propertyBagDealloc));
});
}
}
__strong NSMutableDictionary *_propertyBagHolder; // Properties for every class will go in this property bag
- (id) propertyValueForKey:(NSString*) key{
return [[self propertyBag] valueForKey:key];
}
- (void) setPropertyValue:(id) value forKey:(NSString*) key{
[[self propertyBag] setValue:value forKey:key];
}
- (NSMutableDictionary*) propertyBag{
if (_propertyBagHolder == nil) _propertyBagHolder = [[NSMutableDictionary alloc] initWithCapacity:100];
NSMutableDictionary *propBag = [_propertyBagHolder valueForKey:[[NSString alloc] initWithFormat:#"%p",self]];
if (propBag == nil){
propBag = [NSMutableDictionary dictionary];
[self setPropertyBag:propBag];
}
return propBag;
}
- (void) setPropertyBag:(NSDictionary*) propertyBag{
if (_propertyBagHolder == nil) _propertyBagHolder = [[NSMutableDictionary alloc] initWithCapacity:100];
[_propertyBagHolder setValue:propertyBag forKey:[[NSString alloc] initWithFormat:#"%p",self]];
}
- (void)propertyBagDealloc{
[self setPropertyBag:nil];
[self propertyBagDealloc];//Swizzled
}
#end
Try set a backgroundColor of your transparentView as UIColor(white:0.000, alpha:0.020). Then you can get touch events in touchesBegan/touchesMoved methods. Place the code below somewhere your view is inited:
self.alpha = 1
self.backgroundColor = UIColor(white: 0.0, alpha: 0.02)
self.isMultipleTouchEnabled = true
self.isUserInteractionEnabled = true
Try this
class PassthroughToWindowView: UIView {
override func test(_ point: CGPoint, with event: UIEvent?) -> UIView? {
var view = super.hitTest(point, with: event)
if view != self {
return view
}
while !(view is PassthroughWindow) {
view = view?.superview
}
return view
}
}
I use that instead of override method point(inside: CGPoint, with: UIEvent)
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard self.point(inside: point, with: event) else { return nil }
return self
}
If you can't bother to use a category or subclass UIView, you could also just bring the button forward so that it is in front of the transparent view. This won't always be possible depending on your application, but it worked for me. You can always bring the button back again or hide it.