Uiscrollview con UIButtons: ¿cómo recrear el trampolín?

Intento crear una interfaz tipo trampolín dentro de mi aplicación. Estoy intentando usar UIButtons agregados a UIScrollView. El problema al que me estoy enfrentando es con los botones que no pasan ningún toque al UIScrollView; si bash deslizar / deslizar y presiono el botón, no se registra en UIScrollView, pero si deslizo el espacio entre botones funcionará. Los botones hacen clic / trabajar si los toco.

¿Hay alguna propiedad o configuración que obligue al botón a enviar los eventos táctiles hasta su elemento principal (supervista)? ¿Los botones deben agregarse a otra cosa antes de agregarse 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]; 

Para que UIScrollView determine la diferencia entre un clic que pasa a su vista (s) de contenido y un toque que se convierte en deslizamiento o pellizco, necesita retrasar el toque y ver si su dedo se movió durante ese retraso. Al configurar delaysContentTouches en NO en el ejemplo anterior, impide 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. Intenta 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.

La solución que funcionó para mí incluía:

  1. Estableciendo canCancelContentTouches en UIScrollView en YES .
  2. Extendiendo UIScrollView para anular touchesShouldCancelInContentView:(UIView *)view para devolver YES cuando view es un UIButton .

De acuerdo con la documentación, touchesShouldCancelInContentView devuelve ” YES ” para cancelar otros mensajes táctiles para ver, NO para que la vista continúe recibiendo esos mensajes. El valor predeterminado devuelto es YES si la vista no es un objeto UIControl ; de lo contrario, devuelve NO “.

Como UIButton es un UIControl la extensión es necesaria para que canCancelContentTouches surta efecto y permite el desplazamiento.

Tengo un caso similar que una serie de botones en un UIScrollView, y quiero desplazar estos botones. Al principio, subclasé UIScrollView y UIButton. Sin embargo, noté que mi UIScrollView subclasificado no recibió el evento touchesEnded, así que cambié a 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 en sí mismo. Debes manejar touchesDidEnd y presionar la tecla de prueba dentro de UIScrollView manualmente.

OK aquí está tu respuesta:

Subclase UIButton. (NOTA: llame [super ….] al inicio de cada anulación.

  • Agregar una propiedad. Uno de tipo BOOL (llamado enableToRestore)
  • Agregar una propiedad. Uno de tipo CGPoint (llamado startTouchPosition)
  • en awakeFromNib e initWithFrame, establezca enableToRestore en la propiedad isEnabled)
  • Anule “touchesBegan: withEvent:” para almacenar el inicio de la posición táctil.
  • Anule “touchesMoved: withEvent:” para verificar si hay movimiento horizontal.
  • Si es SÍ, configure habilitado a 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á UIScrollView.

Subclase UIScrollView. (NOTA: llame [super ….] al inicio de cada anulación.

  • Anula los “toquesEntrada: conEvento:” y “tocaCancelado: conEvento:”
  • En la anulación, restablezca todas las subvistas (y sus subvistas) habilitado bandera.
  • NOTA: Use una Categoría y agregue el método a UIView:

.

 - (void) restreAllEnables { NSArray *views = [self subviews]; for (UIView *aView in views) { if ([aView respondsToSelector:@selector(restreEnable)]) { [aView restreEnable]; } } } - (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesEnded:touches withEvent:event]; [self restreAllEnables]; } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesEnded:touches withEvent:event]; [self restreAllEnables]; } 
  • En la categoría:

.

 -(void) restreEnable { NSArray *views = [self subviews]; if ([self respondsToSelector:@selector(enableToRestore)]) { [self setEnabled:[self enableToRestore]]; } for (UIView *aView in views) { if ([aView respondsToSelector:@selector(restreEnable)]) { [aView restreEnable]; } } } 

EDITAR Nota: nunca obtuve la respuesta 3 para que funcione. Del mismo modo: setDelaysContentTouches: NO (establecido en el controlador de visualización o en algún otro lugar) se debe configurar para obtener mejores resultados en la Respuesta 4. Esto proporciona una respuesta muy rápida a los botones. Configuración de setDelaysContentTouches: YES tiene un impacto grave (150 ms) en el tiempo de respuesta a los botones y no permite un toque ligero y rápido.

Otra forma es:
1. Sustituir el botón por una simple UIView personalizada
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 los delaysContentTouches en YES , no cambia nada con respecto al problema. Los botones todavía 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, el método touchesEnded también se llamará para el seguimiento dentro de la vista. Por lo tanto, podría resolver el problema de la siguiente manera:

  1. Sustituir botón por una simple UIView personalizada
  2. Ponga la bandera “userInterationEnable = yes;” en el método init
  3. En la vista, anula el método UIResponder “touchesEnded” aquí, puede desencadenar la acción que

Cuarto. establecer delaysContentTouches a YES