Centrar el contenido de UIScrollView cuando sea más pequeño

140

Tengo un UIImageViewinterior UIScrollViewque utilizo para hacer zoom y desplazarme. Si la imagen / contenido de la vista de desplazamiento es más grande que la vista de desplazamiento, todo funciona bien. Sin embargo, cuando la imagen se vuelve más pequeña que la vista de desplazamiento, se adhiere a la esquina superior izquierda de la vista de desplazamiento. Me gustaría mantenerlo centrado, como la aplicación Fotos.

¿Alguna idea o ejemplo sobre cómo mantener el contenido UIScrollViewcentrado cuando es más pequeño?

Estoy trabajando con iPhone 3.0.

El siguiente código casi funciona. La imagen vuelve a la esquina superior izquierda si la pellizco después de alcanzar el nivel mínimo de zoom.

- (void)loadView {
    [super loadView];

    // set up main scroll view
    imageScrollView = [[UIScrollView alloc] initWithFrame:[[self view] bounds]];
    [imageScrollView setBackgroundColor:[UIColor blackColor]];
    [imageScrollView setDelegate:self];
    [imageScrollView setBouncesZoom:YES];
    [[self view] addSubview:imageScrollView];

    UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"WeCanDoIt.png"]];
    [imageView setTag:ZOOM_VIEW_TAG];
    [imageScrollView setContentSize:[imageView frame].size];
    [imageScrollView addSubview:imageView];

    CGSize imageSize = imageView.image.size;
    [imageView release];

    CGSize maxSize = imageScrollView.frame.size;
    CGFloat widthRatio = maxSize.width / imageSize.width;
    CGFloat heightRatio = maxSize.height / imageSize.height;
    CGFloat initialZoom = (widthRatio > heightRatio) ? heightRatio : widthRatio;

    [imageScrollView setMinimumZoomScale:initialZoom];
    [imageScrollView setZoomScale:1];

    float topInset = (maxSize.height - imageSize.height) / 2.0;
    float sideInset = (maxSize.width - imageSize.width) / 2.0;
    if (topInset < 0.0) topInset = 0.0;
    if (sideInset < 0.0) sideInset = 0.0;
    [imageScrollView setContentInset:UIEdgeInsetsMake(topInset, sideInset, -topInset, -sideInset)];
}

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
    return [imageScrollView viewWithTag:ZOOM_VIEW_TAG];
}

/************************************** NOTE **************************************/
/* The following delegate method works around a known bug in zoomToRect:animated: */
/* In the next release after 3.0 this workaround will no longer be necessary      */
/**********************************************************************************/
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
    [scrollView setZoomScale:scale+0.01 animated:NO];
    [scrollView setZoomScale:scale animated:NO];
    // END Bug workaround

    CGSize maxSize = imageScrollView.frame.size;
    CGSize viewSize = view.frame.size;
    float topInset = (maxSize.height - viewSize.height) / 2.0;
    float sideInset = (maxSize.width - viewSize.width) / 2.0;
    if (topInset < 0.0) topInset = 0.0;
    if (sideInset < 0.0) sideInset = 0.0;
    [imageScrollView setContentInset:UIEdgeInsetsMake(topInset, sideInset, -topInset, -sideInset)];
}
hpique
fuente
¿Alguna vez resolviste este problema por completo? Estoy luchando con el mismo problema.
Jonás
1
Atención: use el valor initialZoom para calcular el recuadro si NO es uno (sin zoom). Por ejemplo, use estas líneas: float topInset = (maxSize.height - imageSize.height * initialZoom) / 2.0; flotante sideInset = (maxSize.width - imageSize.width * initialZoom) / 2.0; y finalmente establezca el valor de zoom inicial [imageScrollView setZoomScale: initialZoom];
Felix

Respuestas:

228

¡Tengo una solución muy simple! Todo lo que necesita es actualizar el centro de su subvista (vista de imagen) mientras hace zoom en ScrollViewDelegate. Si la imagen ampliada es más pequeña que la vista de desplazamiento, ajuste subview.center center, el centro es (0,0).

- (void)scrollViewDidZoom:(UIScrollView *)scrollView 
{
    UIView *subView = [scrollView.subviews objectAtIndex:0];

    CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
    CGFloat offsetY = MAX((scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5, 0.0);

    subView.center = CGPointMake(scrollView.contentSize.width * 0.5 + offsetX, 
                                 scrollView.contentSize.height * 0.5 + offsetY);
}
Erdemus
fuente
55
Para mí, esta solución es más desigual que usar NYOBetterZoom de Liam. Tal vez depende del tamaño de la imagen, etc. La moraleja; use la solución que mejor se adapte a sus necesidades
wuf810
2
Stackoverflow GOLD STAR para esto. Estaba luchando con esto, pero la solución es muy simple.
n13
2
para simplificar un poco:CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
Marek R
66
Este ajuste me ayudó cuando la vista de desplazamiento tenía inserciones: CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentInset.left - scrollView.contentInset.right - scrollView.contentSize.width) * 0.5, 0.0); CGFloat offsetY = MAX((scrollView.bounds.size.height - scrollView.contentInset.top - scrollView.contentInset.bottom - scrollView.contentSize.height) * 0.5, 0.0);
Kieran Harper
3
Me di cuenta de que esto, como muchas otras técnicas de centrado, parece sufrir problemas al usarlo zoomToRect:. El uso del contentInsetenfoque funciona mejor, si es que necesita esa funcionalidad. Visite petersteinberger.com/blog/2013/how-to-center-uiscrollview para más detalles.
Matej Bukovinski
52

La respuesta de @ EvelynCordner fue la que mejor funcionó en mi aplicación. Mucho menos código que las otras opciones también.

Aquí está la versión Swift si alguien la necesita:

func scrollViewDidZoom(_ scrollView: UIScrollView) {
    let offsetX = max((scrollView.bounds.width - scrollView.contentSize.width) * 0.5, 0)
    let offsetY = max((scrollView.bounds.height - scrollView.contentSize.height) * 0.5, 0)
    scrollView.contentInset = UIEdgeInsetsMake(offsetY, offsetX, 0, 0)
}
William T.
fuente
44
¡Este funciona muy bien! La vista incluso se animó correctamente después de reducirse.
Carter Medlin
44
Puse este código en un func y también lo llamé en viewDidLayoutSubviews para asegurarme de que se configuró correctamente inicialmente.
Carter Medlin
Buena llamada @CarterMedlin me ayudó mucho con mi carga inicial de Swift 3.
AJ Hernández
Esto parece funcionar, pero no puedo entender por qué, ya que siempre parece calcular las mismas inserciones: los límites de la vista de desplazamiento son fijos, al igual que el tamaño del contenido.
Vaddadi Kartick
contentSize cambia al hacer zoom en scrollView size solo es fijo
Michał Ziobro
25

De acuerdo, he estado luchando contra esto durante los últimos dos días, y finalmente he llegado a una solución bastante confiable (hasta ahora ...), pensé que debería compartirlo y ahorrarle un poco de dolor a los demás. :) Si encuentra algún problema con esta solución, ¡grite!

Básicamente he pasado por lo que todos los demás tienen: buscando en StackOverflow, los foros de desarrolladores de Apple, miré el código para three20, ScrollingMadness, ScrollTestSuite, etc. Intenté ampliar el marco UIImageView, jugando con el desplazamiento y / o las inserciones de UIScrollView desde el ViewController, etc. pero nada funcionó muy bien (como todos los demás también lo han descubierto).

Después de dormir, probé un par de ángulos alternativos:

  1. Subclasificar el UIImageView para que modifique su propio tamaño dinámicamente; esto no funcionó en absoluto.
  2. Subclasificar el UIScrollView para que altere su propio contenido Offset dinámicamente: este es el que parece ser un ganador para mí.

Con este método de subclases UIScrollView, estoy anulando el mutador contentOffset para que no se configure {0,0} cuando la imagen se escala más pequeña que la ventana gráfica, sino que establece el desplazamiento de modo que la imagen se mantenga centrada en la ventana gráfica. Hasta ahora, siempre parece funcionar. Lo he comprobado con imágenes anchas, altas, pequeñas y grandes y no tiene el problema "funciona pero pellizcar con el zoom mínimo lo rompe".

He subido un proyecto de ejemplo a github que usa esta solución, puede encontrarlo aquí: http://github.com/nyoron/NYOBetterZoom

Liam Jones
fuente
2
Para aquellos interesados: he actualizado el proyecto vinculado anteriormente, por lo que depende un poco menos de que ViewController haga "lo correcto", el UIScrollView personalizado se encarga de más detalles.
Liam Jones el
2
Liam, eres genial. Lo digo como autor de ScrollingMadness. Por CIERTO en iPad en 3.2+ configuración de contenidoInstablecer en scrollViewDidZoom (un nuevo método de delegado 3.2+) Simplemente funciona.
Andrey Tarantsov
Muy ordenado pedazo de código. He estado rascándome la cabeza con esto por un tiempo. Se agregó el proyecto a handyiphonecode.com
samvermette
Esto es genial. Gracias. Sin embargo, no compensaba que mi UIStatusBar estuviera oculto, así que cambié la línea anOffset.y = -(scrollViewSize.height - zoomViewSize.height) / 2.0aanOffset.y = (-(scrollViewSize.height - zoomViewSize.height) / 2.0) + 10;
Gordon Fontenot el
La solución dada bi Erdemus es mucho más simple en mi opinión.
gyozo kudor
23

Este código debería funcionar en la mayoría de las versiones de iOS (y ha sido probado para funcionar en 3.1 hacia arriba).

Se basa en el código WWDC de Apple para el photoscoller.

Agregue lo siguiente a su subclase de UIScrollView, y reemplace tileContainerView con la vista que contiene su imagen o mosaicos:

- (void)layoutSubviews {
    [super layoutSubviews];

    // center the image as it becomes smaller than the size of the screen
    CGSize boundsSize = self.bounds.size;
    CGRect frameToCenter = tileContainerView.frame;

    // center horizontally
    if (frameToCenter.size.width < boundsSize.width)
        frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2;
    else
        frameToCenter.origin.x = 0;

    // center vertically
    if (frameToCenter.size.height < boundsSize.height)
        frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2;
    else
        frameToCenter.origin.y = 0;

    tileContainerView.frame = frameToCenter;
}
JosephH
fuente
3
Esta debería ser la respuesta aceptada, porque es más simple. Gracias. ¡¡¡¡¡¡Me salvaste la vida!!!!!! : D
Duck
Después de eliminar todas las llamadas de API iOS 3.2+, la lógica de centrado solo parece funcionar en dispositivos con iOS 3.2+ y no 3.1.3 (jumpy, flickery, obtiene un desplazamiento aleatorio). He comparado las salidas de origen y tamaño de trama entre 3.1.3 y 3.2+ y, aunque coinciden, por alguna razón, la vista secundaria todavía se posiciona incorrectamente. Muy raro. Solo la respuesta de Liam Jone funcionó para mí.
David H
1
Ambas soluciones @Erdemus y @JosephH funcionan, pero el UIScrollViewenfoque de subclase parece preferible: se invoca cuando la vista se muestra por primera vez + continuamente mientras el zoom está en progreso (mientras scrollViewDidZoomque solo se invoca una vez por desplazamiento y después del hecho)
SwiftArchitect
22

Para obtener una solución más adecuada para las vistas de desplazamiento que usan autolayout, use inserciones de contenido de la vista de desplazamiento en lugar de actualizar los marcos de las subvistas de su vista de desplazamiento.

- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
    CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
    CGFloat offsetY = MAX((scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5, 0.0);

    self.scrollView.contentInset = UIEdgeInsetsMake(offsetY, offsetX, 0.f, 0.f);
}
Evelyn Cordner
fuente
1
Esto funcionó para mí, sin embargo, si la imagen es más pequeña para empezar, de alguna manera este código (cuando se llama directamente) no actualizó la vista de desplazamiento. TODO lo que necesitaba era poner en primer lugar el UIViewque está dentro de la UIScrollviewal mismo centro ( view.center = scrollview.center;) y luego en el scrollViewDidZoomconjunto de la view.frame.origin xy yde 0nuevo.
morksinaanab
También funciona bien para mí, pero hice una dispatch_asynccola en la cola principal viewWillAppeardonde llamo scrollViewDidZoom:a mi vista de desplazamiento principal. Esto hace que la vista aparezca con imágenes centradas.
Pascal
Realice una llamada a este código viewDidLayoutSubviewspara asegurarse de que esté configurado correctamente cuando la vista se muestre por primera vez.
Carter Medlin
21

Actualmente estoy subclasificando UIScrollViewy anulando setContentOffset:para ajustar el desplazamiento según contentSize. Funciona tanto con pellizco como con zoom programático.

@implementation HPCenteringScrollView

- (void)setContentOffset:(CGPoint)contentOffset
{
    const CGSize contentSize = self.contentSize;
    const CGSize scrollViewSize = self.bounds.size;

    if (contentSize.width < scrollViewSize.width)
    {
        contentOffset.x = -(scrollViewSize.width - contentSize.width) / 2.0;
    }

    if (contentSize.height < scrollViewSize.height)
    {
        contentOffset.y = -(scrollViewSize.height - contentSize.height) / 2.0;
    }

    [super setContentOffset:contentOffset];
}

@end

Además de ser breve y dulce, este código produce un zoom mucho más suave que la solución @Erdemus. Puede verlo en acción en la demostración de RMGallery .

hpique
fuente
66
No necesita subclasificar UIScrollView para implementar este método. Apple le permite ver eventos de desplazamiento y zoom en los métodos de delegado. Específicamente: - (vacío) scrollViewDidZoom: (UIScrollView *) scrollView;
Rog
1
La mejor solución que he visto hasta ahora (funciona incluso cuando se usan restricciones).
Frizlab
12

Pasé un día luchando con este problema y terminé implementando scrollViewDidEndZooming: withView: atScale: como sigue:

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
    CGFloat screenWidth = [[UIScreen mainScreen] bounds].size.width;
    CGFloat screenHeight = [[UIScreen mainScreen] bounds].size.height;
    CGFloat viewWidth = view.frame.size.width;
    CGFloat viewHeight = view.frame.size.height;

    CGFloat x = 0;
    CGFloat y = 0;

    if(viewWidth < screenWidth) {
        x = screenWidth / 2;
    }
    if(viewHeight < screenHeight) {
        y = screenHeight / 2 ;
    }

    self.scrollView.contentInset = UIEdgeInsetsMake(y, x, y, x);
}

Esto garantiza que cuando la imagen sea más pequeña que la pantalla, todavía haya espacio suficiente a su alrededor para que pueda colocarla en el lugar exacto que desee.

(suponiendo que su UIScrollView contiene un UIImageView para contener la imagen)

Esencialmente, lo que esto hace es verificar si el ancho / alto de la vista de la imagen es más pequeño que el ancho / alto de la pantalla, y si es así, crear un recuadro de la mitad del ancho / alto de la pantalla (probablemente podría aumentarlo si desea que la imagen salir de los límites de la pantalla).

Tenga en cuenta que dado que este es un método UIScrollViewDelegate , no olvide agregarlo a la declaración de su controlador de vista, para evitar problemas de compilación.

Vicario
fuente
7

Apple lanzó los videos de la sesión WWDC 2010 a todos los miembros del programa para desarrolladores de iPhone. ¡Uno de los temas discutidos es cómo crearon la aplicación de fotos! Construyen una aplicación muy similar paso a paso y han hecho que todo el código esté disponible de forma gratuita.

Tampoco utiliza API privada. No puedo poner ninguno de los códigos aquí debido al acuerdo de no divulgación, pero aquí hay un enlace a la descarga del código de muestra. Probablemente necesitará iniciar sesión para obtener acceso.

http://connect.apple.com/cgi-bin/WebObjects/MemberSite.woa/wa/getSoftware?code=y&source=x&bundleID=20645

Y, aquí hay un enlace a la página iTunes WWDC:

http://insideapple.apple.com/redir/cbx-cgi.do?v=2&la=en&lc=&a=kGSol9sgPHP%2BtlWtLp%2BEP%2FnxnZarjWJglPBZRHd3oDbACudP51JNGS8KlsFgxZto9X%2BTsnqSbeUSWX0doe%2Fzv%2FN5XV55%2FomsyfRgFBysOnIVggO%2Fn2p%2BiweDK%2F%2FmsIXj

Jonás
fuente
1
El ejemplo en cuestión es MyImagePicker y, curiosamente, presenta el mismo problema.
Colin Barrett el
1
Debería haber sido más claro. El ejemplo en cuestión es en realidad "PhotoScroller" y no "MyImagePicker". Tienes razón en que "MyImagePicker" no funciona correctamente. Pero, "PhotoScroller" sí. Intentalo.
Jonás
¿Recuerdas quizás el título del video de WWDC donde discuten el Photoscroller?
Grzegorz Adam Hankiewicz
1
Se llama "Diseño de aplicaciones con vistas de desplazamiento".
Jonás
2

Ok, esta solución me está funcionando. Tengo una subclase de UIScrollViewcon una referencia a lo UIImageViewque se muestra. Cada vez que se UIScrollViewhace zoom, se ajusta la propiedad contentSize. Es en el setter que escalo UIImageViewapropiadamente y también ajusto su posición central.

-(void) setContentSize:(CGSize) size{
CGSize lSelfSize = self.frame.size;
CGPoint mid;
if(self.zoomScale >= self.minimumZoomScale){
    CGSize lImageSize = cachedImageView.initialSize;
    float newHeight = lImageSize.height * self.zoomScale;

    if (newHeight < lSelfSize.height ) {
        newHeight = lSelfSize.height;
    }
    size.height = newHeight;

    float newWidth = lImageSize.width * self.zoomScale;
    if (newWidth < lSelfSize.width ) {
        newWidth = lSelfSize.width;
    }
    size.width = newWidth;
    mid = CGPointMake(size.width/2, size.height/2);

}
else {
    mid = CGPointMake(lSelfSize.width/2, lSelfSize.height/2);
}

cachedImageView.center = mid;
[super  setContentSize:size];
[self printLocations];
NSLog(@"zoom %f setting size %f x %f",self.zoomScale,size.width,size.height);
}

Cada vez que configuro la imagen, la cambio de UIScrollViewtamaño. El UIScrollViewen la vista de desplazamiento también es una clase personalizada que creé.

-(void) resetSize{
    if (!scrollView){//scroll view is view containing imageview
        return;
    }

    CGSize lSize = scrollView.frame.size;

    CGSize lSelfSize = self.image.size; 
    float lWidth = lSize.width/lSelfSize.width;
    float lHeight = lSize.height/lSelfSize.height;

    // choose minimum scale so image width fits screen
    float factor  = (lWidth<lHeight)?lWidth:lHeight;

    initialSize.height = lSelfSize.height  * factor;
    initialSize.width = lSelfSize.width  * factor;

    [scrollView setContentSize:lSize];
    [scrollView setContentOffset:CGPointZero];
    scrollView.userInteractionEnabled = YES;
}

Con estos dos métodos puedo tener una vista que se comporta igual que la aplicación de fotos.

AHA
fuente
Esto parece hacer el truco, y es una solución bastante simple. Algunas transiciones de zoom no son perfectas, pero creo que se puede arreglar. Voy a experimentar un poco más.
hpique
La solución estaba clara antes de la actualización. Ahora es un poco confuso. ¿Puedes aclarar?
hpique
2

La forma en que lo hice es agregar una vista adicional a la jerarquía:

UIScrollView -> UIView -> UIImageView

Dale UIViewla misma relación de aspecto que la tuya UIScrollViewy céntrate UIImageViewen eso.

hatfinch
fuente
Gracias, hatfinch. Intentaremos esto también. ¿Puede publicar un código de muestra o cambiar mi código de muestra para mostrar cómo construye la jerarquía de vistas?
hpique
Este tipo de trabajos. Excepto por el hecho de que debido a que UIView es del tamaño de UIScrollView, si la imagen es más pequeña (es decir, horizontal en lugar de vertical) puede desplazar parte de la imagen fuera de la pantalla. La aplicación de fotos no permite esto y se ve mucho mejor.
Jonás
2

Sé que algunas respuestas anteriores son correctas, pero solo quiero dar mi respuesta con alguna explicación, los comentarios te harán entender por qué nos gusta esto.

Cuando cargo el scrollView por primera vez, escribo el siguiente código para que sea el centro, tenga en cuenta que configuramos contentOffsetprimero, luegocontentInset

    scrollView.maximumZoomScale = 8
    scrollView.minimumZoomScale = 1

    // set vContent frame
    vContent.frame = CGRect(x: 0,
                            y: 0  ,
                            width: vContentWidth,
                            height: vContentWidth)
    // set scrollView.contentSize
    scrollView.contentSize = vContent.frame.size

    //on the X direction, if contentSize.width > scrollView.bounds.with, move scrollView from 0 to offsetX to make it center(using `scrollView.contentOffset`)
    // if not, don't need to set offset, but we need to set contentInset to make it center.(using `scrollView.contentInset`)
    // so does the Y direction.
    let offsetX = max((scrollView.contentSize.width - scrollView.bounds.width) * 0.5, 0)
    let offsetY = max((scrollView.contentSize.height - scrollView.bounds.height) * 0.5, 0)
    scrollView.contentOffset = CGPoint(x: offsetX, y: offsetY)

    let topX = max((scrollView.bounds.width - scrollView.contentSize.width) * 0.5, 0)
    let topY = max((scrollView.bounds.height - scrollView.contentSize.height) * 0.5, 0)
    scrollView.contentInset = UIEdgeInsets(top: topY, left: topX, bottom: 0, right: 0)

Luego, cuando pellizco vContent, escribo el siguiente código para que sea el centro.

func scrollViewDidZoom(_ scrollView: UIScrollView) {
    //we just need to ensure that the content is in the center when the contentSize is less than scrollView.size.
    let topX = max((scrollView.bounds.width - scrollView.contentSize.width) * 0.5, 0)
    let topY = max((scrollView.bounds.height - scrollView.contentSize.height) * 0.5, 0)
    scrollView.contentInset = UIEdgeInsets(top: topY, left: topX, bottom: 0, right: 0)
}
Changwei
fuente
2

Si contentInsetno se necesita para otra cosa, se puede usar para centrar el contenido de scrollview.

class ContentCenteringScrollView: UIScrollView {

    override var bounds: CGRect {
        didSet { updateContentInset() }
    }

    override var contentSize: CGSize {
        didSet { updateContentInset() }
    }

    private func updateContentInset() {
        var top = CGFloat(0)
        var left = CGFloat(0)
        if contentSize.width < bounds.width {
            left = (bounds.width - contentSize.width) / 2
        }
        if contentSize.height < bounds.height {
            top = (bounds.height - contentSize.height) / 2
        }
        contentInset = UIEdgeInsets(top: top, left: left, bottom: top, right: left)
    }
}

Ventaja si este enfoque es que aún puede usar contentLayoutGuidepara colocar contenido dentro de la vista de desplazamiento

scrollView.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
    imageView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
    imageView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
    imageView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
    imageView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor)
])

o simplemente arrastre y suelte el contenido en el Creador de interfaces de Xcode.

Wojciech Nagrodzki
fuente
1

Puede ver la contentSizepropiedad de UIScrollView (mediante la observación de valores-clave o similar) y ajustar automáticamente contentInsetcada vez que los contentSizecambios sean menores que el tamaño de la vista de desplazamiento.

Tim
fuente
Intentará. ¿Es posible hacer esto con los métodos UIScrollViewDelegate en lugar de observar contentSize?
hpique
Más probable; usaría - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale(descrito en developer.apple.com/iphone/library/documentation/UIKit/… ), pero preferiría observar contentSizeporque de lo contrario terminará descartando la nueva escala de zoom y de todos modos la encontrará en la vista. (A menos que pueda realizar algunas matemáticas increíbles basadas en los tamaños relativos de sus vistas.)
Tim
Gracias Tim. scrollViewDidEndZooming es lo que estoy usando. Ver el código en cuestión (lo he agregado después de su primera respuesta). Casi funciona. El único problema que queda es que si pellizco la imagen después de alcanzar el valor mínimo de ZoomScale, vuelve a la esquina superior izquierda.
hpique
¿Quiere decir que funciona (centra la imagen) para pellizcar desde cualquier escala que no sea la escala de zoom mínima, y ​​solo se rompe si intenta pellizcarla nuevamente una vez que esté en la escala mínima? ¿O no funciona para pellizcar a la escala mínima?
Tim
El primero. Centra la imagen para pellizcar desde cualquier escala que no sea la escala de zoom mínima, y ​​se rompe si intenta pellizcarla nuevamente una vez que esté en la escala mínima. Además, cuando alcanza la escala de zoom mínima la primera vez, el contenido se anima brevemente como si viniera desde la parte superior, lo que causa un breve efecto extraño. Esto sucede en el simulador 3.0 al menos.
hpique
1

Una forma elegante de centrar el contenido UISCrollViewes esta.

Agregue un observador al contenido Tamaño de su UIScrollView, por lo que este método se llamará cada vez que cambie el contenido ...

[myScrollView addObserver:delegate 
               forKeyPath:@"contentSize"
                  options:(NSKeyValueObservingOptionNew) 
                  context:NULL];

Ahora en su método de observador:

- (void)observeValueForKeyPath:(NSString *)keyPath   ofObject:(id)object   change:(NSDictionary *)change   context:(void *)context { 

    // Correct Object Class.
    UIScrollView *pointer = object;

    // Calculate Center.
    CGFloat topCorrect = ([pointer bounds].size.height - [pointer viewWithTag:100].bounds.size.height * [pointer zoomScale])  / 2.0 ;
            topCorrect = ( topCorrect < 0.0 ? 0.0 : topCorrect );

    topCorrect = topCorrect - (  pointer.frame.origin.y - imageGallery.frame.origin.y );

    // Apply Correct Center.
    pointer.center = CGPointMake(pointer.center.x,
                                 pointer.center.y + topCorrect ); }
  • Deberías cambiar el [pointer viewWithTag:100]. Reemplazar por su vista de contenido UIView.

    • También cambie la imageGalleryorientación hacia el tamaño de su ventana.

Esto corregirá el centro del contenido cada vez que cambie su tamaño.

NOTA: La única forma en que este contenido no funciona muy bien es con la funcionalidad de zoom estándar de UIScrollView.

Equipo de desarrollo de SEQOY
fuente
No se pudo hacer que esto funcione. Parece que estás centrando scrollView y no su contenido. ¿Por qué importa el tamaño de la ventana? ¿No debería el código corregir la posición x también?
hpique
1

Esta es mi solución a ese problema que funciona bastante bien para cualquier tipo de vista dentro de una vista de desplazamiento.

-(void)scrollViewDidZoom:(__unused UIScrollView *)scrollView 
    {
    CGFloat top;
    CGFloat left;
    CGFloat bottom;
    CGFloat right;

    if (_scrollView.contentSize.width < scrollView.bounds.size.width) {
        DDLogInfo(@"contentSize %@",NSStringFromCGSize(_scrollView.contentSize));

        CGFloat width = (_scrollView.bounds.size.width-_scrollView.contentSize.width)/2.0;

        left = width;
        right = width;


    }else {
        left = kInset;
        right = kInset;
    }

    if (_scrollView.contentSize.height < scrollView.bounds.size.height) {

        CGFloat height = (_scrollView.bounds.size.height-_scrollView.contentSize.height)/2.0;

        top = height;
        bottom = height;

    }else {
        top = kInset;
        right = kInset;
    }

    _scrollView.contentInset = UIEdgeInsetsMake(top, left, bottom, right);



  if ([self.tiledScrollViewDelegate respondsToSelector:@selector(tiledScrollViewDidZoom:)])
  {
        [self.tiledScrollViewDelegate tiledScrollViewDidZoom:self];
  }
}
beat843796
fuente
1

Hay muchas soluciones aquí, pero me arriesgaría a poner las mías. Es bueno por dos razones: no ensucia la experiencia de zoom, como lo haría al actualizar el marco de vista de imagen en progreso, y también respeta las inserciones de vista de desplazamiento originales (por ejemplo, definidas en xib o storyboard para el manejo elegante de barras de herramientas semitransparentes, etc.) .

Primero, defina un pequeño ayudante:

CGSize CGSizeWithAspectFit(CGSize containerSize, CGSize contentSize) {
    CGFloat containerAspect = containerSize.width / containerSize.height,
            contentAspect = contentSize.width / contentSize.height;

    CGFloat scale = containerAspect > contentAspect
                    ? containerSize.height / contentSize.height
                    : containerSize.width / contentSize.width;

    return CGSizeMake(contentSize.width * scale, contentSize.height * scale);
}

Para retener inserciones originales, campo definido:

UIEdgeInsets originalScrollViewInsets;

Y en algún lugar de viewDidLoad llénalo:

originalScrollViewInsets = self.scrollView.contentInset;

Para colocar UIImageView en UIScrollView (suponiendo que UIImage se encuentre en loadedImage var):

CGSize containerSize = self.scrollView.bounds.size;
containerSize.height -= originalScrollViewInsets.top + originalScrollViewInsets.bottom;
containerSize.width -= originalScrollViewInsets.left + originalScrollViewInsets.right;

CGSize contentSize = CGSizeWithAspectFit(containerSize, loadedImage.size);

UIImageView *imageView = [[UIImageView alloc] initWithFrame:(CGRect) { CGPointZero, contentSize }];
imageView.autoresizingMask = UIViewAutoresizingNone;
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.image = loadedImage;

[self.scrollView addSubview:imageView];
self.scrollView.contentSize = contentSize;

[self centerImageViewInScrollView];

scrollViewDidZoom: de UIScrollViewDelegate para esa vista de desplazamiento:

- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
    if (scrollView == self.scrollView) {
        [self centerImageViewInScrollView];
    }
}

Y finalmente, centrándose:

- (void)centerImageViewInScrollView {
    CGFloat excessiveWidth = MAX(0.0, self.scrollView.bounds.size.width - self.scrollView.contentSize.width),
            excessiveHeight = MAX(0.0, self.scrollView.bounds.size.height - self.scrollView.contentSize.height),
            insetX = excessiveWidth / 2.0,
            insetY = excessiveHeight / 2.0;

    self.scrollView.contentInset = UIEdgeInsetsMake(
            MAX(insetY, originalScrollViewInsets.top),
            MAX(insetX, originalScrollViewInsets.left),
            MAX(insetY, originalScrollViewInsets.bottom),
            MAX(insetX, originalScrollViewInsets.right)
    );
}

Todavía no probé el cambio de orientación (es decir, la reacción adecuada para cambiar el tamaño de UIScrollView), pero solucionarlo debería ser relativamente fácil.

jazzcat
fuente
1

Verá que la solución publicada por Erdemus funciona, pero ... Hay algunos casos en los que el método scrollViewDidZoom no se invoca y su imagen está pegada en la esquina superior izquierda. Una solución simple es invocar explícitamente el método cuando inicialmente muestra una imagen, como esta:

[self scrollViewDidZoom: scrollView];

En muchos casos, puede invocar este método dos veces, pero esta es una solución más limpia que algunas de las otras respuestas en este tema.

rusos
fuente
1

El ejemplo de Apple Photo Scroller hace exactamente lo que estás buscando. Ponga esto en su subclase UIScrollView y cambie _zoomView para que sea su UIImageView.

-(void)layoutSubviews{
  [super layoutSubviews];
  // center the zoom view as it becomes smaller than the size of the screen
  CGSize boundsSize = self.bounds.size;
  CGRect frameToCenter = self.imageView.frame;
  // center horizontally
  if (frameToCenter.size.width < boundsSize.width){
     frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2;
  }else{
    frameToCenter.origin.x = 0;
  }
  // center vertically
  if (frameToCenter.size.height < boundsSize.height){
     frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2;
  }else{
    frameToCenter.origin.y = 0;
  }
  self.imageView.frame = frameToCenter; 
}

Código de muestra de Apple Photo Scroller

Korey Hinton
fuente
1

Para que la animación fluya bien, configure

self.scrollview.bouncesZoom = NO;

y use esta función (encontrar el centro usando el método en esta respuesta )

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale {
    [UIView animateWithDuration:0.2 animations:^{
        float offsetX = MAX((scrollView.bounds.size.width-scrollView.contentSize.width)/2, 0);
        float offsetY = MAX((scrollView.bounds.size.height-scrollView.contentSize.height)/2, 0);
        self.imageCoverView.center = CGPointMake(scrollView.contentSize.width*0.5+offsetX, scrollView.contentSize.height*0.5+offsetY);
    }];
}

Esto crea el efecto de rebote pero no implica ningún movimiento repentino de antemano.

ldanilek
fuente
1

Solo la respuesta aprobada en forma rápida, pero sin subclasificar con el delegado

func centerScrollViewContents(scrollView: UIScrollView) {
    let contentSize = scrollView.contentSize
    let scrollViewSize = scrollView.frame.size;
    var contentOffset = scrollView.contentOffset;

    if (contentSize.width < scrollViewSize.width) {
        contentOffset.x = -(scrollViewSize.width - contentSize.width) / 2.0
    }

    if (contentSize.height < scrollViewSize.height) {
        contentOffset.y = -(scrollViewSize.height - contentSize.height) / 2.0
    }

    scrollView.setContentOffset(contentOffset, animated: false)
}

// UIScrollViewDelegate    
func scrollViewDidZoom(scrollView: UIScrollView) {
    centerScrollViewContents(scrollView)
}
Hombre ligero
fuente
1

En caso de que su imageView interno tenga un ancho específico inicial (por ejemplo, 300) y solo desee centrar su ancho solo en el zoom más pequeño que su ancho inicial, esto podría ayudarlo también.

 func scrollViewDidZoom(scrollView: UIScrollView){
    if imageView.frame.size.width < 300{
        imageView.center.x = self.view.frame.width/2
    }
  }
dejix
fuente
0

Aquí está la forma actual en que estoy haciendo que esto funcione. Es mejor pero aún no es perfecto. Intenta configurar:

 myScrollView.bouncesZoom = YES; 

para solucionar el problema con la vista no centrada cuando está en minZoomScale.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGSize screenSize = [[self view] bounds].size;//[[UIScreen mainScreen] bounds].size;//
CGSize photoSize = [yourImage size];
CGFloat topInset = (screenSize.height - photoSize.height * [myScrollView zoomScale]) / 2.0;
CGFloat sideInset = (screenSize.width - photoSize.width * [myScrollView zoomScale]) / 2.0;

if (topInset < 0.0)
{ topInset = 0.0; }
if (sideInset < 0.0)
{ sideInset = 0.0; } 
[myScrollView setContentInset:UIEdgeInsetsMake(topInset, sideInset, -topInset, -sideInset)];
ApplicationDelegate *appDelegate = (ApplicationDelegate *)[[UIApplication sharedApplication] delegate];

CGFloat scrollViewHeight; //Used later to calculate the height of the scrollView
if (appDelegate.navigationController.navigationBar.hidden == YES) //If the NavBar is Hidden, set scrollViewHeight to 480
{ scrollViewHeight = 480; }
if (appDelegate.navigationController.navigationBar.hidden == NO) //If the NavBar not Hidden, set scrollViewHeight to 360
{ scrollViewHeight = 368; }

imageView.frame = CGRectMake(0, 0, CGImageGetWidth(yourImage)* [myScrollView zoomScale], CGImageGetHeight(yourImage)* [myScrollView zoomScale]);

[imageView setContentMode:UIViewContentModeCenter];
}

Además, hago lo siguiente para evitar que la imagen se pegue al costado después de alejarse.

- (void) scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
myScrollView.frame = CGRectMake(0, 0, 320, 420);
 //put the correct parameters for your scroll view width and height above
}
Jonás
fuente
Hola jonah Parece que hemos estado lidiando con el mismo problema por un tiempo. Verificará sus dos soluciones y responderá pronto.
hpique
Hola Jonah, probé tu última solución. Sin embargo, ¿qué es temporalImage? Intenté poner temporaryImage = imageView.image; sin embargo, una vez que hago zoom, la imagen desaparece. Gracias, Pannag
psanketi
Pannag, temporaryImage fue mal nombrado. Debería llamarse myImage ya que es cualquier imagen que esté utilizando. Perdón por la confusion.
Jonás
0

De acuerdo, creo que he encontrado una solución bastante buena para este problema. El truco consiste en reajustar constantemente el imageView'smarco. Creo que esto funciona mucho mejor que ajustar constantemente el contentInsetsocontentOffSets . Tuve que agregar un poco de código extra para acomodar tanto las imágenes verticales como las horizontales.

Aquí está el código:

- (void) scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {

CGSize screenSize = [[self view] bounds].size;

if (myScrollView.zoomScale <= initialZoom +0.01) //This resolves a problem with the code not working correctly when zooming all the way out.
{
    imageView.frame = [[self view] bounds];
    [myScrollView setZoomScale:myScrollView.zoomScale +0.01];
}

if (myScrollView.zoomScale > initialZoom)
{
    if (CGImageGetWidth(temporaryImage.CGImage) > CGImageGetHeight(temporaryImage.CGImage)) //If the image is wider than tall, do the following...
    {
        if (screenSize.height >= CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale]) //If the height of the screen is greater than the zoomed height of the image do the following...
        {
            imageView.frame = CGRectMake(0, 0, 320*(myScrollView.zoomScale), 368);
        }
        if (screenSize.height < CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale]) //If the height of the screen is less than the zoomed height of the image do the following...
        {
            imageView.frame = CGRectMake(0, 0, 320*(myScrollView.zoomScale), CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale]);
        }
    }
    if (CGImageGetWidth(temporaryImage.CGImage) < CGImageGetHeight(temporaryImage.CGImage)) //If the image is taller than wide, do the following...
    {
        CGFloat portraitHeight;
        if (CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale] < 368)
        { portraitHeight = 368;}
        else {portraitHeight = CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale];}

        if (screenSize.width >= CGImageGetWidth(temporaryImage.CGImage) * [myScrollView zoomScale]) //If the width of the screen is greater than the zoomed width of the image do the following...
        {
            imageView.frame = CGRectMake(0, 0, 320, portraitHeight);
        }
        if (screenSize.width < CGImageGetWidth (temporaryImage.CGImage) * [myScrollView zoomScale]) //If the width of the screen is less than the zoomed width of the image do the following...
        {
            imageView.frame = CGRectMake(0, 0, CGImageGetWidth(temporaryImage.CGImage) * [myScrollView zoomScale], portraitHeight);
        }
    }
    [myScrollView setZoomScale:myScrollView.zoomScale -0.01];
}
Jonás
fuente
0

Simplemente deshabilite la paginación, para que funcione bien:

scrollview.pagingEnabled = NO;
Nagaraj
fuente
0

Tuve exactamente el mismo problema. Así es como lo resolví

Este código debería llamarse como resultado de scrollView:DidScroll:

CGFloat imageHeight = self.imageView.frame.size.width * self.imageView.image.size.height / self.imageView.image.size.width;
BOOL imageSmallerThanContent = (imageHeight < self.scrollview.frame.size.height) ? YES : NO;
CGFloat topOffset = (self.imageView.frame.size.height - imageHeight) / 2;

// If image is not large enough setup content offset in a way that image is centered and not vertically scrollable
if (imageSmallerThanContent) {
     topOffset = topOffset - ((self.scrollview.frame.size.height - imageHeight)/2);
}

self.scrollview.contentInset = UIEdgeInsetsMake(topOffset * -1, 0, topOffset * -1, 0);
aryaxt
fuente
0

Aunque la pregunta es un poco antigua, el problema aún existe. Lo resolví en Xcode 7 al hacer que la restricción de espacio vertical del elemento superior (en este caso, el topLabel) a los superViews (el scrollView) arriba IBOutlety luego recalculando su constante cada vez que el contenido cambia dependiendo de la altura de las subvistas de scrollView ( topLabely bottomLabel)

class MyViewController: UIViewController {

    @IBOutlet weak var scrollView: UIScrollView!
    @IBOutlet weak var topLabel: UILabel!
    @IBOutlet weak var bottomLabel: UILabel!
    @IBOutlet weak var toTopConstraint: NSLayoutConstraint!

    override func viewDidLayoutSubviews() {
        let heightOfScrollViewContents = (topLabel.frame.origin.y + topLabel.frame.size.height - bottomLabel.frame.origin.y)
        // In my case abs() delivers the perfect result, but you could also check if the heightOfScrollViewContents is greater than 0.
        toTopConstraint.constant = abs((scrollView.frame.height - heightOfScrollViewContents) / 2)
    }

    func refreshContents() {
        // Set the label's text …

        self.view.layoutIfNeeded()
    }
}
Nick Podratz
fuente