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?

È stato utile?

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];
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top