ios - UIScrollView con UIImageView centrado, como la aplicación Fotos
objective-c iphone (11)
Me gustaría tener vista de desplazamiento con una vista de contenido de imagen. La imagen es en realidad un mapa que es mucho más grande que la pantalla. El mapa debe estar inicialmente en el centro de la vista de desplazamiento, como las fotos en la aplicación Fotos cuando se convierte el iPhone en orientación horizontal.
No logré tener el mapa en el centro con el zoom correcto y el desplazamiento al mismo tiempo. Siempre que la imagen del mapa comience desde la parte superior de la pantalla (en orientación vertical), el código se ve más o menos así:
- (void)loadView {
mapView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"map.jpg"]];
CGFloat mapHeight = MAP_HEIGHT * SCREEN_WIDTH / MAP_WIDTH;
mapView.frame = CGRectMake(0, 0, SCREEN_WIDTH, mapHeight);
scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)];
scrollView.delegate = self;
scrollView.contentSize = mapView.frame.size;
scrollView.maximumZoomScale = MAP_WIDTH / SCREEN_WIDTH;
scrollView.minimumZoomScale = 1;
[scrollView addSubview:mapView];
self.view = scrollView;
}
Cuando muevo el marco de la imagen al centro, la imagen crece solo desde la parte superior de su marco hacia abajo. Intenté jugar con la transformación mapView, con un marco dinámicamente cambiante de imageView. Nada funciona para mí hasta ahora.
Apple ha lanzado los videos de la sesión de la 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 todo el código disponible de forma gratuita.
No usa una API privada tampoco. No puedo poner aquí ningún código debido al acuerdo de no divulgación, pero aquí hay un enlace a la descarga del código de muestra. Probablemente necesites iniciar sesión para obtener acceso.
Y, aquí hay un enlace a la página WWDC de iTunes:
Aquí hay una solución alternativa, similar a la respuesta de @ JosephH, pero esta toma en cuenta las dimensiones reales de la imagen. De modo que cuando el usuario realiza un barrido / acercamiento, nunca tenga más espacios en blanco en la pantalla de lo requerido. Este es un problema común, por ejemplo, al mostrar una imagen de paisaje en una pantalla de retrato. Habrá espacios en blanco arriba y abajo de la imagen cuando toda la imagen esté en la pantalla (Ajuste de aspecto). Luego, al hacer zoom, las otras soluciones consideran que el espacio en blanco forma parte de la imagen, porque está en el imageView. Le permitirán desplazar la mayor parte de la imagen de la pantalla, dejando solo el espacio en blanco visible. Esto se ve mal para el usuario.
Con esta clase, necesitas pasarla con la imagen con la que está trabajando. Estuve tentado de tenerlo detectar automáticamente, pero esto es más rápido y quieres toda la velocidad que puedas obtener en el método layoutSubviews
.
Nota: Como es, esto requiere que AutoLayout no esté habilitado para scrollView.
//
// CentringScrollView.swift
// Cerebral Gardens
//
// Created by Dave Wood
// Copyright © 2016 Cerebral Gardens Inc. All rights reserved.
//
import UIKit
class CentringScrollView: UIScrollView {
var imageView: UIImageView?
override func layoutSubviews() {
super.layoutSubviews()
guard let superview = superview else { return }
guard let imageView = imageView else { return }
guard let image = imageView.image else { return }
var frameToCentre = imageView.frame
let imageWidth = image.size.width
let imageHeight = image.size.height
let widthRatio = superview.bounds.size.width / imageWidth
let heightRatio = superview.bounds.size.height / imageHeight
let minRatio = min(widthRatio, heightRatio, 1.0)
let effectiveImageWidth = minRatio * imageWidth * zoomScale
let effectiveImageHeight = minRatio * imageHeight * zoomScale
contentSize = CGSize(width: max(effectiveImageWidth, bounds.size.width), height: max(effectiveImageHeight, bounds.size.height))
frameToCentre.origin.x = (contentSize.width - frameToCentre.size.width) / 2
frameToCentre.origin.y = (contentSize.height - frameToCentre.size.height) / 2
imageView.frame = frameToCentre
}
}
De acuerdo, creo que he encontrado una solución bastante buena para este problema. El truco es reajustar constantemente el marco de imageView. Encuentro que esto funciona mucho mejor que ajustar constantemente contentInsets o contentOffSets. Tuve que agregar un poco de código adicional para acomodar imágenes de retrato y paisaje.
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];
}
En Monotouch eso funcionó para mí.
this._scroll.ScrollRectToVisible(new RectangleF(_scroll.ContentSize.Width/2, _scroll.ContentSize.Height/2,1,1),false);
Este código debería funcionar en la mayoría de las versiones de iOS (y se ha probado que funciona en 3.1 hacia arriba).
Se basa en el código WWDC de Apple mencionado en la respuesta de Jonah.
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;
}
Esto es lo que consideraría, la solución ya que se comporta exactamente como la aplicación de fotos de Apple. He estado usando soluciones que utilizan:
-(void) scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale
para volver a centrar pero no me gustó esa solución porque después de que se hizo el acercamiento, rebotaba y luego saltaba rápidamente hacia el centro, que era muy poco sexy. Resulta que prácticamente haces la misma lógica pero en esta función de delegado:
-(void)scrollViewDidZoom:(UIScrollView *)pScrollView
ambos comienzan desde el centro y cuando alejas, permanece centrado:
-(void)scrollViewDidZoom:(UIScrollView *)pScrollView {
CGRect innerFrame = imageView.frame;
CGRect scrollerBounds = pScrollView.bounds;
if ( ( innerFrame.size.width < scrollerBounds.size.width ) || ( innerFrame.size.height < scrollerBounds.size.height ) )
{
CGFloat tempx = imageView.center.x - ( scrollerBounds.size.width / 2 );
CGFloat tempy = imageView.center.y - ( scrollerBounds.size.height / 2 );
CGPoint myScrollViewOffset = CGPointMake( tempx, tempy);
pScrollView.contentOffset = myScrollViewOffset;
}
UIEdgeInsets anEdgeInset = { 0, 0, 0, 0};
if ( scrollerBounds.size.width > innerFrame.size.width )
{
anEdgeInset.left = (scrollerBounds.size.width - innerFrame.size.width) / 2;
anEdgeInset.right = -anEdgeInset.left; // I don''t know why this needs to be negative, but that''s what works
}
if ( scrollerBounds.size.height > innerFrame.size.height )
{
anEdgeInset.top = (scrollerBounds.size.height - innerFrame.size.height) / 2;
anEdgeInset.bottom = -anEdgeInset.top; // I don''t know why this needs to be negative, but that''s what works
}
pScrollView.contentInset = anEdgeInset;
}
Donde ''imageView'' es el UIImageView
que estás usando.
Esto funcionó para mí:
- (void) scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
CGFloat tempx = view.center.x-160;
CGFloat tempy = view.center.y-160;
myScrollViewOffset = CGPointMake(tempx,tempy);
}
donde 160 es la mitad del ancho / alto de su UIScrollView
.
Luego más tarde configuré el contentoffset al capturado aquí.
Nota: este método funciona de alguna manera. si la imagen es más pequeña que la vista de imagen, se desplazará parcialmente fuera de la pantalla. No es un gran problema, pero tampoco es tan agradable como la aplicación de fotos.
En primer lugar, es importante comprender que estamos tratando con 2 vistas, la vista de la imagen con la imagen y la vista de desplazamiento con la vista de la imagen que contiene. Entonces, primero configure la vista de la imagen para el tamaño de la pantalla:
[myImageView setFrame:self.view.frame];
Luego, centra tu imagen en la vista de imagen:
myImageView.contentMode = UIViewContentModeCenter;
Aquí está mi código completo:
- (void)viewDidLoad {
AppDelegate *appDelegate = (pAppDelegate *)[[UIApplication sharedApplication] delegate];
[super viewDidLoad];
NSString *Path = [[NSBundle mainBundle] bundlePath];
NSString *ImagePath = [Path stringByAppendingPathComponent:(@"data: %@", appDelegate.MainImageName)];
UIImage *tempImg = [[UIImage alloc] initWithContentsOfFile:ImagePath];
[imgView setImage:tempImg];
myScrollView = [[UIScrollView alloc] initWithFrame:[[self view] bounds]];
[myScrollView addSubview:myImageView];
//Set ScrollView Appearance
[myScrollView setBackgroundColor:[UIColor blackColor]];
myScrollView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
//Set Scrolling Prefs
myScrollView.bounces = YES;
myScrollView.delegate = self;
myScrollView.clipsToBounds = YES; // default is NO, we want to restrict drawing within our scrollview
[myScrollView setCanCancelContentTouches:NO];
[myScrollView setScrollEnabled:YES];
//Set Zooming Prefs
myScrollView.maximumZoomScale = 3.0;
myScrollView.minimumZoomScale = CGImageGetWidth(tempImg.CGImage)/320;
myScrollView.zoomScale = 1.01; //Added the .01 to enable scrolling immediately upon view load.
myScrollView.bouncesZoom = YES;
[myImageView setFrame:self.view.frame];//rect];// .frame.size.height = imageHeight;
myImageView.contentMode = UIViewContentModeCenter;
self.view = myScrollView;
[tempImg release];
}
Ojalá fuera así de simple. Hice algunas investigaciones en la red y descubrí que no es solo mi problema, pero muchas personas están luchando con el mismo problema no solo en el iPhone, sino también en el escritorio Cocoa de Apple. Ver los siguientes enlaces:
http://www.iphonedevsdk.com/forum/iphone-sdk-development/5740-uiimageview-uiscrollview.html
La solución descrita se basa en la propiedad UIViewContentModeScaleAspectFit de la imagen, pero desafortunadamente no funciona muy bien. La imagen está centrada y crece correctamente, pero el área de rebote parece ser mucho más grande que la imagen.
Este tipo tampoco obtuvo la respuesta:
http://discussions.apple.com/thread.jspa?messageID=8322675
Y finalmente, el mismo problema en el escritorio Cocoa de Apple:
http://www.cocoadev.com/index.pl?CenteringInsideNSScrollView
Supongo que la solución funciona, pero está basada en NSClipView, que no está en iPhone ...
¿Alguien tiene alguna solución para iPhone?
Sospecho que debes establecer el contentOffset
UIScrollView
.
Una manera elegante de centrar el contenido de UISCrollView
es esto.
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 tu método de observación:
- (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ía cambiar el
[pointer viewWithTag:100]
. Reemplazar por su contenido verUIView
.- También cambie
imageGallery
apuntando a su tamaño de ventana.
- También cambie
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
.