2009-01-15 8 views
15

Tengo un gráfico dibujado dentro de un UIScrollView. Es uno grande UIView usando una subclase personalizada de CATiledLayer como su capa.¿Cómo reinicio después de un zoom UIScrollView?

Al acercar y alejar el UIScrollView, quiero que el gráfico cambie de tamaño dinámicamente como cuando devuelvo el gráfico de viewForZoomingInScrollView. Sin embargo, el Gráfico se redibuja en el nuevo nivel de zoom, y quiero restablecer la escala de transformación a 1x1 para que la próxima vez que el usuario haga zoom, la transformación comience desde la vista actual. Si restablezco la transformación a Identidad en scrollViewDidEndZooming, funciona en el simulador, pero arroja un EXC_BAD_ACCSES en el dispositivo.

Esto ni siquiera resuelve el problema completamente en el simulador, ya que la próxima vez que el usuario amplía, la transformación se restablece al nivel de zoom en el que estaba, y así parece, si se amplió a 2x , por ejemplo, de repente está en 4x. Cuando termino el zoom, termina en la escala correcta, pero el acto real de acercamiento se ve mal.

Primero: ¿cómo permito que el gráfico se vuelva a dibujar a sí mismo en la escala estándar de 1x1 después de hacer zoom, y cómo puedo hacer un zoom uniforme durante todo el proceso?

Editar: Nuevos hallazgos El error parece ser "[CALayer retainCount]: message sent to deallocated instance"

nunca estoy desasignar las capas mí mismo. Antes, ni siquiera borraba ningún punto de vista ni nada. Este error fue lanzado sobre el zoom y también sobre la rotación. Si elimino el objeto antes de la rotación y lo vuelvo a agregar después, no arroja la excepción. Esta no es una opción para hacer zoom.

Respuesta

41

No puedo ayudarte con el bloqueo, aparte de decirte que verifiques y asegúrate de que no estás autorrealizando involuntariamente una vista o capa en algún lugar dentro de tu código. He visto que el simulador maneja el tiempo de las autorreleas de forma diferente que en el dispositivo (más a menudo cuando hay hilos involucrados).

La escala de la vista es un problema con UIScrollView que me he encontrado. Durante un evento de zoom pellizco, UIScrollView tomará la vista que especificó en el método de delegado viewForZoomingInScrollView: y le aplicará una transformación. Esta transformación proporciona una escala suave de la vista sin tener que volver a dibujar cada cuadro. Al final de la operación de zoom, se llamará a su método de delegado scrollViewDidEndZooming:withView:atScale: y le dará la oportunidad de realizar una representación de su vista más de alta calidad con el nuevo factor de escala. En general, se sugiere que restablezca la transformación en su vista para que sea CGAffineTransformIdentity y luego haga que su vista se vuelva a dibujar manualmente en la nueva escala de tamaño.

Sin embargo, esto causa un problema porque UIScrollView no parece controlar la transformación de la vista de contenido, por lo que en la siguiente operación de zoom establece la transformación de la vista de contenido a cualquiera que sea el factor de escala global. Como ha vuelto a dibujar manualmente su vista en el último factor de escala, aumenta la escala, que es lo que está viendo.

Como solución, utilizo una subclase UIView para mi vista de contenido con los siguientes métodos definidos:

- (void)setTransformWithoutScaling:(CGAffineTransform)newTransform; 
{ 
    [super setTransform:newTransform]; 
} 

- (void)setTransform:(CGAffineTransform)newValue; 
{ 
    [super setTransform:CGAffineTransformScale(newValue, 1.0f/previousScale, 1.0f/previousScale)]; 
} 

donde previousScale es una variable de instancia flotador de la vista. entonces implementar el método delegado zoom de la siguiente manera:

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale; 
{ 
    [contentView setTransformWithoutScaling:CGAffineTransformIdentity]; 
// Code to manually redraw view at new scale here 
    contentView.previousScale = scale; 
    scrollView.contentSize = contentView.frame.size; 
} 

De esta manera, las transformadas enviadas a la vista de contenido se ajustan en base a la escala a la que la vista era la última vuelve a dibujar.Cuando se realiza el zoom de pellizco, la transformación se restablece a una escala de 1.0 al eludir el ajuste en el método normal setTransform:. Esto parece proporcionar el comportamiento correcto de escalado al mismo tiempo que le permite dibujar una vista nítida al completar un zoom.

ACTUALIZACIÓN (23/07/2010): iPhone OS 3.2 y superior han cambiado el comportamiento de las vistas de desplazamiento en lo que respecta a la ampliación. Ahora, un UIScrollView respetará la transformación de identidad que aplica a una vista de contenido y solo proporciona el factor de escala relativo en -scrollViewDidEndZooming:withView:atScale:. Por lo tanto, el código anterior para una subclase UIView solo es necesario para dispositivos que ejecutan versiones de iPhone OS anteriores a la versión 3.2.

+2

Es posible que tenga que ser un poco más específico. ¿Qué errores? Utilizo el código casi exactamente así en una aplicación, y funciona muy bien allí. –

+0

¿por qué la vista de contenido sigue desapareciendo? – domlao

+1

No ha proporcionado mucha información, pero si no está ocultando o eliminando manualmente la vista de contenido de la jerarquía, supongo que se está volviendo más grande que el tamaño de textura admitido por la GPU. Si la vista se vuelve más ancha o más alta que 2048 píxeles, ya no se puede representar en el iPhone y iPhone 3G (no puedo hablar por el 3G S). –

5

Tengo una discusión detallada de cómo (y por qué) UIScrollView zooming funciona en github.com/andreyvit/ScrollingMadness/.

(El enlace también contiene una descripción de cómo enfocar mediante programación UIScrollView, cómo emular las fotos de estilo Biblioteca paginación + zoom + desplazamiento, un proyecto de ejemplo y la clase ZoomScrollView que encapsula algo de la magia de zoom.)

Cita:

UIScrollView no tiene una noción de un "nivel de zoom actual", ya que cada subvista que contiene puede tener su propio nivel de zoom actual. Tenga en cuenta que no hay ningún campo en UIScrollView para mantener el nivel de zoom actual. Sin embargo, sabemos que alguien almacena ese nivel de zoom, porque si pellizcas y alejas una subvista, restableces su transformación a CGAffineTransformIdentity y pellizcas de nuevo, notarás que se ha restaurado el nivel de zoom anterior de la subvista.

De hecho, si observa el desmontaje, es UIView que almacena su propio nivel de zoom (dentro del objeto UIGestureInfo apuntado por el campo _gestureInfo). También tiene un conjunto de buenos métodos no documentados como zoomScale y setZoomScale:animated:. (Tenga en cuenta que también tiene muchos métodos relacionados con la rotación, tal vez recibamos soporte de gestos de rotación algún día pronto).

Sin embargo, si creamos una nueva UIView solo para acercarla y agregamos nuestra vista real de zoomable como su hijo, siempre comenzaremos con el nivel de zoom 1.0. Mi implementación del zoom programático se basa en este truco.

+0

zoomScale es parte de UIScrollView, no UIView. – Jonny

+0

Creo que ya has movido tu contenido de Scrolling Madness? ¿Sería capaz de actualizar el enlace (no he podido encontrar el nuevo hogar). – Benjohn

+1

@Benjohn Oye, quedó totalmente anticuado hace muchos años. Pero ahora puedes restablecer zoomScale en UIScrollView, por lo que este truco ya no es necesario (desde iOS 3 o 4). –

2

Tenga en cuenta que la solución provista por Brad tiene un problema: si continúa haciendo zoom programáticamente (digamos en evento de doble toque) y deja que el usuario manualmente (pellizcar) aleje, después de un tiempo la diferencia entre el verdadero escala y la escala UIScrollView es un seguimiento crecerá demasiado grande, por lo que el previousScale caerá en algún momento fuera de la precisión del flotador que eventualmente dará como resultado un comportamiento impredecible

3

Un enfoque bastante confiable, apropiado para iOS 3.2 y 4.0 y posteriores, es como sigue. Debe estar preparado para proporcionar su vista escalable (la subvista principal de la vista de desplazamiento) en cualquier escala solicitada. Luego, en scrollViewDidEndZooming:, eliminará la versión borrosa transformada a escala de esta vista y la reemplazará con una nueva vista dibujada en la nueva escala.

Necesitará:

  • Un Ivar para el mantenimiento de la escala anterior; vamos a llamarlo oldScale

  • Una forma de identificar el punto de vista escalable, tanto para viewForZoomingInScrollView: y para scrollViewDidEndZooming:; aquí, voy a aplicar una etiqueta (999)

  • Método que crea la vista escalable, la etiqueta y la inserta en la vista de desplazamiento (aquí la llamaré addNewScalableViewAtScale:).Recuerde, debe hacer todo de acuerdo con la escala: dimensiona la vista y sus subvistas o dibujos, y ajusta el tamaño del contenido de la vista de desplazamiento para que coincida.

  • Constantes para el mínimoZoomScale y maximumZoomScale. Estos son necesarios porque cuando reemplazamos la vista en escala por la versión más grande y detallada, el sistema va a pensar que la escala actual es 1, por lo que debemos engañar para permitir al usuario escalar la vista hacia abajo.

Muy bien entonces. Cuando crea la vista de desplazamiento, inserta la vista escalable en la escala 1.0. Esto podría ser en su viewDidLoad, por ejemplo (sv es la vista de desplazamiento):

[self addNewScalableViewAtScale: 1.0]; 
sv.minimumZoomScale = MIN; 
sv.maximumZoomScale = MAX; 
self->oldScale = 1.0; 
sv.delegate = self; 

Luego, en su scrollViewDidEndZooming: haces lo mismo, pero compensando el cambio de escala:

UIView* v = [sv viewWithTag:999]; 
[v removeFromSuperview]; 
CGFloat newscale = scale * self->oldScale; 
self->oldScale = newscale; 
[self addNewScalableViewAtScale:newscale]; 
sv.minimumZoomScale = MIN/newscale; 
sv.maximumZoomScale = MAX/newscale; 
12

Gracias a todas las respuestas anteriores, y aquí está mi solución.
Implementar UIScrollViewDelegate métodos:

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView 
{ 
    return tmv; 
} 

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale 
{ 
    CGPoint contentOffset = [tmvScrollView contentOffset]; 
    CGSize contentSize = [tmvScrollView contentSize]; 
    CGSize containerSize = [tmv frame].size; 

    tmvScrollView.maximumZoomScale = tmvScrollView.maximumZoomScale/scale; 
    tmvScrollView.minimumZoomScale = tmvScrollView.minimumZoomScale/scale; 

    previousScale *= scale; 

    [tmvScrollView setZoomScale:1.0f]; 

    [tmvScrollView setContentOffset:contentOffset]; 
    [tmvScrollView setContentSize:contentSize]; 
    [tmv setFrame:CGRectMake(0, 0, containerSize.width, containerSize.height)]; 

    [tmv reloadData]; 
} 
  • TMV es mi subclase de UIView
  • tmvScrollView - salida a UIScrollView
  • conjunto maximumZoomScale y minimumZoomScale antes
  • crear previousScale variable de instancia y establezca su valor a 1.0f

Obras para mí perfectamente.

BR, Eugene.

+0

Me enfrenté a una situación similar. Tu solución resolvió mi problema, gracias. +1 representante va a los tuyos :) – fyasar

2

Estaba buscando restablecer en la carga a la escala de zoom predeterminada, porque cuando la acerco, scrollview tiene la misma escala de zoom para la próxima vez hasta que se desasigna.

-(void) viewWillAppear:(BOOL)animated { 
     [self.scrollView setZoomScale:1.0f]; 
} 

Cambió el juego.

Cuestiones relacionadas