I have a UIView subclass called Card that I move around on my board and drop on hot spots called slots. When I drop the card I use the hitTest to figure out if I am dropping the card on one of my hotspots. I want to get a property of that hot spot but I am having trouble getting that to work properly. My only guess is the hitTest returns a UIView and my hot spot is a UIView subclass. The error I get is "Request for member 'slotIndex' in something not a structure or union"
Here is the TouchesEnded method I am using from my Card Class
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent*)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.superview];
[self setUserInteractionEnabled:NO];
UIView *backView = [self.superview hitTest:location withEvent:nil];
if ([backView isKindOfClass:[CardSlot class]]) {
self.center = backView.center;
NSLog(#"Slot Number: %#", backView.slotIndex);
} else {
//Move it back to the top corner
self.center = CGPointMake(50,50);
}
[self setUserInteractionEnabled:YES];
}
My question is how do I go about testing if I am in a slot hot spot and then get the properties of that slot (UIView Subclass)?
To help the compiler, you need to cast the pointer to a CardSlot after determining that it is one. This way the compiler can know about the slotIndex property. For example:
if ([backView isKindOfClass:[CardSlot class]]) {
CardSlot *cardSlot = (CardSlot *)backView;
// From here you can access cardSlot.slotIndex
}
Related
I am sure that this problem will be easily resolved however I am relatively new to iOS development. I am trying to handle passing touch events to children that are lower in the draw order on a UIView. For example -
I create extended UIImageView to create my MoveableImage class. This class is just basically UIImageView that implements the touchesBegan,touchesEnded and touchesMoved-
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self showFrame];
//if multitouch dont move
if([[event allTouches]count] > 1)
{
return;
}
UITouch *touch = [[event touchesForView:self] anyObject ];
// Animate the first touch
CGPoint colorPoint = [touch locationInView:self];
CGPoint touchPoint = [touch locationInView:self.superview];
//if color is alpha of 0 , they are touching the frame and bubble to next responder
UIColor *color = [self colorOfPoint:colorPoint];
[color getRed:NULL green:NULL blue:NULL alpha:&touchBeganAlpha];
NSLog(#"alpha : %f",touchBeganAlpha);
if(touchBeganAlpha > 0)
{
[self animateFirstTouchAtPoint:touchPoint];
}
else {
[super.nextResponder touchesBegan:touches withEvent:event];
}
}
So the end result is basically this- If they are touching the frame of the imageView and not the image inside the other image that will be underneath can possibly respond. See this image for an example.
So far I have tried next responder however that does not solve the problem. Any help would be greatly appreciated!
Resolved- I stopped checking the alpha on the touchesBegan and touchesMoved. Ovveriding pointInside allowed the UIView to handle that for me.
-(BOOL) pointInside:(CGPoint)point withEvent:(UIEvent *) event
{
BOOL superResult = [super pointInside:point withEvent:event];
if(!superResult)
{
return superResult;
}
if(CGPointEqualToPoint(point, self.previousTouchPoint))
{
return self.previousTouchHitTestResponse;
}else{
self.previousTouchPoint = point;
}
BOOL response = NO;
//if image is nil then return yes and fall back to super
if(self.image == nil)
{
response = YES;
}
response = [self isAlphaVisibleAtPoint:point];
self.previousTouchHitTestResponse = response;
return response;
}
You can override instead - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event method for your subclass of UIImageView. (It is a method of uiview that each subclass can override)
The UIView uses this method in hitTest:withEvent: to determine which subview should receive a touch event. If pointInside:withEvent: returns YES, then the subview’s hierarchy is traversed; otherwise, its branch of the view hierarchy is ignored.
Check the source code on github of OBShapedButton. They handle tap event only for the opaque part of a button.
We are adding drag and drop functionality to what is to become a sports field with positions for players.
The positions are mapped out using Interface Builder with each being a separate UIImageView.
We want to be able to drag player images from bench positions from the side of the screen onto positions on the field.
How best can we detect when the selected player which is being moved around collides with an existing gamePosition imageView?
We are looking for a way to detect if there is a view or ImageView under the current location.
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:touch.view];
tile1.center = location;
if gamePositionExistsAtCurrentLocation(location) { //want something like this
[tile1 setBackgroundColor:[UIColor blueColor]];
} else {
[tile1 setBackgroundColor:[UIColor yellowColor]];
}
}
check if the frame of the item you are moving intersects with the frame from on of your subviews
for (UIView *anotherView in self.subviews) {
if (movingView == anotherView)
continue;
if (CGRectIntersectsRect(movingView.frame, anotherView.frame)) {
// Do something
}
}
If I were you, I would add all game relevant items to an NSArray and for-loop through this. So you don't detect collisions with subviews that have nothing to do with your game like labels and so on.
Also might want to consider view.center with CGRectContainsPoint()
Could someone please tell me how to make an image appear when the user taps the screen and make it appear at the position of the tap.
Thanks in advance,
Tate
UIView is a subclass of UIResponder, which has the following methods that might help: -touchesBegan:withEvent:, -touchesEnded:withEvent:, -touchesCancelled:withEvent: and -touchesMoved:withEvent:.
The first parameter of each of those is an NSSet of UITouch objects. UITouch has a -locationInView: instance method which should yield the position of the tap in your view.
You could create an initial star and just move it every time view is touched.
I'm not sure what you're end result will look like.
Note:
This code will give you 1 star that moves with a tap
Here is my code:-
(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSSet *allTouches = [event allTouches];
switch ([allTouches count]) {
case 1:
{
UITouch *touch = [[allTouches allObjects] objectAtIndex:0];
CGPoint point = [touch locationInView:myView];
myStar.center = point;
break;
}
default:
break;
}
}
It seems implied from the question that you want the user to be able to tap anywhere on the screen and have an image drawn where they tap? As opposed to tapping in a designated place and having the image appear there?
If so, you're probably going to have to go with a custom view. In that case, you'd do something like the following:
Create a subclass of UIView.
Override the touchesBegan method. Call [[touches anyObject] locationInView:self] (where touches is the first argument to the method, an NSSet of UITouch objects) to get the location of the touch, and record it.
Override the touchesEnded method. Determine the location touches ended at using the same method as in step 2.
If the second location is near the first, you'll want to place your image at that location. Record that location and call [self setNeedsDisplay] to cause the custom view to be redrawn.
Override the drawRect method. Here, if the location has been set in step 4, you can use the UIImage method drawAtPoint to draw your image at the selected location.
For further details, this link might be worth a look. Hope that helps!
EDIT: I've notice you've asked essentially the same question before. If you're not happy with the answers given there, it's generally considered better to "bump" the old one, perhaps by editing it to ask for further clarification, rather than create a new question.
EDIT: As requested, some very brief sample code follows. This is probably not the best code around, and I haven't tested it, so it may be a little iffy. Just for clarification, the THRESHOLD allows the user to move their finger a little while tapping (up to 3px), because it's very difficult to tap without moving your finger a little.
MyView.h
#define THRESHOLD 3*3
#interface MyView : UIView
{
CGPoint touchPoint;
CGPoint drawPoint;
UIImage theImage;
}
#end
MyView.m
#implementation MyView
- (id) initWithFrame:(CGRect) newFrame
{
if (self = [super initWithFrame:newFrame])
{
touchPoint = CGPointZero;
drawPoint = CGPointMake(-1, -1);
theImage = [[UIImage imageNamed:#"myImage.png"] retain];
}
return self;
}
- (void) dealloc
{
[theImage release];
[super dealloc];
}
- (void) drawRect:(CGRect) rect
{
if (drawPoint.x > -1 && drawPoint.y > -1)
[theImage drawAtPoint:drawPoint];
}
- (void) touchesBegan:(NSSet*) touches withEvent:(UIEvent*) event
{
touchPoint = [[touches anyObject] locationInView:self];
}
- (void) touchesEnded:(NSSet*) touches withEvent:(UIEvent*) event
{
CGPoint point = [[touches anyObject] locationInView:self];
CGFloat dx = point.x - touchPoint.x, dy = point.y - touchPoint.y;
if (dx + dy < THRESHOLD)
{
drawPoint = point;
[self setNeedsDisplay];
}
}
#end
Could someone please tell me how to make an image appear when the user taps the screen and make it appear at the position of the tap.
Thanks in advance,
Tate
UIView is a subclass of UIResponder, which has the following methods that might help: -touchesBegan:withEvent:, -touchesEnded:withEvent:, -touchesCancelled:withEvent: and -touchesMoved:withEvent:.
The first parameter of each of those is an NSSet of UITouch objects. UITouch has a -locationInView: instance method which should yield the position of the tap in your view.
You could create an initial star and just move it every time view is touched.
I'm not sure what you're end result will look like.
Note:
This code will give you 1 star that moves with a tap
Here is my code:-
(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSSet *allTouches = [event allTouches];
switch ([allTouches count]) {
case 1:
{
UITouch *touch = [[allTouches allObjects] objectAtIndex:0];
CGPoint point = [touch locationInView:myView];
myStar.center = point;
break;
}
default:
break;
}
}
It seems implied from the question that you want the user to be able to tap anywhere on the screen and have an image drawn where they tap? As opposed to tapping in a designated place and having the image appear there?
If so, you're probably going to have to go with a custom view. In that case, you'd do something like the following:
Create a subclass of UIView.
Override the touchesBegan method. Call [[touches anyObject] locationInView:self] (where touches is the first argument to the method, an NSSet of UITouch objects) to get the location of the touch, and record it.
Override the touchesEnded method. Determine the location touches ended at using the same method as in step 2.
If the second location is near the first, you'll want to place your image at that location. Record that location and call [self setNeedsDisplay] to cause the custom view to be redrawn.
Override the drawRect method. Here, if the location has been set in step 4, you can use the UIImage method drawAtPoint to draw your image at the selected location.
For further details, this link might be worth a look. Hope that helps!
EDIT: I've notice you've asked essentially the same question before. If you're not happy with the answers given there, it's generally considered better to "bump" the old one, perhaps by editing it to ask for further clarification, rather than create a new question.
EDIT: As requested, some very brief sample code follows. This is probably not the best code around, and I haven't tested it, so it may be a little iffy. Just for clarification, the THRESHOLD allows the user to move their finger a little while tapping (up to 3px), because it's very difficult to tap without moving your finger a little.
MyView.h
#define THRESHOLD 3*3
#interface MyView : UIView
{
CGPoint touchPoint;
CGPoint drawPoint;
UIImage theImage;
}
#end
MyView.m
#implementation MyView
- (id) initWithFrame:(CGRect) newFrame
{
if (self = [super initWithFrame:newFrame])
{
touchPoint = CGPointZero;
drawPoint = CGPointMake(-1, -1);
theImage = [[UIImage imageNamed:#"myImage.png"] retain];
}
return self;
}
- (void) dealloc
{
[theImage release];
[super dealloc];
}
- (void) drawRect:(CGRect) rect
{
if (drawPoint.x > -1 && drawPoint.y > -1)
[theImage drawAtPoint:drawPoint];
}
- (void) touchesBegan:(NSSet*) touches withEvent:(UIEvent*) event
{
touchPoint = [[touches anyObject] locationInView:self];
}
- (void) touchesEnded:(NSSet*) touches withEvent:(UIEvent*) event
{
CGPoint point = [[touches anyObject] locationInView:self];
CGFloat dx = point.x - touchPoint.x, dy = point.y - touchPoint.y;
if (dx + dy < THRESHOLD)
{
drawPoint = point;
[self setNeedsDisplay];
}
}
#end
Please can someone help sort a noob out? I've posted this problem on various forums and received no answers, while many searches for other answers have turned up stackOverflow, so I'm hoping this is the place.
I've got a BeachView.h (subclass of UIScrollView, picture of a sandy beach) covered with a random number of Stone.h (subclass of UIImageView, a random PNG of a stone, userInteractionEnabled = YES to accept touches).
If the user touches and moves on the beach, it should scroll.
If the user taps a stone, it should call method "touchedStone".
If the user taps the beach where there is no stone, it should call method "touchedBeach".
Now, I realize this sounds dead simple. Everyone and everything tells me that if there's something on a UIScrollView that accepts touches that it should pass control on to it. So when I touch and drag, it should scroll; but if I tap, and it's on a stone, it should ignore beach taps and accept stone taps, yes?
However, it seems that both views are accepting the tap and calling both touchedStone AND touchedBeach. Furthermore, the beach tap occurs first, so I can't even put in a "if touchedStone then don't run touchedBeach" type flag.
Here's some code.
On BeachView.m
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.decelerating) { didScroll = YES; }
else { didScroll = NO; }
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchLocation = [touch locationInView:touch.view];
NSLog(#"touched beach = %#", [touch view]);
lastTouch = touchLocation;
[super touchesBegan:touches withEvent:event];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
didScroll = YES;
[super touchesMoved:touches withEvent:event];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if (didScroll == NO && isPaused == NO) {
[self touchedBeach:YES location:lastTouch];
}
[super touchesEnded:touches withEvent:event];
}
On Stone.m
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[parent stoneWasTouched]; // parent = ivar pointing from stone to beachview
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchLocation = [touch locationInView:touch.view];
NSLog(#"touched stone = %#", [touch view]);
[parent touchedStone:YES location:touchLocation];
}
After a stone tap, My NSLog looks like this:
Touched beach = <BeachView: 0x1276a0>
ran touchedBeach
Touched Stone = <Stone: 0x1480c0>
ran touchedStone
So it's actually running both. What's even stranger is if I take the touchesBegan and touchesEnded out of Stone.m but leave userInteractionEnabled = YES, the beachView registers both touches itself, but returns the Stone as the view it touched (the second time).
Touched beach = <BeachView: 0x1276a0>
ran touchedBeach
Touched beach = <Stone: 0x1480c0>
ran touchedBeach
So PLEASE, I've been trying to sort this for days. How do I make it so a tapped stone calls only touchedStone and a tapped beach calls only touchedBeach? Where am I going wrong?
Is true, iPhone SDK 3.0 and up, don't pass touches to -touchesBegan: and -touchesEnded: **UIScrollview**subclass methods anymore. You can use the touchesShouldBegin and touchesShouldCancelInContentView methods that is not the same.
If you really want to get this touches, have one hack that allow this.
In your subclass of UIScrollView override the hitTest method like this:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *result = nil;
for (UIView *child in self.subviews)
if ([child pointInside:point withEvent:event])
if ((result = [child hitTest:point withEvent:event]) != nil)
break;
return result;
}
This will pass to you subclass this touches, however you can't cancel the touches to UIScrollView super class.
Prior to iPhone OS 3.0, the UIScrollView's hitTest:withEvent: method always returns self so that it receives the UIEvent directly, only forwarding it to the appropriate subview if and when it determines it's not related to scrolling or zooming.
I couldn't really comment on iPhone OS 3.0 as it's under NDA, but check your "iPhone SDK Release notes for iPhone OS 3.0 beta 5" :)
If you need to target pre-3.0, you could override hitTest:withEvent: in BeachView and set a flag to ignore the next beach touch if the CGPoint is actually in a stone.
But have you tried simply moving your calls to [super touches*:withEvent:] from the end of your overridden methods to the start? This might cause the stone tap to occur first.
I had a similar problem with a simpleView and it is added to a scrollView , and whenever I touched the simpleView , the scrollView used to get the touch and instead of the simpleView , the scrollView moved . To avoid this , I disabled the srcolling of the scrollView when the user touched the simpleView and otherwise the scrolling is enabled .
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *result = [super hitTest:point withEvent:event] ;
if (result == simpleView)
{
scrollView.scrollEnabled = NO ;
}
else
{
scrollView.scrollEnabled = YES ;
}
return result ;
}
This could be related to a bug in iOS7 please review my issue (bug report submitted)
UIScrollView subclass has changed behavior in iOS7