UIScrollview con UIButtons: ¿cómo recrear el trampolín?
-
19-08-2019 - |
Pregunta
Estoy tratando de crear una interfaz similar a un trampolín dentro de mi aplicación. Estoy tratando de usar UIButtons agregados a un UIScrollView. El problema con el que me estoy encontrando es que los botones no pasan ningún toque al UIScrollView: si trato de mover / deslizar y presiono el botón, no se registra para el UIScrollView, pero si cambio el espacio entre botones funcionará. Los botones hacen clic / funcionan si los toco.
¿Hay alguna propiedad o configuración que obligue al botón a enviar los eventos táctiles a su padre (supervista)? ¿Es necesario agregar los botones a otra cosa antes de agregar UIScrollView?
Aquí está mi código:
//init scrolling area
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 480, 480)];
scrollView.contentSize = CGSizeMake(480, 1000);
scrollView.bounces = NO;
scrollView.delaysContentTouches = NO;
//create background image
UIImageView *rowsBackground = [[UIImageView alloc] initWithImage:[self scaleAndRotateImage:[UIImage imageNamed:@"mylongbackground.png"]]];
rowsBackground.userInteractionEnabled = YES;
//create button
UIButton *btn = [[UIButton buttonWithType:UIButtonTypeCustom] retain];
btn.frame = CGRectMake(100, 850, 150, 150);
btn.bounds = CGRectMake(0, 0, 150.0, 150.0);
[btn setImage:[self scaleAndRotateImage:[UIImage imageNamed:@"basicbutton.png"]] forState:UIControlStateNormal];
[btn addTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];
//add "stuff" to scrolling area
[scrollView addSubview:rowsBackground];
[scrollView addSubview:btn];
//add scrolling area to cocos2d
//this is just a UIWindow
[[[Director sharedDirector] openGLView] addSubview:scrollView];
//mem-mgmt
[rowsBackground release];
[btn release];
[scrollView release];
Solución
Para que UIScrollView
determine la diferencia entre un clic que pasa a su (s) vista (s) de contenido y un toque que se convierte en un deslizamiento o pellizco, necesita retrasar el toque y ver si su dedo se movió durante ese retraso. Al configurar delaysContentTouches
en NO
en su ejemplo anterior, está evitando que esto suceda. Por lo tanto, la vista de desplazamiento siempre pasa el toque al botón, en lugar de cancelarlo cuando resulta que el usuario está realizando un gesto de deslizamiento. Intente configurar delaysContentTouches
en YES
.
También podría ser una buena idea, estructuralmente, agregar todas las vistas que se alojarán en su vista de desplazamiento a una vista de contenido común y solo usar esa vista como subvista de la vista de desplazamiento.
Otros consejos
La solución que funcionó para mí incluyó:
- Configurando
canCancelContentTouches
enUIScrollView
enYES
. - Extender
UIScrollView
para anulartouchShouldCancelInContentView: (UIView *) view
para devolverYES
cuandoview
es un < code> UIButton .
De acuerdo con la documentación, touchShouldCancelInContentView
devuelve " YES
para cancelar más mensajes táctiles para ver, NO
para que la vista continúe recibiendo esos mensajes. El valor devuelto predeterminado es YES
si view no es un objeto UIControl
; de lo contrario, devuelve NO
. "
Dado que UIButton
es un UIControl
, la extensión es necesaria para que canCancelContentTouches
surta efecto y permita el desplazamiento.
Tengo un caso similar al de varios botones en un UIScrollView, y quiero desplazar estos botones. Al principio, subclasifiqué UIScrollView y UIButton. Sin embargo, noté que mi subclase UIScrollView no recibió el evento touchesEnded, así que cambié a solo la subclase UIButton.
@interface MyPhotoButton : UIButton {
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
@end
@implementation MyPhotoButton
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesMoved:touches withEvent:event];
[self setEnabled:NO];
[self setSelected:NO];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
[self setEnabled:YES];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesCancelled:touches withEvent:event];
[self setEnabled:YES];
}
@end
UIScrollView maneja muchos eventos por sí mismo. Debe manejar touchchesDidEnd y presionar la prueba de botones dentro del UIScrollView manualmente.
OK, aquí está tu respuesta:
Subclase UIButton. (NOTA: llame a [super ....] al comienzo de cada anulación.
- Añadir una propiedad. Uno de tipo BOOL (llamado enableToRestore)
- Añadir una propiedad. Uno de tipo CGPoint (llamado startTouchPosition)
- en el wakekefromNib y initWithFrame, establezca el enableToRestore en el isEnabled propiedad)
- Anular " touchBegan: withEvent: " para almacenar el inicio del toque posición.
- Anular " touchMoved: withEvent: " a verifique si hay horizontal movimiento.
- En caso afirmativo, configure habilitado en NO y seleccionado a NO.
Código de muestra:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
[super touchesBegan:touches withEvent:event];
[self setStartTouchPosition:[touch locationInView:self]];
}
//
// Helper Function
//
- (BOOL)isTouchMovingHorizontally:(UITouch *)touch
{
CGPoint currentTouchPosition = [touch locationInView:self];
BOOL rValue = NO;
if (fabsf([self startTouchPosition].x - currentTouchPosition.x) >= 2.0)
{
rValue = YES;
}
return (rValue);
}
//
// This is called when the finger is moved. If the result is a left or right
// movement, the button will disable resulting in the UIScrollView being the
// next responder. The parrent ScrollView will then re-enable the button
// when the finger event is ended of cancelled.
//
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
if ([self isTouchMovingHorizontally:[touches anyObject]])
{
[self setEnabled:NO];
[self setSelected:NO];
}
}
Esto activará el UIScrollView.
Subclase UIScrollView. (NOTA: llame a [super ....] al comienzo de cada anulación.
- Reemplazar ambos " touchesEnded: withEvent: " y " touchCancelled: withEvent: "
- En la anulación, restablezca todas las subvistas (y sus subvistas) marca habilitada.
- NOTA: Use una Categoría y agregue el método a UIView:
.
- (void) restoreAllEnables
{
NSArray *views = [self subviews];
for (UIView *aView in views)
{
if ([aView respondsToSelector:@selector(restoreEnable)])
{
[aView restoreEnable];
}
}
}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
[self restoreAllEnables];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
[self restoreAllEnables];
}
- En la Categoría:
.
-(void) restoreEnable
{
NSArray *views = [self subviews];
if ([self respondsToSelector:@selector(enableToRestore)])
{
[self setEnabled:[self enableToRestore]];
}
for (UIView *aView in views)
{
if ([aView respondsToSelector:@selector(restoreEnable)])
{
[aView restoreEnable];
}
}
}
EDITAR Nota: nunca obtuve la respuesta 3 para trabajar. Del mismo modo: setDelaysContentTouches: NO (establecido en el controlador de vista o en algún lugar) se debe configurar para obtener mejores resultados en la Respuesta 4. Esto proporciona una respuesta muy rápida a los botones. Establecer setDelaysContentTouches: YES ejerce un grave impacto (150 ms) en el tiempo de respuesta a los botones y hace que no sea posible tocar la luz rápidamente.
Otra forma es:
1. Sustituya el botón por un simple UIView personalizado
2. Ponga la bandera " userInterationEnable = yes; " en el método init
3. En la vista, anule el método UIResponder " touchesEnded " aquí puede activar la acción que necesita como un botón.
En mi experiencia, la primera respuesta, es decir, simplemente configurar delaysContentTouches
en YES
, no cambia nada con respecto al problema. Los botones aún no entregarán resultados de seguimiento a la vista de desplazamiento. La tercera respuesta es simple y muy útil. Gracias sieroaoj!
Sin embargo, para que la tercera respuesta funcione, también necesita delaysContentTouches
establecido en YES
. De lo contrario, también se llamará al método touchesEnded
para realizar un seguimiento dentro de la vista. Por lo tanto, podría resolver el problema:
- Sustituya el botón por una simple UIView personalizada
- Ponga la bandera " userInterationEnable = yes; " en el método init
- En la vista, anule el método UIResponder " touchesEnded " aquí puedes activar la acción que
Cuarto. establezca delaysContentTouches
en YES