Why isn't my UIViewController responding to touches? - iphone

I have a UIViewController that contains a UIScrollView, which has a UIView inside of its contentview.
I have the following code that does not work, keyboard is not dismissed, why?:
#pragma mark -
#pragma mark Touch Events
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
if ([touch view] == scrollView || [touch view] == self.view)
{
[usernameTextField resignFirstResponder];
[passwordTextField resignFirstResponder];
}
}

touchesBegan:withEvent: is a UIView method, not a UIViewController method. What are you trying to achieve here? You should very seldom have a UI reaction to touchesBegan:. You probably mean to use UITapGestureRecognizer instead.
Make sure to use accessors (self.scrollView) rather than accessing your ivars directly. Direct ivar access is the #1 cause of memory management crashes.

Related

touches* events - SKScene vs. ViewController

I have a classic SKScene with some buttons (all made programmatically) and ViewController for that scene. Where should be touches events handled - in SKScene, or in ViewController. I need to switch to another scenes and another view controllers when touching different buttons via push segue. When I handle touches events in view controller, it returns me nil for SKNode touched. Here is my code in view controller (scene is its property):
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self.scene];
SKNode *node = [self.scene nodeAtPoint:location];
if ([node.name isEqual: #"campaign"]) {
CampaignViewController *levelViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"CampaignScene"];
[self.navigationController pushViewController:levelViewController animated:NO];
}
}
Thank you for explaining.
Implementing touch delegates in the ViewController cannot possibly get you the nodes, as it is the SKScene which manages them. Hence, to be able to use nodeAtPoint:, you need to implement the touch delegates in the SKScene itself.
Now, you also need a way for the SKScene to communicate with the UIViewController, and pass messages which will trigger segues or other methods. For this, you can either use Delegation or NSNotificationCenter, the implementation of which is demonstrated in this answer.
After you have implemented either of the options from the answer, your code should look like the following:
//In ViewController.m
-(void) presentCampaignVieController
{
CampaignViewController *levelViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"CampaignScene"];
[self.navigationController pushViewController:levelViewController animated:NO];
}
//In SKScene.m (Using Delegation)
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self.scene];
SKNode *node = [self.scene nodeAtPoint:location];
if ([node.name isEqual: #"campaign"]) {
[self.delegate presentCampaignVieController];
}
}
In order to call the same method in the viewController using NSNotificationCenter, you will first have to add an observer:
//ViewController.m, under viewDidLoad
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(presentCampaignVieController) name:#"gotoCampaign" object:nil];
//In SKScene.m (Using NSNotificationCenter)
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self.scene];
SKNode *node = [self.scene nodeAtPoint:location];
if ([node.name isEqual: #"campaign"])
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"gotoCampaign" object:nil];
}
}

UIGestureRecognizer sample code

I cannot figure out what is wrong. Below is my code, and it calls the delegate methods once then stops.
What should I do? I haven;t been able to find the sample code that uses these delegate methods. All I've found were gesture recognizers for swipes and taps, using different delegates.
Code so far:
-(void)initTouchesRecognizer{
DLog(#"");
recognizer = [[UIGestureRecognizer alloc] init];
[self addGestureRecognizer:recognizer];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
DLog(#"");
NSSet *allTouches = [event allTouches];
for (UITouch *touch in allTouches)
{
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:touch.view];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
DLog(#"");
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[self touchesEnded:touches withEvent:event];
}
I call initTouchesRecognizer from initwithrect for my image view.
What am i doing fundamentally wrong?
UIGestureRecognizer is an abstract class, you're not supposed to add it directly to your view. You need to use a concrete subclass that inherits from UIGestureRecognizer, like UITapGestureRecognizer or UIPanGestureRecognizer for example. You could also make your own concrete subclass but that usually isn't necessary.
Here is an example of adding a UIPanGestureRecognizer to your view (in your view class code, often the gesture is added to the view from the controller):
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(mySelector:)];
[self addGestureRecognizer:panGesture];
In this case, the selector will be called when ever the user pans in this view. If you added a UITapGestureRecognizer, the selector would be called when the user tapped.
You can check out the apple docs for more info:
http://developer.apple.com/library/ios/#documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/GestureRecognizer_basics/GestureRecognizer_basics.html#//apple_ref/doc/uid/TP40009541-CH2-SW2
Also, I find Paul Hagerty's Stanford lectures to be great, here's one on gesture recognizers:
https://itunes.apple.com/ca/course/6.-views-gestures-january/id593208016?i=132123597&mt=2
You should also understand that none of the methods that you posted are delegate methods, and none of them have anything to do with the UIGestureRecognizer that you added in your code. Those are instance methods of UIResponder (a class that UIView inherits from) that you're overriding. The abstract UIGestureRecognizer also has instance methods with those same names, but it is not the UIGestureRecognizer methods that are getting called in your class.
There was no need to add the gesture recognizer. By overriding the touchesMoved, touchesEnded and touchesBegan methods, I was able to track the user's finger across the screen.
simply do not call the:
-(void)initTouchesRecognizer
code, and the code I originally posted will work.

Resigning keyboard on touch

I've found some code that helps me resign the keyboard when a user touches the screen off of the UITextView element.
Here's how it looks:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [[event allTouches] anyObject];
if([self.speechBubble.speechText isFirstResponder] && [touch view] != self.speechBubble.speechText){
[self.speechBubble.speechText resignFirstResponder];
}
[super touchesBegan:touches withEvent:event];
}
This works perfectly so far, and will remove the keyboard if a user touches anywhere outside of the text view. However, it only works for the particular object that I'm running it for, so if I have two speechBubbles, it won't work.
How can I change this so that ANY speechBubble will have the same effect? (I could move this code from my ViewController to my SpeechBubble class, but I'd have a little issue with how to use [touch view] to get touches outside of the speechBubble's view. ) Thanks
Something I just discovered recently that may be of use to you is:
[self.view endEditing:YES];
It will resign first responder from any element that currently has it without you having to manually keep track of it yourself.
In reference to your example code, something like this might work, depending on how your speechBubbles work:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [[event allTouches] anyObject];
// Note the '!':
if(![[touch view] class] isKindOfClass [speechBubble class]]){
// It's not a bubble they touched, dismiss the keyboard:
[self.view endEditing:YES];
}
[super touchesBegan:touches withEvent:event];
}

Problem capturing single/double taps inside a ScrollView

I'm posting this message because I've been reading the forum and I haven't been able to find a similar problem. I need to be able to discriminate taps and double taps (this is a standard thing) BUT my problem is that for whatever reasons I have a Scroll View inside another ScrollView. So, I had to sub-class my ScrollView in order to get touchedBegin method called.
I have a class called PhotoViewController (a sub-class of BaseViewController) this class contains another class called CustomScrollView (a subclass of ScrollView). I needed to sub-class this CustomScrollView from ScrollView in order to override the touchesBegin method, and to be able to capture the touches made by the user.
I tried calling the touchesBegin method from CustomScrollView using something like return [super touchesBegan:touches withEvent:event] inside the touchesBegin method, but when the touchesBegin method inside PhotoViewController gets called it's parameters are empty (and I can't discriminate if the user made a single or double tap, which is exactly what I need)
I have a class, called PhotoViewController:
#class PhotoViewController
#interface PhotoViewController : BaseViewController <UIScrollViewDelegate> {
CustomScrollView* myScrollView;
}
#implementation PhotoViewController
...
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
NSUInteger tapCount = [touch tapCount];
switch (tapCount) {
case 1:
[self performSelector:#selector(singleTapMethod) withObject:nil afterDelay:.4];
break;
case 2:
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(singleTapMethod) object:nil];
[self performSelector:#selector(doubleTapMethod) withObject:nil afterDelay:.4];
break;
default:
break;
}
}
the class CustomScrollView is (CustomScrollView.h):
#interface CustomScrollViewPhoto : UIScrollView {
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
#end
and it's implementation is this(CustomScrollView.m):
#implementation CustomScrollViewPhoto
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.superview touchesBegan:[NSSet set] withEvent:event];
return [super touchesBegan:touches withEvent:event];
}
Am I going in the wrong direction with what I want to do? Maybe, I should capture the taps/double taps inside the CustomScrollView class(this works fine!), and from there using a #selector or something call the appropiate methods in PhotoViewController?
Thanks for reading!
I think you're going the wrong route (only slightly!). I do a very similar thing in a photo viewer and I capture the touches in the CustomScrollView. You shouldn't need to do anything in PhotoViewController.
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
UITouch *touch = [touches anyObject];
if(touch.tapCount == 2)
{
if (self.zoomScale == self.minimumZoomScale)
{
//Zoom where the user has clicked from
CGPoint pos = [touch locationInView:self];
[self zoomToRect:CGRectMake((pos.x - 5.0)/self.zoomScale, (pos.y-5.0)/self.zoomScale, 10.0, 10.0) animated:YES];
}
else
{
//Zoom back out to full size
[self setZoomScale:self.minimumZoomScale animated:YES];
}
}
}

detecting the [UITouch view] in touchesMoved method

I wish to drag from one subview to another within my application (and connect them with a line), and so I need to keep track of the current view being touched by the user.
I thought that I could achieve this by simply calling [UITouch view] in the touchesMoved method, but after a quick look at the docs I've found out that [UITouch view] only returns the view in which the initial touch occurred.
Does anyone know how I can detect the view being touched while the drag is in progress?
And my way:
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
if ([self pointInside:[touch locationInView:self] withEvent:event]) {
[self sendActionsForControlEvents:UIControlEventTouchUpInside];
} else {
[self sendActionsForControlEvents:UIControlEventTouchUpOutside];
}
}
After a bit more research I found the answer.
Originally, I was checking for the view like so:
if([[touch view] isKindOfClass:[MyView* class]])
{
//hurray.
}
But, as explained in my question, the [touch view] returns the view in which the original touch occurred. This can be solved by replacing the code above with the following:
if([[self hitTest:[touch locationInView:self] withEvent:event] isKindOfClass:[MyView class]])
{
//hurrraaay! :)
}
Hope this helps
UIView is subclass of UIResponder. So you can override touches ended/ began methods in your custom class, that inherits from UIView. Than you should add delegate to that class and define protocol for it. In the methods
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
or whatever interactions you need just send appropriate message to object's delegate also passing this object. E.g.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[delegate touchesBeganInView: self withEvent: event];
}