UIDatePicker dentro UIScrollView com páginas
-
06-09-2019 - |
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?
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];