iOS - touchesMoved called only once in subclassed UIScrollView - objective-c

I am trying to drop movable UIImageViews within a subclassed UIScrollView so that I can drag them around my UIScrollView. I subclassed UISCrollView and the dropping behavior is working, but when I try to drag the images, touchesMoved is only evaluated once. The touchesMoved method in my subclass of UIScrollView looks like this:
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
if (!self.dragging) {
[self.nextResponder touchesMoved: touches withEvent:event];
}else{
[super touchesEnded:touches withEvent:event];
}
}
It is being called continuously during a moving touch, as it should. Can anyone think of a reason that the touchesMoved method in my view controller would only be called once?

IOS 5: UIScrollView not pass touches to nextResponder
Instead of:
[self.nextResponder touchesMoved:touches withEvent:event];
Use:
[[self.nextResponder nextResponder] touchesMoved:touches withEvent:event];

Related

Resend touch event to another view in iOS

I've got an UIImageView on top of (and not an element of) a UIScrollView. Well, I've subclassed the UIImageView to check for touch events and have overridden the touchesBegan:withEvent: method. Inside that method I basically send the subclassed UIImageView behind the UIScrollView element. (I use [self.super bringSubviewToFront: scrollView] to achieve this).
What I want is to be able to send the current touch event of the UIImageView to the UIScrollView element, so that the user doesn't have to lift his finger and touch again to scroll. I already tried to call touchesBegan:withEvent: on the UIScrollView element, but it did nothing.
Does anyone have an idea of what should be done so I could simulate the touch of the UIImageView to the UIScrollView element?
Here is the overridden method in the subclassed UIImageView:
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan: touches withEvent: event];
ViewController* controller = [Singleton getSingleton].controller;
[super bringSubviewToFront: controller.scrollView]; //Now scroll view is in the front
[controller.scrollView touchesBegan: touches withEvent: event]; //Does nothing
}
I need to override hitTest:withEvent: in the UIImageView subclass.
(UIView*) hitTest: (CGPoint)point withEvent: (UIEvent*)event
{
if([self pointInside:point withEvent:event]){
ViewController* controller = [Singleton getSingleton].controller;
[controller.view bringSubviewToFront: controller.scrollView];
return controller.scrollView;
}else{
return [super hitTest:point withEvent: event];
}
}
This way, when a touch on the UIImageView is issued, it would first call this method, which would bring the UIScrollView to front and then return the UIScrollView as the object which should handle the event.

How to stop the UIScrollView get touch events from its parent view?

I have a UIView containing a UIScrollView.
This UIView is supposed to respond to some touch events but it is not responding because of the scrollView it contains.
This scrollView's contentView is set to a MoviePlayer inside the OuterView.
Now whenever I click in the area where the MoviePlayer is, touch events of my OuterView are not responding.
I have even set the movie player's userInteraction to NO.
but now scrollView is interfering it seems.
I have referred to this post on SO itself.
How can a superview interecept a touch sequence before any of its subviews?
But the solution there, asks me to set the userInteractionEnabled to NO for the scrollView
But if I do so, my pinch zoom doesn't take place either!
What to do?
An UIScrollView intercepts all touch events because it has to decide if/when the user has movd their finger in order to scroll the scroll view.
You may want to subclass UIView, then in the
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)evt;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)evt;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)evt;
methods, check for the neccessary touches, then forward all these methods to your UIScrollViewInstance. For example:
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#interface MyView: UIView {
UIScrollView *scrollView;
}
#end
#implementation MyView
/* don't forget to alloc init your ScrollView init -init
and release it in -dealloc! Then add it as a subview of self. */
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)evt
{
/* check for a gesture */
[scrollView touchesBegan:touches withEvent:evt];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)evt;
{
/* check for a gesture */
[scrollView touchesMoved:touches withEvent:evt];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)evt;
{
/* check for a gesture */
[scrollView touchesEnded:touches withEvent:evt];
}
#end

prevent UIScrollView to send touches to subview

How can I prevent the UIScrollView to send touches to it's subviews?
your subview.userInteractionEnabled = NO;
Inherit from UIScrollView and define your own hitTest in following way:
#implementation MyScrollView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
return [self pointInside:point withEvent:event] ? self : nil;
}
#end
So, touches will not be forwarded into internal subviews and will stay on a level of UIScrollView.

Detecting UITableView scrolling

I've subclassed UITableView (as KRTableView) and implemented the four touch-based methods (touchesBegan, touchesEnded, touchesMoved, and touchesCancelled) so that I can detect when a touch-based event is being handled on a UITableView. Essentially what I need to detect is when the UITableView is scrolling up or down.
However, subclassing UITableView and creating the above methods only detects when scrolling or finger movement is occuring within a UITableViewCell, not on the entire UITableView.
As soon as my finger is moved onto the next cell, the touch events don't do anything.
This is how I'm subclassing UITableView:
#import "KRTableView.h"
#implementation KRTableView
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
NSLog(#"touches began...");
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesMoved:touches withEvent:event];
NSLog(#"touchesMoved occured");
}
- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent *)event {
[super touchesCancelled:touches withEvent:event];
NSLog(#"touchesCancelled occured");
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
NSLog(#"A tap was detected on KRTableView");
}
#end
How can I detect when the UITableView is scrolling up or down?
You don't need to intercept the event methods. Check the docs for the UIScrollViewDelegate protocol, and implement the -scrollViewDidScroll: or -scrollViewWillBeginDragging: methods as appropriate to your situation.
I would like to add the following:
If you are working with a UITableView it is likely that you are already implementing the UITableViewDelegate to fill the table with data.
The protocol UITableViewDelegate conforms to UIScrollViewDelegate, so all you need to do is to implement the methods -scrollViewWillBeginDragging and -scrollViewDidScroll directly in your UITableViewDelegate implementation and they will be called automatically if the implementation class is set as delegate to your UITableView.
If you also want to intercept clicks in your table and not only dragging and scrolling, you can extend and implement your own UITableViewCell and use the touchesBegan: methods in there. By combining these two methods you should be able to do most of the things you need when the user interacts with the UITableView.
It was my mistake, I tried to call the method before reloading the tableview. The following code helped me solve this issue.
[mytableview reloadData];
[mytableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:btn.tag-1] atScrollPosition:UITableViewScrollPositionTop animated:YES];

Responding to touchesBegan in UIPickerView instead of UIView

I have a UIPickerView that gets faded out to 20% alpha when not in use. I want the user to be able to touch the picker and have it fade back in.
I can get it to work if I put a touchesBegan method on the main View, but this only works when the user touches the View. I tried sub-classing UIPickerView and having a touchesBegan in there, but it didn't work.
I'm guessing it's something to do with the Responder chain, but can't seem to work it out.
I've been searching for a solution to this problem for over a week. I'm answering you even if you're question is over a year old hoping this helps others.
Sorry if my language is not very technical, but I'm pretty new to Objective-C and iPhone development.
Subclassing UIpickerView is the right way to do it. But you've to override the - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event method. This is the method called whenever you touch the screen and it returns the view that will react to the touch. In other words the view whose touchesBegan:withEvent: method will be called.
The UIPickerView has 9 subviews! In the UIPickerView class implementation - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event won't return self (this means the touchesBegan:withEvent: you write in the subclass won't be called) but will return a subview, exactly the view at index 4 (an undocumented subclass called UIPickerTable).
The trick is to make the - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event method to return self so you have control over the touchesBegan:withEvent:, touchesMoved:withEvent: and touchesEnded:withEvent: methods.
In these methods, in order to keep the standard functionalities of the UIPickerView, you MUST remember to call them again but on the UIPickerTable subview.
I hope this makes sense. I can't write code now, as soon as I'm at home I will edit this answer and add some code.
Here is some code that does what you want:
#interface TouchDetectionView : UIPickerView {
}
- (UIView *)getNextResponderView:(NSSet *)touches withEvent:(UIEvent *)event;
#end
#implementation TouchDetectionView
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UIView * hitTestView = [self getNextResponderView:touches withEvent:event];
[hitTestView touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UIView * hitTestView = [self getNextResponderView:touches withEvent:event];
[hitTestView touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UIView * hitTestView = [self getNextResponderView:touches withEvent:event];
[hitTestView touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
UIView * hitTestView = [self getNextResponderView:touches withEvent:event];
[hitTestView touchesCancelled:touches withEvent:event];
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
return self;
}
- (UIView *)getNextResponderView:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch * touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
UIView * hitTestView = [super hitTest:point withEvent:event];
return ( hitTestView == self ) ? nil : hitTestView;
}
Both of the above answers were very helpful, but I have a UIPickerView nested within a UIScrollView. I'm also doing continual rendering elsewhere on-screen while the GUI is present. The problem is that the UIPickerView doesn't update fully when: a non-selected row is tapped, the picker is moved so that two rows straddle the selection area, or a row is dragged but the finger slides outside of the UIPickerView. Then it's not until the UIScrollView is moved that the picker instantly updates. This result is ugly.
The problem's cause: my continual rendering was keeping the UIPickerView's animation from getting the CPU cycles it needed to finish, hence to show the correct, current selection. My solution --which works-- was this: in the UIPickerView's touchesEnded:withEvent:, execute something to pause my rendering for a short while. Here's the code:
#import "SubUIPickerView.h"
#implementation SubUIPickerView
- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
[pickerTable touchesBegan:touches withEvent:event];
}
- (void) touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
[pickerTable touchesMoved:touches withEvent:event];
}
- (void) touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
{
[singleton set_secondsPauseRendering:0.5f]; // <-- my code to pause rendering
[pickerTable touchesEnded:touches withEvent:event];
}
- (void) touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event
{
[pickerTable touchesCancelled:touches withEvent:event];
}
- (UIView*) hitTest:(CGPoint)point withEvent:(UIEvent*)event
{
if (CGRectContainsPoint(self.bounds, point))
{
if (pickerTable == nil)
{
int nSubviews = self.subviews.count;
for (int i = 0; i < nSubviews; ++i)
{
UIView* view = (UIView*) [self.subviews objectAtIndex:i];
if ([view isKindOfClass:NSClassFromString(#"UIPickerTable")])
{
pickerTable = (UIPickerTable*) view;
break;
}
}
}
return self; // i.e., *WE* will respond to the hit and pass it to UIPickerTable, above.
}
return [super hitTest:point withEvent:event];
}
#end
and then the header, SubUIPickerView.h:
#class UIPickerTable;
#interface SubUIPickerView : UIPickerView
{
UIPickerTable* pickerTable;
}
#end
Like I said, this works. Rendering pauses for an additional 1/2 second (it already pauses when you slide the UIScrollView) allowing the UIPickerView animation to finish. Using NSClassFromString() means you're not using any undocumented APIs. Messing with the Responder chain was not necessary. Thanks to checcco and Tylerc230 for helping me come up with my own solution!
Set canCancelContentTouches and delaysContentTouches of parent view to NO, that worked for me
Couldn't find a recent, easier solution to this so I rigged something to solve my problem. Created an invisible button that stretches over the picker view. Connected that button with a "Touch Down" recognizer to my parent UIView. Now any functionality can be added, I happened to need a random selection timer to be invalidated when someone touches the picker view. Not elegant but it works.