UIView touch handling behavior changed with Xcode 4.2? - ios

I upgraded my iPad to 5.0 a couple days ago, and upgraded Xcode to 4.2 at the same time so I could continue to test my apps. Now I am having problems with touch code in several apps that worked with previous versions of Xcode.
I subclassed UIImageView to add some dragging features by overriding -(void)TouchesBegan and -(void)TouchesMoved. I did not override -(void)TouchesEnded in the subclass, but handled that in the view controller for the view that contains the image view.
I pulled the subclassed UIImageView into a new project for testing, and have narrowed down the issue to the fact that the parent UIView (the template created by Xcode) does not seem to be forwarding touch events to the view controller (also created by Xcode).
If I add this to my subclass:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"touches ended event in ImageToDrag");
[self.nextResponder touchesEnded:touches withEvent:event];
}
and this to my parent view's view controller:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"touches ended event in ViewController");
}
when I let go of the image I am dragging around the screen, I get the "touches ended event in ImageToDrag", but not the log from the view controller.
However, if I intentionally skip over the view by doing this in the subclassed view:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"touches ended event in ImageToDrag");
[[self.nextResponder nextResponder] touchesEnded:touches withEvent:event];
}
then I get both log entries.
The only explanation I can come up with is that for some reason, UIView is consuming the touchesEnded event and not passing it to the view controller.
I have verified that exclusiveTouch is set to NO, and userInteractionEnabled is set to YES on the parent view and the subclassed UIImageView.
I have also tested compiling for iOS 5.0 and iOS 4.2, and deploying the test app to both an iOS 5 iPad and iOS 4.3.1 iPad.
The only way I have been able to get the touch event to the viewController is by skipping over the view and using the double nextResponder in the subclass. Although that method functions, it seems like a hack to me and I'm sure it will come back to bite me later.
Has anybody else seen this behavior? Is there any way for me to find out what the UIView is doing with my touch events?
Thanks,
Dan

I've been trying to track down the a similar issue for the last few hours. Finally managed to solve it with the help of this post
Actually it looks like I just managed to solve it, using the hint from
https://devforums.apple.com/message/519690#519690
Earlier, I just
forwarded the touchesEnded event to self.nextResponder. When I added
touchesBegan, Moved, Cancelled handlers with similar implementations
as the touchesEnded, the event seems to bubble up to the root view
controller.
So I guess on iOS5, views discard touchesEnded events
when they have not seen the relevant touchesBegan.
I didn't need to add Moved/etc., I just forwarded on TouchesBegan, and then TouchesEnded start working again!

Some touch handling did chance in iOS 5.0; especially if you re-link your application against the 5.0 SDK.
There's a section UIView's touch handling methods that says this:
If you override this method without calling super (a common use pattern), you must also override the other methods for handling touch events, if only as stub (empy) implementations.
So if you do one, you need to do them all. I know UIKit started taking steps to make sure this was the case in 5.0.
So I'd start there - override all the methods on your view and see what happens.

Related

iOS UISlider values mismatched

I have implemented Vertical Slider in one of my App which extends UISlider. when scrolling is ended/done I am sending commands to the server with the Slider values. Sometimes when I scroll fast up/down and release then slider values are getting mismatched before sending command and after sending command.
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"Value of Slider before sending command=%f",self.value);
[self sendCommand]; // Here value is something else
[super touchesEnded:touches withEvent:event];
NSLog(#"Slider value after sending command=%f",self.value); // Here value changed
}
But if I place super call before sending command then everything works fine. Please explain if anyone knows why this is happening.
[super touchesEnded:touches withEvent:event];
More interesting fact is if I don't call super then also everything works well.
This is because the method [super touchesEnded:touches withEvent:event]; is where the slider updates it's value, based on the interaction. So the value is out of date before super is called.
A call to super should usually be the first line of an overridden method.
Even though the name touchesEnded: implies that the slider is done updating, it still might update the value depending on how the user lifted up their finger. This makes sense if you think about a user sliding quickly and then lifting up their finger—they probably expect it to go slightly farther in that direction than their finger actually went.
UIKit calls this method when a finger or Apple Pencil is no longer
touching the screen. Many UIKit classes override this method and use
it to clean up state involved in the handling of the corresponding
touch events. The default implementation of this method forwards the
message up the responder chain. When creating your own subclasses,
call super to forward any events that you do not handle yourself. For
example, [super touchesEnded:touches withEvent:event];
If you override this method without calling super (a common use
pattern), you must also override the other methods for handling touch
events, even if your implementations do nothing.

Handling a touch event across multiple subviews

I am new to iOS development. I have a custom drawn view which is composed of multiple subviews covering the target area on screen. Specifically this is a board game like chess where I used a view for each square. The squares are created as subviews on a UIView. There is one UIViewController for this. From what I read, I have to have touchesBegan, touchesEnded etc calls in my UIView to handle these. But none of these functions are getting called. I added these calls on the base view and all the subviews. So -
How do I simulate these touch events in the iOS simulator? A mouse click is not calling the touchesBegan ,touchesEnded calls on any view.
Ideally I would like to handle these in the UIViewController because I want to run the touch through some logic. Is that possible? How do I achieve it?
Please refer THIS
It is tutorial in Apple sample codes it describes how to handle touches very nicely.
Just run the sample code and go through description you will get clear idea how touches work un iOS.
Turns out when I add the view programmatically and not through the storyboard the userInteractionEnabled variable is set to NO by default. After setting it up, I get the touchesEnabled call getting called in the view.
Check this :
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if(![touch.view isKindOfClass:[yourView class]])
{
}
}
Hope this will help.

Need to block all touches except for specific one in an overlay view for iOS

I am trying to set up a tutorial type class that presents an overlay view and requires actions from the user before continuing. I currently have a hierarchy set up as follows.
UIWindow
|---- UIViewController
| |---- UIViewA (View performing tutorial action on)
| |---- UIViewB
|
|---- UIViewT (tutorial overlay)
|---- CGRect (defined by UIViewA)
During the tutorial, views will get dragged around, new views will be created, etc, which is why I added the tutorial overlay view to the UIWindow. This way the I don't have to mess with the view hierarchy within the view controller as suggested in many places on SO. The purpose of the overlay window is to block all actions, except for the required action expected by the tutorial.
Currently the tutorial overlay view is implemented as follows
#interface ZSOverlayView : UIView <UIGestureRecognizerDelegate>
#property (nonatomic, assign) CGRect activeRegion;
#end
#implementation ZSOverlayView
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
return !CGRectContainsPoint(_activeRegion, point);
}
#end
Where activeRegion is the CGRect defined by UIViewA. This successfully blocks all unwanted events and gestures from making it through the overlay, outside of the activeRegion. In this specific case UIViewB does not get the event or gestures.
The problem is that I only want a single gesture to make it through, not all of them, for UIViewA. For example if UIViewA has a double tap, pan, and custom gesture, I may only want the double tap to be active at once, or perhaps the custom gesture to be active at once, or perhaps both. The tutorial doesn't know what gestures the view has, so it needs a generic way of passing along the needed ones, and blocking the ones that aren't. Currently none of the gestures are blocked. Even if I have flags in place, which I currently do, that determine what gestures should be able to make it through, I am still running into problems in how to block specific ones, and let others through.
I'm unsure how to proceed because the tutorial overlay is not the delegate of any of the gesture recognizers, nor do I want it to be because by taking over as the delegate the tutorial might remove special conditions specified by the existing delegates.
Any ideas how to proceed to get the functionality I'm looking for?
I don't really like the solution, but the best answer was given by Lyndsey Scott in the comments.
If I'm understanding correctly, you could set the UIGestureRecognizerDelegates then just use conditionals in the delegate methods to specify what to do when the gesture view is the tutorial window vs when the gesture view is the main window.
I would have preferred not to rely on this method since I was trying to have my tutorial library do all of the work but since there hasn't been an answer in a while, I just wanted to throw it out there that this worked.
Have you tried to just use -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event ?
If you're wanting to block touch events for something, you can just do it here like:
Prevent touch events for a view
Call a helper method to determine which view(s) can be touched
etc.
Edit: Better late then never. I actually ran into needing to do this (again...) and (yet again) found the answer I was referring to in the comments below =]
Anyways, using touchesBegan, you could just do this ( to obtain all gesture recognizers who are receiving the type of touch ( or touches ) you are looking for:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// In this example, I'm only actually looking for (and caring) about a single touch.
if( [touches count] == 1 ) {
UITouch *touch = (UITouch *)[touches anyObject];
// So here they are now
NSArray *gestureRecognizersForTouch = [touch.gestureRecognizers copy];
// ... whatever else.
}
}
At this point, you can either remove the recognizers, set a value to a property they all have access to in your object, submit a notification, etc. etc. etc.

Touch Event in Scrollview ios

Scrolling is not stopping when I touch over the contact labels. How can I add this feature for this open project.
https://www.cocoacontrols.com/controls/scroller
If I touch the background, it is working perfectly. I would like to have same thing for the contacts labels too.
Basically, it uses scrollview and there is an animation while scrolling. I can not make stop it when I touch over the labels.
Any help is welcome.
Though I am unfamiliar with the scroller project, maybe this can at least get you on the right path.
The likely reason why touching the contacts isn't stopping the scrolling is because the labels are receiving their own touch events for their own purpose, which is probably the desired behavior, since you would probably want to touch one of the contacts and have it do something. It's possible that since the touch events are being intercepted in that view for that reason, that you can not interact with the scroll view using the same event.
You may need to set the userInteractionEnabled property of the view surrounding each contact to false until the scrollview has stopped scrolling. There are several ways you could do this, but this might be enough to get you started on a good solution.
My condition may be similar with yours.
I build a scroll view in storyboard and a view is added to the scroll view.All of my UI component was placed in the content view including two textfields.Generally speaking, I would like to rewrite the - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event method, and end editing actions in this view.
However,rewrite the method in scrollview's superview has little help.But when I subclass the view and rewrite that method in this subclass Every thing is OK.
According to my condition ,subclass the view and rewrite - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event.Process the logic in view level.

Why Does The iPad Become Nonresponsive After UIGestureRecognizer is Added to UIWindow?

Background: I need to look at every UITouch that happens in an application and see if the UITouch is NOT inside a certain UIImageView.
WildcardGestureRecognizer looked very promising and I tried it. (Code is here):
How to intercept touches events on a MKMapView or UIWebView objects?
It worked great for a sandbox/quickly-created application. However that application didn't reflect the complexity that the actual target project has. The target project has a Table View Controller and more.
After adding the WildcardGestureRecognizer to the more involved iPad application, I see that none of the other controls work after the gesture recognizer is added and one click happens.
Here's some code where I was playing with the idea. Again, the sandbox code does not yet have controls on it (such as a Table View Controller or even a UIButton) to see if they work after adding the gesture recognizer to the UIWindow.
Should I pick something other than the UIWindow to add the gesture recognizer to or am I going to run into the same problem regardless? Is there a better way?
sandbox code: https://github.com/finneycanhelp/GestureKata
You might want to try another approach: create a UIWindow subclass, use that in your XIB and override hitTest:withEvent:. It returns the view that has been "selected" to receive the touch events.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *v;
v = [super hitTest:point withEvent:event];
if (v == myImageView) {
// Do something. Maybe return nil to prevent the touch from getting
// sent to the image view.
}
return v;
}
Overriding this method can also be helpful when debugging.

Resources