Defining maximum variance of an UITextView - iphone

If you have a normal UITextView with loads of text in it you can swipe up and down:
if you swipe left or right in a very horizontal line, nothing will happen
if you swipe left or right with a very slight angle, you will swipe up or down
So there is obviously some kind of class definition which tells the UITextView when to react to a swipe and when not to. Something like a maximumVariance constant, I guess.
My problem is that I'm trying to subclass an UITextView to enable left/right swipe. However, you will need to swipe in a pretty straight line, otherwise, you'll get an up or down swipe. So my question is if there is someway to re-define the standard variance variable of an UITextView?
Here is my subclass .m file. Note that the kMaximumVariance doesn't really have any effect as this is overwritten by whatever variance the standard UITextView class provides:
//
// SwipeTextView.m
// Swipes
//
#import "SwipeTextView.h"
#import "PageView.h"
#define kMinimumGestureLength 15
#define kMaximumVariance 100
#implementation SwipeTextView
#synthesize delegate, gestureStartPoint;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
UITouch *touch =[touches anyObject];
gestureStartPoint = [touch locationInView:self];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesMoved:touches withEvent:event];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
[self resignFirstResponder]; // this hides keyboard if horizonal swipe
UITouch *touch = [touches anyObject];
CGPoint currentPosition = [touch locationInView:self];
CGFloat deltaXX = (gestureStartPoint.x - currentPosition.x); // positive = left, negative = right
CGFloat deltaYY = (gestureStartPoint.y - currentPosition.y); // positive = up, negative = down
CGFloat deltaX = fabsf(gestureStartPoint.x - currentPosition.x); // will always be positive
CGFloat deltaY = fabsf(gestureStartPoint.y - currentPosition.y); // will always be positive
NSLog(#"deltaXX (%.1f) deltaYY (%.1f) deltaX (%.1f) deltaY (%.1f)",deltaXX, deltaYY, deltaX, deltaY);
if (deltaX >= kMinimumGestureLength && deltaY <= kMaximumVariance) {
if (deltaXX > 0) {
NSLog(#"Horizontal Left swipe - FINISHED detected");
}
else {
NSLog(#"Horizontal Right swipe - FINISHED detected");
}
}
}
#end
EDIT:
Suggestion to cancel touches reaching UITextView (see below):
// Prevent recognizing touches
- (BOOL)gestureRecognizer:(UISwipeGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if ([gestureRecognizer direction] == UISwipeGestureRecognizerDirectionLeft) {
// Left swipe: I want to handle this
return YES;
}
if ([gestureRecognizer direction] == UISwipeGestureRecognizerDirectionRight) {
// Right swipe: I want to handle this
return YES;
}
if ([gestureRecognizer direction] == UISwipeGestureRecognizerDirectionUp) {
// Up swipe: textView needs to handle this
return NO;
}
if ([gestureRecognizer direction] == UISwipeGestureRecognizerDirectionDown) {
// Down swipe: textView needs to handle this
return NO;
} else
return YES;
}
-(void)viewDidLoad
{
UITapGestureRecognizer *GesRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(doSomething:)];
[GesRec setCancelsTouchesInView:NO];
[self addGestureRecognizer:GesRec];
[GesRec release];
}
-(void)doSomething
{
NSLog(#"doSomething");
}

Try adding an UISwipeGestureRecognizer to the view and set cancelsTouchesInView to false so the swipe gesture is not passed through. This should let you detect left and right motion more accurately.
If this doesn't do the trick, implement your own subclass of UIGestureRecognizer and add that instead.
Edit
You add the gesture recognizer to the UITextView in your controller's viewDidLoad method.
In your delegate methods, you can't return NO in gestureRecognizer:shouldReceiveTouch: because it means the gesture recognizer won't examine the incoming touch object. In your case you want to return YES when the swipe is horizontal and NO otherwise, so vertical swipes are passed through while horizontal swipe is completed handled by you.

No subclass needed. set textView.userInteractionEnabled to NO and than put a transparent UIView over your textView. handle touches and gestures anyway you want on that UIView and make the textView respond to these programatically.

Related

Passing touch to next responder or other sub-view iOS

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.

Swipe Gesture Recognition Inside UIWebView

I have read a bunch of questions on this but none of them seem to achieve what I am looking for... So lets say I have an arbitrary UIWebView inside of a UIViewController. The UIViewController has a SwipeGestureRecognizer that works fine. It even works within the UIWebView-- whenever there is no scrollbar. (Before I load a page or even if I load a page that can fit appropriately within the size of my UIWebView). However, if I load a webpage that requires horizontal scrolling left or right, then inside the UIWebView part of my view, I cannot get any swipe gestures to be recognized. Every click/drag/swipe just triggers the scroll action. Is there a way to differentiate between a "swipe" and just scrolling with your finger (not lifting it but rather dragging to scroll).
Yes, you can tell the UIWebView's UIScrollView that its UIPanGestureRecognizer should only fire when your own UISwipeGestureRecognizer has failed.
This is how you do it:
UISwipeGestureRecognizer *rightSwipeGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleSwipeGesture:)];
UISwipeGestureRecognizer *leftSwipeGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleSwipeGesture:)];
rightSwipeGesture.direction = UISwipeGestureRecognizerDirectionRight;
leftSwipeGesture.direction = UISwipeGestureRecognizerDirectionLeft;
[self.view addGestureRecognizer:rightSwipeGesture];
[self.view addGestureRecognizer:leftSwipeGesture];
[_webView.scrollView.panGestureRecognizer requireGestureRecognizerToFail:rightSwipeGesture];
[_webView.scrollView.panGestureRecognizer requireGestureRecognizerToFail:leftSwipeGesture];
That should do the trick for you.
You will have to subclass UIWebView and override the gesture recogniser calls.
EDIT - Look at this post Handling touches inside UIWebview and this link http://mithin.in/2009/08/26/detecting-taps-and-events-on-uiwebview-the-right-way/
Johannes Fahrenkrug's answer succeeded in conditionally blocking the webView's built-in pan gestures. However, I found that this only worked if the webView's pan gestures were very slow ... if I panned the webView with any reasonable speed, the swipe gesture was triggered. I wanted only a fast swipe to trigger the swipe gesture, and medium or slow pans to use the default webView scrolling functionality.
The UISwipeGestureRecognizer has no properties for customizing the speed of a swipe, and the UIPanGestureRecognizer has a velocity property but no way to set a required velocity, so I set up a custom gesture recognizer based on this tutorial:
FastSwipeGestureRecognizer.h
#import <UIKit/UIKit.h>
#import <UIKit/UIGestureRecognizerSubclass.h>
#define REQUIRED_TOUCHES 5
#define REQUIRED_STRAIGHTNESS 3
#define REQUIRED_TIME .1
typedef enum {
DirectionUp = 0,
DirectionRight,
DirectionDown,
DirectionLeft
} Direction;
#interface FastSwipeGestureRecognizer : UIGestureRecognizer {
CGPoint firstTouchLocation;
NSTimeInterval firstTouchTime;
int touchesCount;
Direction direction;
}
#property (nonatomic) CGPoint firstTouchLocation;
#property (nonatomic) NSTimeInterval firstTouchTime;
#property (nonatomic) int touchesCount;
#property (nonatomic) Direction direction;
#end
FastSwipeGestureRecognizer.m
#import "FastSwipeGestureRecognizer.h"
#implementation FastSwipeGestureRecognizer
#synthesize firstTouchLocation;
#synthesize firstTouchTime;
#synthesize touchesCount;
-(void)reset {
[super reset];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
self.firstTouchLocation = [[touches anyObject] locationInView:self.view];
self.firstTouchTime = [NSDate timeIntervalSinceReferenceDate];
self.touchesCount = 1;
self.state = UIGestureRecognizerStatePossible;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesMoved:touches withEvent:event];
self.touchesCount++;
if (self.touchesCount > REQUIRED_TOUCHES) { // wait until we have a few touches before we evaluate the gesture
CGPoint thisTouchLocation = [[touches anyObject] locationInView:self.view];
float horizontalRatio = (ABS(thisTouchLocation.x - self.firstTouchLocation.x) / ABS(thisTouchLocation.y - self.firstTouchLocation.y));
float verticalRatio = 1 / horizontalRatio;
NSTimeInterval elapsedTime = [NSDate timeIntervalSinceReferenceDate] - self.firstTouchTime;
NSLog(#"swipe? %f, %f, %f", verticalRatio, horizontalRatio, elapsedTime);
// if we're moving straight enough and fast enough, complete the gesture
if (((horizontalRatio > REQUIRED_STRAIGHTNESS)||(verticalRatio > REQUIRED_STRAIGHTNESS))&&(elapsedTime < REQUIRED_TIME)) {
if (horizontalRatio > REQUIRED_STRAIGHTNESS) {
self.direction = (thisTouchLocation.x > self.firstTouchLocation.x) ? DirectionRight : DirectionLeft ;
} else if (verticalRatio > REQUIRED_STRAIGHTNESS) {
self.direction = (thisTouchLocation.y > self.firstTouchLocation.y) ? DirectionDown : DirectionUp ;
}
self.state = UIGestureRecognizerStateRecognized;
} else {
self.state = UIGestureRecognizerStateFailed;
}
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
if (self.touchesCount < REQUIRED_TOUCHES) {
self.state = UIGestureRecognizerStateFailed;
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesCancelled:touches withEvent:event];
self.state = UIGestureRecognizerStateFailed;
}
#end
Set up your gestures
FastSwipeGestureRecognizer *swipeGesture = [[FastSwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleSwipeGesture:)];
[self.view addGestureRecognizer:swipeGesture];
[self.webView.scrollView.panGestureRecognizer requireGestureRecognizerToFail:swipeGesture];
Then detect the direction of the received gesture
- (void)handleSwipeGesture:(FastSwipeGestureRecognizer *)gesture {
if (gesture.state == UIGestureRecognizerStateEnded) {
if (gesture.direction == DirectionRight) {
// do something
} else if (gesture.direction == DirectionLeft) {
// do something
} else if (gesture.direction == DirectionUp) {
// do something
} else if (gesture.direction == DirectionDown) {
// do something
}
}
}
Note that this only requires one gesture to handle all four swipe directions, instead of one UISwipeGestureRecognizer per direction.

Custom UISlider: avoid updating when dragging outside

I'm quite new to iPhone development and I'm building my first app :)
In one of my view controllers I built a customSlider that should behave as the native "slide to unlock" slider.
My doubt right now is how to implement the "drag outside" behaviour. As said, I would like to have it exactly as the native slider, that means that when the finger is dragging the slider if it moves out the slider, the slider should be going to zero.
My doubt is not on the animation part (I'm already using animation block successfully), but on control events part. Which control event should I use?
I'm using:
[customSlider addTarget:self action:#selector(sliderMoved:) forControlEvents:UIControlEventValueChanged];
to handle the sliding part (finger sliding the cursor), and
[customSlider addTarget:self action:#selector(sliderAction:) forControlEvents:UIControlEventTouchUpInside];
to handle the release part, but the problem is that if I release the finger outside, the sliderAction function it's not called.
EDIT:
I've tried to implement the solution #Bruno Domingues gave me, but I'm realizing that the issue is that by default UISlider keep getting updated even if the finger is dragged outside of it (try to open for example the Brightness section in System Preferences and you'll see that the slider will keep on updating even if you drag outside of it). So my question could be redefined: How to avoid this default behaviour and have the slider updating only when the finger is moving on it?
Simply interrupt the touches methods in your custom subclass and only forward the touches you want acted on to the superclass, like so:
in .h:
#interface CustomSlider : UISlider
#end
in .m:
#import "CustomSlider.h"
#implementation CustomSlider
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint touchLocation = [[touches anyObject] locationInView:self];
if (touchLocation.x < 0 || touchLocation.y<0)return;
if (touchLocation.x > self.bounds.size.width || touchLocation.y > self.bounds.size.height)return;
[super touchesBegan:touches withEvent:event];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint touchLocation = [[touches anyObject] locationInView:self];
if (touchLocation.x < 0 || touchLocation.y<0)return;
if (touchLocation.x > self.bounds.size.width || touchLocation.y > self.bounds.size.height)return;
[super touchesMoved:touches withEvent:event];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint touchLocation = [[touches anyObject] locationInView:self];
if (touchLocation.x < 0 || touchLocation.y<0)return;
if (touchLocation.x > self.bounds.size.width || touchLocation.y > self.bounds.size.height)return;
[super touchesEnded:touches withEvent:event];
}
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint touchLocation = [[touches anyObject] locationInView:self];
if (touchLocation.x < 0 || touchLocation.y<0)return;
if (touchLocation.x > self.bounds.size.width || touchLocation.y > self.bounds.size.height)return;
[super touchesCancelled:touches withEvent:event];
}
#end
Please note that this implementation will start updating the control if your finger moves back to the control. To eliminate this, simply set a flag if a touch is received outside of the view then check that flag in subsequent touches methods.
You need to set a [slider addTarget:self action:#selector(eventDragOutside:) forControlEvents:UIControlEventTouchDragOutside];
And add a function
- (IBAction)eventDragOutside:(UISlider *)sender {
sender.value = 0.0f;
}

removing subview from superview when user moves very fast

how to remove subviews very fast according to user touched point or moving point in the view.I am able to get the user touched point and moved point in the view and also i can remove the subview according with the user touched point using [subview removefromsuperview];.
When the user moves very fast some subviews getting deleted and some of the images not getting deleted.If the user moves slowly then subviews getting deleted exactly.i am placing some of my code is below
`
-(void)deleteSubView:(CGPoint)userCurrentPoint{
for(int i=0;i<[subviews count];i++){
if (CGRectContainsPoint([[subviews objectAtIndex:i] CGRectValue], userCurrentPoint)) {
[[superview viewWithTag:i ] removeFromSuperview];
}
}
`
is there any way to delete subviews according with the user fast moving points.i need your suggestion.
Thanks all
Put this in your header file:
#interface YourView : UIView
#property (nonatomic, assign) CGPoint lastTouchLocation;
#end
This should be the implementation:
#implementation YourView
#synthesize lastTouchLocation;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
self.lastTouchLocation = [[touches anyObject] locationInView:self];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
CGPoint oldLocation = self.lastTouchLocation;
CGPoint newLocation = [[touches anyObject] locationInView:self];
for (UIView *subview in self.subviews)
{
if (LineIntersectsRect(oldLocation, newLocation, subview.frame))
{
[subview removeFromSuperview];
}
}
self.lastTouchLocation = newLocation;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
self.lastTouchLocation = CGPointZero;
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesCancelled:touches withEvent:event];
self.lastTouchLocation = CGPointZero;
}
#end
What this does: we check whether or not a line between the last received touch and the current one intersects the frame of a subview. Why? See deanWombourne's answer.
Here's the intersection code, originally by habjan - you should put in in the header, too.
static inline BOOL LineIntersectsLine(CGPoint l1p1, CGPoint l1p2, CGPoint l2p1, CGPoint l2p2)
{
CGFloat q = (l1p1.y - l2p1.y) * (l2p2.x - l2p1.x) - (l1p1.x - l2p1.x) * (l2p2.y - l2p1.y);
CGFloat d = (l1p2.x - l1p1.x) * (l2p2.y - l2p1.y) - (l1p2.y - l1p1.y) * (l2p2.x - l2p1.x);
if(d == 0)
{
return false;
}
CGFloat r = (q / d);
q = (l1p1.y - l2p1.y) * (l1p2.x - l1p1.x) - (l1p1.x - l2p1.x) * (l1p2.y - l1p1.y);
CGFloat s = (q / d);
if((r < 0) || (r > 1) || (s < 0) || (s > 1))
{
return false;
}
else
{
return true;
}
}
static inline BOOL LineIntersectsRect(CGPoint p1, CGPoint p2, CGRect r)
{
if (CGRectContainsPoint(r, p1) || CGRectContainsPoint(r, p2))
{
return YES;
}
else
{
CGPoint topLeft = CGPointMake(r.origin.x, r.origin.y);
CGPoint topRight = CGPointMake(r.origin.x + r.size.width, r.origin.y);
CGPoint bottomLeft = CGPointMake(r.origin.x, r.origin.y + r.size.height);
CGPoint bottomRight = CGPointMake(r.origin.x + r.size.width, r.origin.y + r.size.height);
return (LineIntersectsLine(p1, p2, topLeft, topRight) ||
LineIntersectsLine(p1, p2, topRight, bottomRight) ||
LineIntersectsLine(p1, p2, bottomRight, bottomLeft) ||
LineIntersectsLine(p1, p2, bottomLeft, topLeft));
}
}
It's not to do with your enumeration :)
If the user moves their finger quickly, you don't get a list of every pixel they touched, you get some of them. If they were to move fast enough, you might only get the start and end points.
You can't just use the point that their finger is dragged to, you have to work out which subviews have been touched between the last event and this one :)
Think of it as drawing a line between the start and end points and working out which subviews overlap with that line.
If I understand your question correctly, you want to remove all the subviews that the user touches as he drags his finger across your view.
Your problem is that touchesMoved:withEvent: fires at a limited frequency. It isn't guaranteed to fire for every pixel the user touches. As a result, when the user moves his finger quickly, there are gaps between the points reported in touchesMoved:withEvent. If the user moves his finger quickly enough, these gaps may be large enough that they skip over some subviews entirely.
To solve this problem your code needs to remember the position of the previous point, and then test the line segment formed by the previous point and the current point to see if it intersects with the frame of the subview. An efficient algorithm for this is described in: How to test if a line segment intersects an axis-aligned rectange in 2D?

UIScrollView touches vs subview touches

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