I'm making a "Tapping-App" where you have to kill zombies by tapping on them. Currently i can't make any progress because i can't use the methods i need properly.
My App works like that:
I have a timer that spawns an image 3 times per second:
[NSTimer scheduledTimerWithTimeInterval:1.0/3
target:self
selector:#selector(Spawn)
userInfo:nil
repeats:YES];
then i have the Spawn command:
- (void) Spawn {
CGRect myImageRect = CGRectMake(0.0f, 0.0f, 320.0f, 109.0f);
UIImageView *myImage = [[UIImageView alloc] initWithFrame:myImageRect];
[myImage setImage:[UIImage imageNamed:#"myImage.png"]];
myImage.opaque = YES;
[self.view addSubview:myImage];
[myImage release]
}
I also have a "TouchesBegan" command:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchLocation = [touch locationInView:self.view];
}
What I want to do, is put all the spawning images into an Array; If one of the images is touched i want it to be removed from the superview.I want to know how an array works and how i can remove objects added to the array from a different function like "TouchesBegan".
Please help me with my problem!
EDIT:
Nevermind... I searched around the internet and found some useful resources witch were almost what i needed. I finally figured out how it works and got my spawner function to work with collision and with "TouchesBegan".
If somebody wants the code, just ask me.
DD
Use [NSMUtableArray addObject:] and [NSMutableArray removeObject:]
In your interface, something like:
#interface MyClass
{
NSMutableArray *zombies_;
}
In your implementation, after initializing the array, change your spawn method to include the line
[self.zombies addObject:myImage];
and in your touch handler, after you determine which view was touched, something like
[zombies_ removeObject:theView];
[theView removeFromSuperview];
Although, after thinking about it, you may be better off just creating UIButton instances instead of UIIMageViews and writing touch handlers to determine which view was touched.
Related
I know there are lots of questions on multi finger touches and restrictions here and there,but none of them seem to work.All they suggest is to try setting exclusive touch and disable multi touch i.e.:
[self.view.subviews makeObjectsPerformSelector:#selector(setExclusiveTouch:) withObject:[NSNumber numberWithBool:YES]];
[self.view setMultipleTouchEnabled:NO];
Note: Initially when I used self.view.exclusiveTouch = NO; ,it didn't work for me.Mr.Hlung's comment in the 1st link provided some useful info of enabling the subviews exclusive touch through selector.
But they seem to work incomplete.I have a game view where there are multiple images on left and their corresponding images on the right.User has to drag the left image to the respective right image and match it.I am using UIPanGestureRecognizer to do so.Here comes a scenario which was the outcome of some rash testing by a tester in my office :)
When the user scrambles the images,i.e. plays awkwardly with all/few of his fingers,what happens is the frames are getting disturbed or few images overlap with one another.This is all because some where multiple touch is still available for user.Hence I want to eliminate that feature.I am also aware of the fact that there is a method called -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event ,through which we can detect the number of user touches.What if I delete all user touches if the count is more than 1,i.e. if more than 1 finger touch is detected!!!
But I found no method to remove touches there,I also tried to assign last touch object to my image view through which I thought would ignore the rest of touches detected,i.e.:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if ([event allTouches].count>1)
{
UITouch *touch = [[touches allObjects] lastObject];
self.dragObjectImageView = (UIImageView *)[touch view];
}
}
Even this doesn't seem to work.
EDIT-Setting Max and Min number of touches:
I have also set the pan gesture's max and min no of touches to 1 as well,i.e.:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view.subviews makeObjectsPerformSelector:#selector(setExclusiveTouch:) withObject:[NSNumber numberWithBool:YES]];
for(UIImageView *iView in self.movableArray){
if ([iView isMemberOfClass:[UIImageView class]]){
UIPanGestureRecognizer * recognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
recognizer.minimumNumberOfTouches = 1;
recognizer.maximumNumberOfTouches = 1;
iView.multipleTouchEnabled = NO;
[iView addGestureRecognizer:recognizer];
[iView setUserInteractionEnabled:YES];
recognizer.delegate = self;
}
}
}
Tried another tactic of disabling user interaction for image if multiple touches are detected,but in vain!
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if ([event allTouches].count > 1)
{
for (UIView* view in self.view.subviews)
{
if ([view isKindOfClass:[UIImageView class]])
{
[view setUserInteractionEnabled:NO];
}
}
}
else
{
for (UIView* view in self.view.subviews)
{
if ([view isKindOfClass:[UIImageView class]])
{
[view setUserInteractionEnabled:YES];
}
}
}
}
Some people might say there can't be a case where user will use multiple fingers to play.But I was asked to fix the issue and a developer has to obey and respect the testers perspective as well.
Can some one please help me get rid of the problem.
Thanks every one in advance :)
Instead of setting exclusive touch to our own view,turning on the same for the subview or the view we assign to self.view worked,in my case it is...
[gameView.subviews makeObjectsPerformSelector:#selector(setExclusiveTouch:) withObject:[NSNumber numberWithBool:YES]];
Hope it helps some one,thanks :)
As per apple documentation for the property multipleTouchEnabled in UIView Class Reference
multipleTouchEnabled
Other views in the same window can still receive touch events when
this property is NO. If you want this view to handle multi-touch
events exclusively, set the values of both this property and the
exclusiveTouch property to YES.
I see in your code, you've set setExclusiveTouch to all subviews but setMultipleTouchEnabled only for the container view and not all image views. Add the following line before/after setting exclusive touch and single touch may work correctly.
[self.view.subviews makeObjectsPerformSelector:#selector(setMultipleTouchEnabled:) withObject:[NSNumber numberWithBool:NO]];
I'm using CAKeyframeAnimation.
-(IBAction)start:(id)sender {
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(600, 150)];
[path addLineToPoint:CGPointMake(600, 300)];
[path addLineToPoint:CGPointMake(450, 300)];
[path addLineToPoint:CGPointMake(450, 150)];
[path addLineToPoint:CGPointMake(600, 150)];
CAKeyframeAnimation *move = [CAKeyframeAnimation animationWithKeyPath:#"position"];
move.path = path.CGPath;
move.duration = 6.0f;
move.repeatCount = 100;
[testButton.layer addAnimation:move forKey:#"move"];
I need to make possible to use button while it moving.
Also I tried use touches detection but its only working with button while it stop.
Is it possible?
Thanks.
Really! you want the user to press the button while it is animating? People prefer to turn off interaction during animation. But anyway it this is your interaction then so be it. Have you tried - [yourButton setUserInteractionEnabled:TRUE];
Normally during any UIView animations we need to simply put UIViewAnimationOptionAllowUserInteraction as the animation option and thats it. Since you have gone more deeper in using CoreAnimation and layers, not sure what options make it work there. Please let us know how it works out for you...
UPDATE: This problem looked interesting to me. So I was just trying this out in Xcode. Here is what I found. To hit a moving button, you need to do hit testing on the button's .layer.presentationLayer property (need QuartzCore for this) in your view controller.
Internally, The animation is just eye candy. The animation lags behind the actual movement of the view. The button is already at the destination point when the animation starts. You just see a movie of the view/button moving. If you want a button to be clickable during the animation, you'll have to make the animation yourself.
So the coe would be like this-
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *t = [touches anyObject];
CGPoint location = [t locationInView:self.view];
for (UIButton *button in self.buttonsOutletCollection)
{
if ([button.layer.presentationLayer hitTest:location])
{
// This button was hit whilst moving - do something with it here
break;
}
}
}
So, right now I have a rocket which bounces side to side across the bottom of the iphone 5 screen. I want the rocket however to stop moving side to side but to go upwards once the user touches the screen (so touching the screen makes the rocket take off).
First, in viewDidAppear I told the compiler to run the spawnRocket method after 2 seconds.
In the spawnRocket method I established the rockets image and frame, and then I added it to the view and performed a uiview animation sending the rocket to the right side of the screen. In the finished parameter in the uiview animation method I told the compiler to run the moveRocketLeft method.
In the moveRocketLeft method I did an animation which would send the rocket to the left of the screen, and in the finished parameter I ran the moveRocketRight method.
The moveRocketMethod is basically the spawnRocket method except I don't establish the rockets image and frame and add it to the view.
So after all this, I tried to implemented the -touchesBegan:withEvent: method. I tried simply running a uiview animation which changed the rockets y to off the screen upwards, and the x to whatever the x was currently at when the user touched the screen.
However, I realize that the calling the rockets frame does not return the location that the rocket looks like while animating. It really just returns what the rockets frame will be when its done animating. So, to get the frame I want should I call layer? I remember reading somewhere that layer will return the actual location of a uiimageview during an animation.
But, layer only has the property bounds and not frame, so I'm a little confused.
I also tried calling [self.view.layer removeAllAnimations]; and then running a new animation which would shoot the awkward up, but the removeAllAnimations method never worked (and anyway i don't really like the idea of using that because i heard it lags).
So anyone have any idea how i can implement touchesBegan:withEvent: method so that the rocket can take off correctly? (or if you think I have to change my whole program please feel free to help)
#import "ViewController.h"
#import <QuartzCore/QuartzCore.h>
#interface ViewController ()
#property UIImageView *rocket;
#end
#implementation ViewController
#synthesize rocket=_rocket;
- (void)viewDidLoad
{
[super viewDidLoad];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[self.view.layer removeAllAnimations];
// [UIView animateWithDuration:3 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^(){self.rocket.frame=CGRectMake(self.rocket.frame.origin.x, -40, 25, 40);} completion:^(BOOL finished){}];
// this didn't work :(
}
-(void)viewDidAppear:(BOOL)animated{
[self performSelector:#selector(spawnRocket) withObject:self afterDelay:2];
}
-(void)spawnRocket{
self.rocket=[[UIImageView alloc]initWithImage:[UIImage imageNamed:#"default.png"]]; //places imageview right off screen
self.rocket.frame=CGRectMake(-25, 420, 25, 40);
[self.view addSubview:self.rocket];
[UIView animateWithDuration:1.5 delay:0 options:UIViewAnimationOptionCurveLinear animations:^(){self.rocket.frame=CGRectMake(295, 420, 25, 40);} completion:^(BOOL finished){if (finished)[self moveRocketLeft]; }];
}
-(void) moveRocketLeft{
[UIView animateWithDuration:1.5 delay:0 options:UIViewAnimationOptionCurveLinear animations:^(){self.rocket.frame=CGRectMake(0, 420, 25, 40);} completion:^(BOOL finished){if (finished)[self moveRocketRight];}];
}
-(void)moveRocketRight{
[UIView animateWithDuration:1.5 delay:0 options:UIViewAnimationOptionCurveLinear animations:^(){self.rocket.frame=CGRectMake(295, 420, 25, 40);} completion:^(BOOL finished){if (finished)[self moveRocketLeft];}];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
This should do the trick
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
CALayer *rocketPresentationLayer = [self.rocket.layer presentationLayer];
self.rocket.layer.position = rocketPresentationLayer.position;
[UIView animateWithDuration:3 delay:0 options:UIViewAnimationOptionCurveEaseIn|UIViewAnimationOptionBeginFromCurrentState animations:^(){self.rocket.frame=CGRectMake(self.rocket.frame.origin.x, -40, 25, 40);} completion:^(BOOL finished){}];
}
Note 1: I didn't put logic in here to check whether the user tapped the rocket again, when it is taking off vertically. You might want to add a BOOL or something to see if the user successfully hit the rocket or not, and if so, don't perform the vertical animation again.
Note 2: If in future you only want this to occur when the rocket is hit, you can use hitTest checking to call the animation conditionally.
// Get the touch
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:self.view];
// See if we hit the rocket
CALayer *rocketPresentationLayer = [self.rocket.layer presentationLayer];
CALayer* hitTest = [rocketPresentationLayer hitTest:touchPoint];
// If we did, then stop animation and move upwards
if (hitTest) {
....
}
P.S: Also as a slight mention, #property UIImageView *rocket; should be changed to #property (nonatomic) UIImageView *rocket;. This is primarily for performance reasons, as I don't expect your rocket to be accessed by multiple threads (it shouldn't, it's the UI!).
I have a view (parent) with two subviews, one on top (topChild) of the other (bottomChild).
If I tap on the screen only topChild and parent receive the touch event.
What should I change to propagate the touch event to bottomChild as well?
The code:
- (void)viewDidLoad
{
[super viewDidLoad];
MYView* parent = [[MYView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
parent.tag = 3;
parent.backgroundColor = [UIColor redColor];
MYView* bottomChild = [[MYView alloc] initWithFrame:CGRectMake(0, 0, 90, 90)];
bottomChild.tag = 2;
bottomChild.backgroundColor = [UIColor blueColor];
[parent addSubview:bottomChild];
MYView* topChild = [[MYView alloc] initWithFrame:CGRectMake(0, 0, 80, 80)];
topChild.tag = 1;
topChild.backgroundColor = [UIColor greenColor];
[parent addSubview:topChild];
[self.view addSubview:parent];
}
Where MYView is a subclass of UIView that only logs touchesBegan.
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"%d", self.tag);
[super touchesBegan:touches withEvent:event];
}
The result:
Touching the green area produces the following log:
TouchTest[25062:f803] 1
TouchTest[25062:f803] 3
My first idea was to make parent propagate all the touchesSomething calls to its children but (A) I suspect there might be an easier solution and (B) I don't know which child sent the event to parent, and sending touchesSomething messages twice to the same view might cause shenanigans.
After asking the question I've found this post that suggests to override hitTest to change the view that receives the touches. I will try this approach and update the question if it works.
It's an interesting problem you have that is probably something best addressed through a rethink of the way you have things structured. But to make it work the way you suggest you need to catch the touch event in the current top view, pass it to the parent and then propagate it down through all subviews of the parent view. To make this work you would need the touchesBegan: (or whatever other method you use to intercept the touch) to do nothing in all the views, doing the action only in the method called by the parent view.
Which is really another way of saying don't handle touches in the views, catch them but notify the parent view view and then call subview methods as required to cause the effect you want.
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// Do nothing, parent view calls my parentNotifiedTouchesBegan method
[self.superview touchesBegan:touches withEvent:event];
}
- (void) parentNotifiedTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// Act on the touch here just as my sibling views are doing
}
Note I changed super to self.superview in that code. You may or may not also want to call super's method depending on what you are doing, and the place to call that may be in parentNotifiedTouchesBegan.
You can know which subView sent the event of course, just use a custom method to notify the superview instead of calling its touchesBegan:. Use a self argument.
If you don't need the touches on the children then set
bottomChild.userInteractionEnabled = NO;
topChild.userInteractionEnabled = NO;
I have a view derived from a UIScrollView controller that I use to page through images in a library. That works fine.
I overlayed a UIView on the right side to handle touches for scrolling quickly through the list of images. My problem is that if I pass the touches through to either 'super' or 'nextResponder' they never make it to the UIScrollView object below.
My question is how can I force the UIScrollView below to handle the touches that I don't need to handle? I'm setting a timer for 0.3 seconds that during that time all touches are passed to the UIScrollView to handle. So if the user started a swipe gesture to turn the page, it will happen.
Here's the code for the touchesBegan method:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
// Start a timer to trigger the display of the slider and the processing of events.
touchesTimer = [NSTimer scheduledTimerWithTimeInterval:0.3 target:self selector:#selector(showSlider:) userInfo:nil repeats:NO];
// Where are we at right now?
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:self.view];
lastPage = [self page:currentPoint.y];
// Pass the event though until we need it.
// [self.nextResponder touchesBegan:touches withEvent:event];
if ([touch.view isKindOfClass:[UIScrollView class]])
{
if (self.nextResponder != nil &&
[self.nextResponder respondsToSelector:#selector(touchesEnded:withEvent:)])
{
[self.nextResponder touchesEnded:touches withEvent:event];
}
}
}
I would try using a regular UIViewController rather than a UIScrollViewController and then add the scroll view object where you need it and put the scroll faster view you want on the right side. This way they are separate and the touches won't get confused.
Here's an easier solutions that worked well for me:
In the UIView on top of the UIScrollview make the width and height both 0 (so that the view is no longer technically on top of the UIScrollView) and set clipsToBounds = NO (so that the contents of the view still show up on top of the UIScrollView).
self.OverlayView.clipsToBounds = NO;
CGRect frame = self.OverlayView.frame;
self.OverlayView.frame = CGRectMake(frame.origin.x, frame.origin.y, 0, 0);
Note that if the UIView contains interactive controls then they will no longer work. You'll need to move it into it's own view above the UIScrollView.