iphone objective-c uiscrollview

iphone - UIScrollView: ¿paginación horizontal, desplazamiento vertical?



objective-c (19)

Aquí está mi implementación de un UIScrollView paginado que solo permite desplazamientos ortogonales. Asegúrese de configurar pagingEnabled en YES .

PagedOrthoScrollView.h

@interface PagedOrthoScrollView : UIScrollView @end

PagedOrthoScrollView.m

#import "PagedOrthoScrollView.h" typedef enum { PagedOrthoScrollViewLockDirectionNone, PagedOrthoScrollViewLockDirectionVertical, PagedOrthoScrollViewLockDirectionHorizontal } PagedOrthoScrollViewLockDirection; @interface PagedOrthoScrollView () @property(nonatomic, assign) PagedOrthoScrollViewLockDirection dirLock; @property(nonatomic, assign) CGFloat valueLock; @end @implementation PagedOrthoScrollView @synthesize dirLock; @synthesize valueLock; - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self == nil) { return self; } self.dirLock = PagedOrthoScrollViewLockDirectionNone; self.valueLock = 0; return self; } - (void)setBounds:(CGRect)bounds { int mx, my; if (self.dirLock == PagedOrthoScrollViewLockDirectionNone) { // is on even page coordinates, set dir lock and lock value to closest page mx = abs((int)CGRectGetMinX(bounds) % (int)CGRectGetWidth(self.bounds)); if (mx != 0) { self.dirLock = PagedOrthoScrollViewLockDirectionHorizontal; self.valueLock = (round(CGRectGetMinY(bounds) / CGRectGetHeight(self.bounds)) * CGRectGetHeight(self.bounds)); } else { self.dirLock = PagedOrthoScrollViewLockDirectionVertical; self.valueLock = (round(CGRectGetMinX(bounds) / CGRectGetWidth(self.bounds)) * CGRectGetWidth(self.bounds)); } // show only possible scroll indicator self.showsVerticalScrollIndicator = dirLock == PagedOrthoScrollViewLockDirectionVertical; self.showsHorizontalScrollIndicator = dirLock == PagedOrthoScrollViewLockDirectionHorizontal; } if (self.dirLock == PagedOrthoScrollViewLockDirectionHorizontal) { bounds.origin.y = self.valueLock; } else { bounds.origin.x = self.valueLock; } mx = abs((int)CGRectGetMinX(bounds) % (int)CGRectGetWidth(self.bounds)); my = abs((int)CGRectGetMinY(bounds) % (int)CGRectGetHeight(self.bounds)); if (mx == 0 && my == 0) { // is on even page coordinates, reset lock self.dirLock = PagedOrthoScrollViewLockDirectionNone; } [super setBounds:bounds]; } @end

¿Cómo puedo forzar un UIScrollView en el que las paginación y el desplazamiento están activados para moverse vertical u horizontalmente en un momento dado?

Tengo entendido que la propiedad directionalLockEnabled debe lograr esto, pero un deslizamiento diagonal aún hace que la vista se desplace diagonalmente en lugar de restringir el movimiento a un solo eje.

Editar: para ser más claro, me gustaría permitir al usuario desplazarse horizontalmente O verticalmente, pero no ambos simultáneamente.


Chicos, es tan simple como hacer que la altura de la subvista sea la misma que la altura de la vista de desplazamiento y la altura del tamaño del contenido. Entonces el desplazamiento vertical está desactivado.


De los documentos:

"el valor predeterminado es NO, lo que significa que se permite el desplazamiento en ambas direcciones horizontal y vertical. Si el valor es SÍ y el usuario comienza a arrastrar en una dirección general (horizontal o vertical), la vista de desplazamiento deshabilita el desplazamiento en la otra dirección. "

Creo que la parte importante es "si ... el usuario comienza a arrastrar en una dirección general". Entonces, si comienzan a arrastrarse diagonalmente, esto no entra en acción. No es que estos documentos sean siempre confiables en lo que respecta a la interpretación, pero parece encajar con lo que está viendo.

Esto en realidad parece sensato. Tengo que preguntar: ¿por qué quieres restringir a solo horizontal o solo vertical en cualquier momento? Tal vez UIScrollView no es la herramienta para ti?


Estás en una situación muy difícil, debo decir.

Tenga en cuenta que debe usar un UIScrollView con pagingEnabled=YES para alternar entre páginas, pero necesita pagingEnabled=NO para desplazarse verticalmente.

Hay 2 estrategias posibles. No sé cuál funcionará / es más fácil de implementar, así que pruebe ambos.

Primero: UIScrollViews anidados. Francamente, todavía tengo que ver a una persona que tiene esto para trabajar. Sin embargo, no me he esforzado lo suficiente personalmente, y mi práctica muestra que cuando lo intentas lo suficiente, puedes hacer que UIScrollView haga lo que quieras .

Por lo tanto, la estrategia es permitir que la vista de desplazamiento exterior solo controle el desplazamiento horizontal y las vistas de desplazamiento interior para que solo se controle el desplazamiento vertical. Para lograr eso, debes saber cómo funciona UIScrollView internamente. hitTest método hitTest y siempre se devuelve a sí mismo, de modo que todos los eventos táctiles entren en UIScrollView. Luego, dentro de touchesBegan , touchesBegan , etc., comprueba si le interesa el evento y lo maneja o lo pasa a los componentes internos.

Para decidir si el toque se va a manejar o reenviar, UIScrollView inicia un temporizador cuando lo toca por primera vez:

  • Si no ha movido el dedo significativamente dentro de los 150 ms, pasa el evento a la vista interior.

  • Si movió su dedo significativamente dentro de los 150 ms, comienza a desplazarse (y nunca pasa el evento a la vista interna).

    Tenga en cuenta cómo cuando toca una tabla (que es una subclase de vista de desplazamiento) y comienza a desplazarse de inmediato, nunca se resalta la fila que tocó.

  • Si no has movido el dedo significativamente dentro de los 150 ms y UIScrollView comenzó a pasar los eventos a la vista interna, pero luego has movido el dedo lo suficiente para que comience el desplazamiento, las llamadas a UIScrollView touchesCancelled en la vista interna y comienzan a desplazarse.

    Tenga en cuenta que al tocar una mesa, mantenga presionado el dedo y luego comience a desplazarse, la fila que tocó se resalta primero, pero luego se resalta.

Esta secuencia de eventos se puede modificar mediante la configuración de UIScrollView:

  • Si la función delaysContentTouches es NO, entonces no se utiliza ningún temporizador; los eventos pasan inmediatamente al control interno (pero luego se cancelan si mueves el dedo lo suficiente).
  • Si cancelsTouches es NO, una vez que los eventos se envían a un control, el desplazamiento nunca ocurrirá.

Tenga en cuenta que es UIScrollView que recibe todos los touchesBegin , touchesMoved , touchesEnded y touchesCanceled de CocoaTouch (porque hitTest le dice que lo haga). Luego los envía a la vista interna si lo desea, siempre que lo desee.

Ahora que ya sabes todo sobre UIScrollView, puedes modificar su comportamiento. Puedo apostar que quieres dar preferencia al desplazamiento vertical, de modo que una vez que el usuario toque la vista y comience a mover su dedo (aunque sea levemente), la vista comenzará a desplazarse en dirección vertical; pero cuando el usuario mueve su dedo en la dirección horizontal lo suficiente, desea cancelar el desplazamiento vertical e iniciar el desplazamiento horizontal.

Desea subclasificar su UIScrollView externo (por ejemplo, nombre a su clase RemorsefulScrollView), de modo que en lugar del comportamiento predeterminado, inmediatamente reenvía todos los eventos a la vista interna, y solo cuando se detecta movimiento horizontal significativo se desplaza.

¿Cómo hacer que RemorsefulScrollView se comporte de esa manera?

  • Parece que deshabilitar el desplazamiento vertical y delaysContentTouches configuraciónContentTouches en NO debería hacer que UIScrollViews anidados funcione. Lamentablemente, no es así; UIScrollView parece hacer un poco de filtrado adicional para movimientos rápidos (que no se pueden deshabilitar), por lo que incluso si UIScrollView solo se puede desplazar horizontalmente, siempre se comerá (e ignorará) movimientos verticales lo suficientemente rápidos.

    El efecto es tan severo que no se puede utilizar el desplazamiento vertical dentro de una vista de desplazamiento anidado. (Parece que tienes exactamente esta configuración, así que pruébalo: mantén un dedo durante 150ms, y luego muévelo en dirección vertical - ¡UIScrollView anidado funciona como se esperaba!)

  • Esto significa que no puede usar el código de UIScrollView para el manejo de eventos; debe anular los cuatro métodos de manejo táctil en RemorsefulScrollView y hacer su propio procesamiento primero, solo reenviando el evento a super (UIScrollView) si ha decidido ir con desplazamiento horizontal.

  • Sin embargo, debe pasar los touchesBegan a UIScrollView, porque desea que recuerde una coordenada base para el desplazamiento horizontal futuro (si luego decide que se trata de un desplazamiento horizontal). No podrá enviar touchesBegan a UIScrollView más tarde, porque no puede almacenar el argumento de touches : contiene objetos que se touchesMoved antes del próximo evento touchesMoved y no podrá reproducir el estado antiguo.

    Por lo tanto, debe pasar los touchesBegan a UIScrollView inmediatamente, pero ocultará cualquier otro evento touchesMoved hasta que decida desplazarse horizontalmente. No touchesMoved significa que no hay desplazamiento, por lo que este touchesBegan inicial de touchesBegan no hará daño. Pero establezca delaysContentTouches en NO, para que no interfieran temporizadores sorpresa adicionales.

    (Offtopic: a diferencia de ti, UIScrollView puede almacenar toques correctamente y puede reproducir y reenviar el evento original touchesBegan más tarde. Tiene una ventaja injusta de utilizar API no publicadas, por lo que puede clonar objetos táctiles antes de que se muten).

  • Dado que siempre reenvía touchesBegan , también debe reenviar touchesCancelled y touchesEnded . Sin embargo, tiene que convertir los touchesEnded en touchesCancelled , porque UIScrollView interpretaría touchesBegan , touchesEnded sequence como un toque-clic y lo reenviaría a la vista interna. Ya estás reenviando los eventos propios, por lo que nunca querrás que UIScrollView reenvíe nada.

Básicamente, aquí está el pseudocódigo de lo que debes hacer. Para simplificar, nunca se permite el desplazamiento horizontal después de que se haya producido un evento multitáctil.

// RemorsefulScrollView.h @interface RemorsefulScrollView : UIScrollView { CGPoint _originalPoint; BOOL _isHorizontalScroll, _isMultitouch; UIView *_currentChild; } @end // RemorsefulScrollView.m // the numbers from an example in Apple docs, may need to tune them #define kThresholdX 12.0f #define kThresholdY 4.0f @implementation RemorsefulScrollView - (id)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { self.delaysContentTouches = NO; } return self; } - (id)initWithCoder:(NSCoder *)coder { if (self = [super initWithCoder:coder]) { self.delaysContentTouches = NO; } return self; } - (UIView *)honestHitTest:(CGPoint)point withEvent:(UIEvent *)event { UIView *result = nil; for (UIView *child in self.subviews) if ([child pointInside:point withEvent:event]) if ((result = [child hitTest:point withEvent:event]) != nil) break; return result; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesBegan:touches withEvent:event]; // always forward touchesBegan -- there''s no way to forward it later if (_isHorizontalScroll) return; // UIScrollView is in charge now if ([touches count] == [[event touchesForView:self] count]) { // initial touch _originalPoint = [[touches anyObject] locationInView:self]; _currentChild = [self honestHitTest:_originalPoint withEvent:event]; _isMultitouch = NO; } _isMultitouch |= ([[event touchesForView:self] count] > 1); [_currentChild touchesBegan:touches withEvent:event]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { if (!_isHorizontalScroll && !_isMultitouch) { CGPoint point = [[touches anyObject] locationInView:self]; if (fabsf(_originalPoint.x - point.x) > kThresholdX && fabsf(_originalPoint.y - point.y) < kThresholdY) { _isHorizontalScroll = YES; [_currentChild touchesCancelled:[event touchesForView:self] withEvent:event] } } if (_isHorizontalScroll) [super touchesMoved:touches withEvent:event]; // UIScrollView only kicks in on horizontal scroll else [_currentChild touchesMoved:touches withEvent:event]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { if (_isHorizontalScroll) [super touchesEnded:touches withEvent:event]; else { [super touchesCancelled:touches withEvent:event]; [_currentChild touchesEnded:touches withEvent:event]; } } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesCancelled:touches withEvent:event]; if (!_isHorizontalScroll) [_currentChild touchesCancelled:touches withEvent:event]; } @end

No he intentado ejecutar o incluso compilar esto (y he escrito toda la clase en un editor de texto sin formato), pero puede comenzar con lo anterior y, con suerte, ponerlo en funcionamiento.

La única pega oculta que veo es que si agrega vistas secundarias no UIScrollView a RemorsefulScrollView, los eventos táctiles que reenvía a un niño pueden llegar a usted a través de la cadena de respuesta, si el niño no siempre maneja toques como lo hace UIScrollView. Una implementación de RemorsefulScrollView a prueba de balas protegería contra touchesXxx .

Segunda estrategia: si por algún motivo UIScrollViews anidados no funcionan o resultan demasiado difíciles de acertar, puedes tratar de llevarte bien con un solo UIScrollView, cambiando su propiedad pagingEnabled sobre la marcha desde tu método de delegado scrollViewDidScroll .

Para evitar el desplazamiento en diagonal, primero debe intentar recordar contentOffset en scrollViewWillBeginDragging y verificar y restablecer contentOffset dentro de scrollViewDidScroll si detecta un movimiento en diagonal. Otra estrategia para intentar es restablecer contentSize para permitir solo el desplazamiento en una dirección, una vez que decidas en qué dirección va el dedo del usuario. (UIScrollView parece bastante indulgente acerca de jugar con contentSize y contentOffset desde sus métodos de delegado).

Si eso tampoco funciona o produce imágenes descuidadas, debes anular touchesBegan , touchesMoved , etc. y no reenviar eventos de movimiento diagonal a UIScrollView. (Sin embargo, la experiencia del usuario será subóptima en este caso, ya que tendrá que ignorar los movimientos diagonales en lugar de forzarlos en una sola dirección. Si se siente realmente aventurero, puede escribir su propia apariencia de UITouch, algo así como RevengeTouch. -C es la antigua C, y no hay nada más patoso en el mundo que C, mientras nadie mire la clase real de los objetos, que creo que nadie lo hace, puedes hacer que cualquier clase se parezca a cualquier otra clase. una posibilidad de sintetizar los toques que desee, con las coordenadas que desee).

Estrategia de respaldo: existe TTScrollView, una sana reimplementación de UIScrollView en la biblioteca Three20 . Desafortunadamente, se siente muy antinatural y no apto para el usuario. Pero si falla cualquier intento de usar UIScrollView, puede recurrir a una vista de desplazamiento codificada de forma personalizada. Recomiendo en su contra si es posible; El uso de UIScrollView asegura que obtendrá la apariencia nativa, sin importar cómo evolucione en futuras versiones de sistema operativo de iPhone.

De acuerdo, este pequeño ensayo se hizo demasiado largo. Todavía estoy en los juegos UIScrollView después del trabajo en ScrollingMadness hace varios días.

PD: si obtienes alguno de estos trabajos y quieres compartirlos, envíanos un correo electrónico con el código correspondiente a [email protected], felizmente lo incluiré en mi bolsa de trucos de ScrollingMadness .

PPS Añadiendo este pequeño ensayo a ScrollingMadness README.


Esta es una solución mejorada de icompot, que fue bastante inestable para mí. Este funciona bien y es fácil de implementar:

- (void) scrollViewWillBeginDragging: (UIScrollView *) scrollView { self.oldContentOffset = scrollView.contentOffset; } - (void) scrollViewDidScroll: (UIScrollView *) scrollView { float XOffset = fabsf(self.oldContentOffset.x - scrollView.contentOffset.x ); float YOffset = fabsf(self.oldContentOffset.y - scrollView.contentOffset.y ); if (scrollView.contentOffset.x != self.oldContentOffset.x && (XOffset >= YOffset) ) { scrollView.pagingEnabled = YES; scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x, self.oldContentOffset.y); } else { scrollView.pagingEnabled = NO; scrollView.contentOffset = CGPointMake( self.oldContentOffset.x, scrollView.contentOffset.y); } } - (void) scrollViewDidEndDecelerating: (UIScrollView *) scrollView { self.oldContentOffset = scrollView.contentOffset; }


Esto funciona para mí cada vez ...

scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * NumberOfPages, 1);


Gracias Tonetel. Modifiqué ligeramente tu enfoque, pero era exactamente lo que necesitaba para evitar el desplazamiento horizontal.

self.scrollView.contentSize = CGSizeMake(self.scrollView.contentSize.width, 1);


Lo que hice fue crear un paginador UIScrollView con un cuadro del tamaño de la pantalla de visualización y establecer el tamaño del contenido con el ancho necesario para todo mi contenido y una altura de 1 (gracias Tonetel). Luego creé el menú UIScrollVer páginas con marcos establecidos en cada página, el tamaño de la pantalla de visualización y establecer el tamaño del contenido de cada uno al ancho de la pantalla y la altura necesaria para cada uno. Luego simplemente agregué cada una de las páginas del menú a la vista de búsqueda. Asegúrese de que la búsqueda está habilitada en la vista de desplazamiento de la aplicación de búsqueda, pero está desactivada en las vistas del menú (debe estar desactivada de forma predeterminada) y debe estar listo para continuar.

La vista de desplazamiento de paginación ahora se desplaza horizontalmente, pero no verticalmente debido a la altura del contenido. Cada página se desplaza verticalmente pero no horizontalmente debido a sus restricciones de tamaño de contenido también.

Aquí está el código si esa explicación deja algo que desear:

UIScrollView *newPagingScrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds]; [newPagingScrollView setPagingEnabled:YES]; [newPagingScrollView setShowsVerticalScrollIndicator:NO]; [newPagingScrollView setShowsHorizontalScrollIndicator:NO]; [newPagingScrollView setDelegate:self]; [newPagingScrollView setContentSize:CGSizeMake(self.view.bounds.size.width * NumberOfDetailPages, 1)]; [self.view addSubview:newPagingScrollView]; float pageX = 0; for (int i = 0; i < NumberOfDetailPages; i++) { CGRect pageFrame = (CGRect) { .origin = CGPointMake(pageX, pagingScrollView.bounds.origin.y), .size = pagingScrollView.bounds.size }; UIScrollView *newPage = [self createNewPageFromIndex:i ToPageFrame:pageFrame]; // newPage.contentSize custom set in here [pagingScrollView addSubview:newPage]; pageX += pageFrame.size.width; }


Mi vista consiste en 10 vistas horizontales, y algunas de esas vistas consisten en vistas verticales múltiples, por lo que obtendrá algo como esto:

1.0 2.0 3.1 4.1 2.1 3.1 2.2 2.3

Con paginación habilitada en ambos ejes horizontal y vertical

La vista también tiene los siguientes atributos:

[self setDelaysContentTouches:NO]; [self setMultipleTouchEnabled:NO]; [self setDirectionalLockEnabled:YES];

Ahora, para evitar el desplazamiento en diagonal, haga esto:

-(void)scrollViewDidScroll:(UIScrollView *)scrollView { int pageWidth = 768; int pageHeight = 1024; int subPage =round(self.contentOffset.y / pageHeight); if ((int)self.contentOffset.x % pageWidth != 0 && (int)self.contentOffset.y % pageHeight != 0) { [self setContentOffset:CGPointMake(self.contentOffset.x, subPage * pageHeight]; } }

¡Funciona como un encanto para mí!


Necesita restablecer _isHorizontalScroll a NO en touchesEnded y touchesCancelled .


Para aquellos que buscan en Swift en @AndreyTarantsov la segunda solución es así en el código:

var positionScroll:CGFloat = 0 func scrollViewWillBeginDragging(scrollView: UIScrollView) { positionScroll = self.theScroll.contentOffset.x } func scrollViewDidScroll(scrollView: UIScrollView) { if self.theScroll.contentOffset.x > self.positionScroll || self.theScroll.contentOffset.x < self.positionScroll{ self.theScroll.pagingEnabled = true }else{ self.theScroll.pagingEnabled = false } }

lo que estamos haciendo aquí es mantener la posición actual justo antes de que scrollview se arrastre y luego verificar si x ha aumentado o disminuido en comparación con la posición actual de x. En caso afirmativo, establezca pagingEnabled en verdadero, si no (y ha aumentado / disminuido) luego configure en pagingEnabled en false

Espero que sea útil para los recién llegados!


Para las personas perezosas como yo:

Establezca scrollview.directionalLockEnabled en YES

- (void) scrollViewWillBeginDragging: (UIScrollView *) scrollView { self.oldContentOffset = scrollView.contentOffset; } - (void) scrollViewDidScroll: (UIScrollView *) scrollView { if (scrollView.contentOffset.x != self.oldContentOffset.x) { scrollView.pagingEnabled = YES; scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x, self.oldContentOffset.y); } else { scrollView.pagingEnabled = NO; } } - (void) scrollViewDidEndDecelerating: (UIScrollView *) scrollView { self.oldContentOffset = scrollView.contentOffset; }


Para referencia futura, construí sobre la respuesta de Andrey Tanasov y se me ocurrió la siguiente solución que funciona bien.

Primero defina una variable para almacenar el contentOffset y establézcalo en coordenadas de 0,0

var positionScroll = CGPointMake(0, 0)

Ahora implemente lo siguiente en el delegado de scrollView:

override func scrollViewWillBeginDragging(scrollView: UIScrollView) { // Note that we are getting a reference to self.scrollView and not the scrollView referenced passed by the method // For some reason, not doing this fails to get the logic to work self.positionScroll = (self.scrollView?.contentOffset)! } override func scrollViewDidScroll(scrollView: UIScrollView) { // Check that contentOffset is not nil // We do this because the scrollViewDidScroll method is called on viewDidLoad if self.scrollView?.contentOffset != nil { // The user wants to scroll on the X axis if self.scrollView?.contentOffset.x > self.positionScroll.x || self.scrollView?.contentOffset.x < self.positionScroll.x { // Reset the Y position of the scrollView to what it was BEFORE scrolling started self.scrollView?.contentOffset = CGPointMake((self.scrollView?.contentOffset.x)!, self.positionScroll.y); self.scrollView?.pagingEnabled = true } // The user wants to scroll on the Y axis else { // Reset the X position of the scrollView to what it was BEFORE scrolling started self.scrollView?.contentOffset = CGPointMake(self.positionScroll.x, (self.scrollView?.contentOffset.y)!); self.collectionView?.pagingEnabled = false } } }

Espero que esto ayude.


Podría estar haciendo una reverencia aquí, pero me encontré con esta publicación hoy cuando estaba tratando de resolver el mismo problema. Aquí está mi solución, parece estar funcionando bien.

Quiero mostrar una pantalla llena de contenido, pero permitir que el usuario se desplace a una de las otras 4 pantallas, hacia arriba, hacia la izquierda y hacia la derecha. Tengo un UIScrollView único con un tamaño de 320x480 y un contentSize de 960x1440. El desplazamiento del contenido comienza en 320,480. En scrollViewDidEndDecelerating :, redibujo las 5 vistas (vistas centrales y las 4 a su alrededor) y restablezco el offset de contenido a 320,480.

Aquí está la carne de mi solución, el método scrollViewDidScroll:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView { if (!scrollingVertically && ! scrollingHorizontally) { int xDiff = abs(scrollView.contentOffset.x - 320); int yDiff = abs(scrollView.contentOffset.y - 480); if (xDiff > yDiff) { scrollingHorizontally = YES; } else if (xDiff < yDiff) { scrollingVertically = YES; } } if (scrollingHorizontally) { scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x, 480); } else if (scrollingVertically) { scrollView.contentOffset = CGPointMake(320, scrollView.contentOffset.y); } }


Si no has intentado hacer esto en la beta 3.0, te recomiendo que lo pruebes. Actualmente estoy usando una serie de vistas de tabla dentro de una vista de desplazamiento, y funciona tal como se esperaba. (Bueno, el desplazamiento sí, de todos modos. Encontré esta pregunta cuando buscaba una solución a un problema totalmente diferente ...)



También he tenido que resolver este problema, y ​​aunque la respuesta de Andrey Tarantsov definitivamente tiene mucha información útil para entender cómo funciona UIScrollViews , creo que la solución es un poco complicada. Mi solución, que no implica ninguna subclasificación o reenvío táctil, es la siguiente:

  1. Cree dos vistas de desplazamiento anidado, una para cada dirección.
  2. Cree dos UIPanGestureRecognizers ficticios, nuevamente uno para cada dirección.
  3. Cree dependencias de falla entre las propiedades panGestureRecognizer de UIScrollViews y el UIPanGestureRecognizer ficticio correspondiente a la dirección perpendicular a la dirección de desplazamiento deseada de UIScrollView utilizando UIGestureRecognizer''s -requireGestureRecognizerToFail: selector.
  4. En el -gestureRecognizerShouldBegin: devoluciones de llamada para sus UIPanGestureRecognizers ficticios, calcule la dirección inicial de la panorámica con el gesto -translationInView: selector (sus UIPanGestureRecognizers no llamarán a esta devolución de llamada de delegado hasta que su toque se haya traducido lo suficiente como para registrarse como una panorámica). Permita o no permitir que su reconocedor de gestos comience en función de la dirección calculada, lo que a su vez debería controlar si se permitirá que comience o no el reconocedor de gestos panorámico UIScrollView perpendicular.
  5. In -gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: no permita que sus UIPanGestureRecognizers ficticios se ejecuten junto con el desplazamiento (a menos que tenga algún comportamiento adicional que desee agregar).

Usé el siguiente método para resolver este problema, espero que te ayude.

Primero defina las siguientes variables en el archivo de encabezado de su controlador.

CGPoint startPos; int scrollDirection;

startPos mantendrá el valor contentOffset cuando su delegado reciba el mensaje scrollViewWillBeginDragging . Entonces en este método hacemos esto;

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{ startPos = scrollView.contentOffset; scrollDirection=0; }

luego usamos estos valores para determinar la dirección de desplazamiento deseada por los usuarios en el mensaje scrollViewDidScroll .

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{ if (scrollDirection==0){//we need to determine direction //use the difference between positions to determine the direction. if (abs(startPos.x-scrollView.contentOffset.x)<abs(startPos.y-scrollView.contentOffset.y)){ NSLog(@"Vertical Scrolling"); scrollDirection=1; } else { NSLog(@"Horitonzal Scrolling"); scrollDirection=2; } } //Update scroll position of the scrollview according to detected direction. if (scrollDirection==1) { [scrollView setContentOffset:CGPointMake(startPos.x,scrollView.contentOffset.y) animated:NO]; } else if (scrollDirection==2){ [scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x,startPos.y) animated:NO]; } }

finalmente tenemos que detener todas las operaciones de actualización cuando el usuario termine de arrastrar;

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ if (decelerate) { scrollDirection=3; } }


Si está utilizando el constructor de interfaz para diseñar su interfaz, esto podría hacerse con bastante facilidad.

En el constructor de interfaz, simplemente haga clic en UIScrollView y seleccione la opción (en el inspector) que lo limita a solo desplazarse de una manera.