UIDatePicker inside UIScrollView with pages

I have a UIScrollView with 2 pages and I can scroll them horizontally between them. However, on one of my pages I have a UIDatePicker, and the scroll view captures vertical touch events, so I can no longer manipulate the date selection (other than clicking or clicking). Is there a way to tell ScrollView to send vertical touch events to a date, but send horizontal touch events to scroll mode to switch pages?

+4
source share
3 answers

I think there are two parts to this problem. The first is the determination of the user's intention, and the second is the proper management to respond to this intention.

Definition of Intent

I think it’s important to clearly understand what the user intends. Imagine this scenario: the user starts touching the screen and moves his finger to the left, but also a little up. Perhaps the user intended to scroll through the view and did not intend to change the date at all. It would be bad if you scroll the view and change the date, especially as it moves off the screen. Therefore, to determine what the user intends, I propose the following algorithm:

When the user starts touching the screen, record the starting position. When the user finger begins to move away from this position, the controls should not respond at all. As soon as the touch moves past a certain threshold distance from the starting position, determine if it has been moved more horizontally or vertically. If it moves vertically, the user intends to change the date, so ignore the horizontal part of the movement and change only the date. If it moves more horizontally, the user intends to scroll the view, so ignore the vertical part of the movement and view only the scroll.

Implementation

To implement this, you need to handle events before a UIScrollView or date picker does. There are probably several ways to do this, but it comes to mind in particular: create a custom UIView called ScrollingDateMediatorView. Set UIScrollView as a child of this view. Override the ScrolllingDateMediatorView method hitTest: withEvent: and pointInside: withEvent :. These methods should perform the same tests that usually occur, but if the result is a date picker, get yourself back. It effectively captures any touch events designed to select a date, allowing ScrollingDateMediatorView to handle them first. You then implement the algorithm described above in various touch * methods. In particular:

In the touchhesBegan: withEvent method, save the starting position.

In contacts: Love: withEvent, if the user's intention is not yet known, determine whether the touch has gone far enough from the starting position. If so, determine if the user intends to scroll or change the date and save that intention. If the user's intention is already known and it has changed the date, send the date selection by clicking the message Message: withEvent, otherwise send the message UhescrollView message touchhesMoved: withEvent. You will need to do some similar work within touchesEnded: withEvent and touchesCancelled: withEvent to ensure that other views receive the appropriate messages. Both of these methods should reset the stored values.

After the events are correctly propagated, you may have to try some user tests to set the threshold for movement.

+5
source

In fact, there is a much simpler implementation than Bob suggested. This works great for me. You will need to subclass your UIScrollview, if you have not done so already, and enable this method: -

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event { UIView* result = [super hitTest:point withEvent:event]; if ([result.superview isKindOfClass:[UIPickerView class]]) { self.canCancelContentTouches = NO; self.delaysContentTouches = NO; } else { self.canCancelContentTouches = YES; // (or restore bool from prev value if needed) self.delaysContentTouches = YES; // (same as above) } return result; } 

The reason I use result.superview is because the view that receives the strokes will actually be a UIPickerTable, which is a private API.

Greetings

+6
source

Amazing help to Sam! I used this to create a simple category that swizzles the method (because I did it in the UITableViewController and therefore would have to do some really dirty things to subclass the scroll).

 #import <UIKit/UIKit.h> @interface UIScrollView (withControls) + (void) swizzle; @end 

And the main code:

 #import </usr/include/objc/objc-class.h> #import "UIScrollView+withControls.h" #define kUIViewBackgroundImageTag 6183746 static BOOL swizzled = NO; @implementation UIScrollView (withControls) + (void)swizzleSelector:(SEL)orig ofClass:(Class)c withSelector:(SEL)new; { Method origMethod = class_getInstanceMethod(c, orig); Method newMethod = class_getInstanceMethod(c, new); if (class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) { class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod)); } else { method_exchangeImplementations(origMethod, newMethod); } } + (void) swizzle { @synchronized(self) { if (!swizzled) { [UIScrollView swizzleSelector:@selector(hitTest:withEvent:) ofClass:[UIScrollView class] withSelector:@selector(swizzledHitTest:withEvent:)]; swizzled = YES; } } } - (UIView*)swizzledHitTest:(CGPoint)point withEvent:(UIEvent *)event { UIView* result = [self swizzledHitTest:point withEvent:event]; // actually calling the original hitTest method if ([result.superview isKindOfClass:[UIPickerView class]]) { self.canCancelContentTouches = NO; self.delaysContentTouches = NO; } else { self.canCancelContentTouches = YES; // (or restore bool from prev value if needed) self.delaysContentTouches = YES; // (same as above) } return result; } @end 

Then, in my viewDidLoad method, I just called

 [UIScrollView swizzle]; 
+1
source

All Articles