Pergunta

Eu tenho uma UIScrollView com 2 páginas, e eu posso rolar horizontalmente entre eles. No entanto, em uma das minhas páginas, eu tenho um UIDatePicker, ea vista de rolagem é interceptar os eventos de toque verticais então eu já não pode manipular o selecionador de data (exceto clicando ou tocando). Existe alguma maneira de dizer o ScrollView para enviar os eventos de toque verticais para o selecionador de data, mas enviar os eventos de toque horizontais para a exibição de rolagem para páginas switch?

Foi útil?

Solução

Eu acho que há duas partes para este problema. O primeiro é determinar a intenção do usuário, eo segundo é conseguir o controle correto para responder a essa intenção.

determinar a intenção

Eu acho que é importante ser claro sobre o que o utilizador pretende. Imagine este cenário: O usuário inicia tocando a tela e move o dedo longe para a esquerda, mas também um pouco. O usuário provavelmente pretendia deslocar na vista, e não tinha a intenção de alterar a data em tudo. Seria ruim tanto para rolar a exibição e alterar a data, especialmente tal como ele se move fora da tela. Assim, para determinar o que o usuário pretende sugiro o seguinte algoritmo:

Quando o usuário inicia tocando a tela, gravar a posição inicial. Como dedo do usuário começa a se afastar dessa posição, os controles não deve reagir em tudo. Uma vez que o toque se move para além de uma certa distância limiar a partir da posição de partida, determinar se ela se movia mais horizontalmente ou verticalmente. Se movido verticalmente, o usuário tem a intenção de alterar a data, então ignore a parte horizontal do movimento e só alterar a data. Se ele se mudou mais na horizontal, o usuário tem a intenção de rolar a exibição, por isso ignorar a parte vertical do movimento e só rolar a exibição.

Implementação

Para implementar isso, você precisa lidar com os eventos antes da UIScrollView ou data picker fazer. Há provavelmente algumas maneiras de fazer isso, mas uma em particular vem à mente: Faça um costume UIView chamado ScrollingDateMediatorView. Defina o UIScrollView como uma criança dessa visão. Substituir do ScrollingDateMediatorView hitTest: withEvent: e pointInside: withEvent: métodos. Estes métodos precisa executar o mesmo tipo de teste de hit que ocorreria normalmente, mas se o resultado é o selecionador de data, o retorno auto vez. Este sequestra eficazmente quaisquer eventos de toque que foram destinados para o selecionador de data, permitindo que o ScrollingDateMediatorView para lidar com eles primeiro. Então você implementar o algoritmo descrito acima nos vários toques * métodos. Especificamente:

No touchesBegan:. Método withEvent, salvar a posição inicial

Em touchesMoved: withEvent, se a intenção do usuário não se sabe ainda, determinar se o tocou foi movido suficientemente longe da posição inicial. Se ele tem, determinar se o usuário tem a intenção de se deslocar ou alterar a data, e salvar essa intenção. Se da intenção do usuário já é conhecido e é para mudar a data, enviar o selecionador de data do touchedMoved: mensagem withEvent, caso contrário enviar o UIScrollView o touchesMoved: mensagem withEvent. Você vai ter que fazer algum trabalho simliar dentro touchesEnded: withEvent e touchesCancelled: withEvent para garantir que os outros pontos de vista obter as mensagens apropriadas. Ambos os métodos devem repor os valores guardados.

Depois de ter-lo corretamente propagação eventos, você provavelmente vai ter que tentar algum usuário testar para ajustar o limite de movimento.

Outras dicas

Na verdade, não é uma implementação muito mais simples do que o que Bob sugeriu. Isso funciona perfeitamente para mim. Você precisará subclasse seu UIScrollView se você não tiver já, e incluir este método: -

- (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;
}

A razão que eu uso result.superview é que a visão que recebe os toques na verdade será um UIPickerTable, que é uma API privada.

Felicidades

Awesome ajuda Sam! Eu usei isso para criar uma categoria simples que swizzles o método (porque eu estava fazendo isso em um UITableViewController e, assim, teria que fazer algumas coisas realmente confuso a subclasse o ponto de vista de rolagem).

#import <UIKit/UIKit.h>

@interface UIScrollView (withControls)

+ (void) swizzle;

@end

E o código principal:

#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

Então, no meu método viewDidLoad, eu apenas chamado

[UIScrollView swizzle];
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top