UIDatePicker all'interno UIScrollView con pagine
-
06-09-2019 - |
Domanda
Ho un UIScrollView con 2 pagine, e posso scorrere orizzontalmente tra di loro. Tuttavia, in una delle mie pagine, ho un UIDatePicker, e la vista di scorrimento sta intercettando gli eventi touch verticali in modo da non posso più manipolare la selezione data (ad eccezione facendo clic o toccando). C'è qualche modo per dire al ScrollView per inviare gli eventi tocco verticali per il calendario, ma inviare gli eventi tocco orizzontali per la vista di scorrimento per cambiare pagina?
Soluzione
Credo che ci sono due parti a questo problema. Il primo è determinare l'intenzione dell'utente, e la seconda sta ottenendo il controllo corretto per rispondere a tale intenzione.
Determinazione intenti
Credo che sia importante essere chiari su ciò che l'utente intende. Immaginate questo scenario: L'utente avvia toccando lo schermo e sposta il dito lontano alla sinistra, ma anche un po '. L'utente, probabilmente destinato a scorrere la vista, e non ha intenzione di cambiare la data a tutti. Sarebbe male sia per scorrere la vista e modificare la data, in particolare proprio mentre si muove fuori dallo schermo. Quindi, per determinare ciò che l'utente intende suggerisco il seguente algoritmo:
Quando l'utente avvia toccando lo schermo, registrare la posizione di partenza. Come dito dell'utente inizia ad allontanarsi da quella posizione, i controlli non dovrebbero reagire a tutti. Una volta che le tocco si sposta oltre una certa distanza di soglia dalla posizione iniziale, determinare se spostato più orizzontalmente o verticalmente. Se spostata verticalmente, l'utente intende cambiare la data, quindi ignorare la porzione orizzontale del movimento e cambia solo la data. Se si muovesse più orizzontalmente, l'utente intende scorrere la vista, quindi ignorare la porzione verticale del movimento e scorrere solo la visualizzazione.
Attuazione
Al fine di attuare questo, è necessario per gestire gli eventi prima della UIScrollView o la data picker fare. C'è probabilmente un paio di modi per farlo, ma uno in particolare viene in mente: fare un'UIView personalizzato chiamato ScrollingDateMediatorView. Impostare l'UIScrollView come un figlio di questa visione. Override del ScrollingDateMediatorView hitTest: withEvent: e pointInside: withEvent: metodi. Questi metodi devono eseguire lo stesso tipo di test di successo che si verificherebbe normalmente, ma se il risultato è il calendario, auto tornano invece. Questo dirotta in modo efficace eventuali eventi touch che sono stati destinati al calendario, permettendo al ScrollingDateMediatorView gestirli prima. Poi si implementa l'algoritmo descritto in precedenza nei vari tocchi * metodi. In particolare:
Nel touchesBegan:. Metodo withEvent, salvare la posizione di partenza
In touchesMoved: withEvent, se l'intenzione dell'utente non è ancora noto, determinare se l'ha toccato è spostato abbastanza lontano dalla posizione di partenza. In caso affermativo, determinare se l'utente intende scorrere o modificare la data e salvare tale intento. Se l'intenzione dell'utente è già noto ed è per cambiare la data, inviare la selezione data la touchedMoved: messaggio withEvent, altrimenti inviare l'UIScrollView il touchesMoved: messaggio withEvent. Dovrete fare un certo lavoro simliar entro touchesEnded: withEvent e touchesCancelled: withEvent per assicurarsi che le altre viste ottengono i messaggi appropriati. Entrambi questi metodi dovrebbero ripristinare i valori salvati.
Una volta che avete moltiplicazione correttamente gli eventi, probabilmente dovrete provare alcuni test utente per sintonizzare la soglia di traffico.
Altri suggerimenti
In realtà, c'è un'implementazione molto più semplice di quello che Bob ha suggerito. Questo funziona perfettamente per me. Sarà necessario creare una sottoclasse vostro UIScrollView se non l'hai già, e comprendono questo metodo: -
- (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;
}
Il motivo che uso result.superview
è che la visione che ottiene i tocchi sarà effettivamente un UIPickerTable, che è un privato API.
Saluti
Impressionante aiuto Sam! Ho usato che per creare un semplice categoria che swizzles il metodo (perché stavo facendo questo in un UITableViewController e quindi avrei dovuto fare alcune cose davvero disordinato alla sottoclasse la vista di scorrimento).
#import <UIKit/UIKit.h>
@interface UIScrollView (withControls)
+ (void) swizzle;
@end
E il codice principale:
#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
Poi, nel mio metodo viewDidLoad, ho appena chiamato
[UIScrollView swizzle];