ios - UIScrollView zooming con diseño automático
autolayout (3)
Estoy intentando implementar un UIScrollView
the New Way , usando Auto Layout. He configurado restricciones desde la vista interna a la vista de desplazamiento para que pueda calcular su propio contentSize
automáticamente, y eso funciona como un encanto, excepto que todo el infierno se desata cuando trato de acercarme o alejarme. Ni siquiera puedo describir adecuadamente lo que sucede, aparte de decir que la vista interior se "estropea".
Puede ver un ejemplo de este comportamiento here (no es mi proyecto; tiene que configurar la vista de desplazamiento maximumZoomScale
e implementar -viewForZoomingInScrollView:
antes de que funcione el zoom).
¿Alguien más ha tenido este comportamiento? ¿Existe actualmente alguna forma de hacer zoom en un UIScrollView
para que funcione con Auto Layout sin volver a implementar el comportamiento del zoom?
Ejemplo completo de Swift Playground
El ejemplo más simple que se me ocurre es agregar un UIImageView
a un UIScrollView
. Esto es 100% en código, solo tendrá que agregar un PNG al patio de juegos. Llamé al mío Image.png
. En un Patio de recreo, verá todo en una ''Vista en vivo''. Pinch-zoom funciona con Ctrl-clic para colocar un dedo en la pantalla y luego arrastrar. Hasta que el contenido se amplíe, el tamaño de la pantalla no funcionará. Toca dos veces la imagen para alternar entre la escala 1x y 3x.
Basado en la nota técnica TN2154 de Apple: UIScrollView y Autolayout
Gotcha
Encontrarás que todo es muy frustrante si tu contenido no es más grande que el tamaño de la pantalla. Si su contenido se ajusta completamente en la pantalla, nada ocurrirá. Es por eso que debes hacer zoom para trabajar también. Si quieres probarte a ti mismo que funciona, prueba con una imagen realmente grande (más grande que la ventana).
import UIKit
import PlaygroundSupport
enum TapToggle {
case Regular, Large
}
class ScrollingViewController : UIViewController
{
var tapToggle: TapToggle = .Large
var scrollView: UIScrollView?
var imageView: UIImageView?
override func viewDidLoad()
{
let image = UIImage(named: "Image")
let imageView = UIImageView(image: image)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.backgroundColor = .white
imageView.isUserInteractionEnabled = true
let scrollView = UIScrollView()
scrollView.minimumZoomScale = 0.5
scrollView.maximumZoomScale = 10.0
scrollView.delegate = self
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(imageView)
let imageViewKey = "imageView"
let imageViews = [imageViewKey: imageView]
scrollView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[/(imageViewKey)]|", options: [], metrics: nil, views: imageViews))
scrollView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[/(imageViewKey)]|", options: [], metrics: nil, views: imageViews))
self.imageView = imageView
scrollView.backgroundColor = .white
self.view.addSubview(scrollView)
let scrollViewKey = "scrollView"
let scrollViews = [scrollViewKey: scrollView]
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[/(scrollViewKey)]|", options: [], metrics: nil, views: scrollViews))
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[/(scrollViewKey)]|", options: [], metrics: nil, views: scrollViews))
self.scrollView = scrollView
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didDoubleTap(sender:)))
tapGesture.numberOfTapsRequired = 2
self.imageView?.addGestureRecognizer(tapGesture)
}
@objc
public func didDoubleTap(sender: AnyObject)
{
switch self.tapToggle {
case .Regular:
self.scrollView?.zoomScale = 1.0
self.tapToggle = .Large
case .Large:
self.scrollView?.zoomScale = 3.0
self.tapToggle = .Regular
}
}
}
extension ScrollingViewController: UIScrollViewDelegate
{
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return self.imageView
}
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat)
{
print("/(scale)")
}
}
PlaygroundPage.current.needsIndefiniteExecution = true
PlaygroundPage.current.liveView = ScrollingViewController()
La mejor respuesta que he visto es la de Mark ( https://.com/users/1051919/mark-kryzhanouski ), publicada aquí: UIScrollView Zoom no funciona con Autolayout .
El quid de esto es que tiene que anclar la vista de imagen que está anidada en la vista de desplazamiento, al elemento primario de la vista de desplazamiento. A pesar de la guía en las notas de lanzamiento de iOS 6, no es intuitivo para mí qué vista está "flotando" sobre qué. En este caso, la vista de desplazamiento es solo una vista de una sola imagen.
Hice mucha experimentación con esto, con la esperanza de encontrar un enfoque de todo el IB y no encontré ninguno. Aún puede generar la jerarquía de vistas en IB, pero aún tiene que agregar restricciones programáticas. Puede eliminar algunas o todas las restricciones predeterminadas (principalmente solo para apaciguar las advertencias de conflicto de restricciones), pero siempre necesita el código de Mark para vincular la vista de la imagen al elemento principal de la vista de desplazamiento, el principal general de la vista de imagen.
Parece que debería ser más simple que esto, "simplemente debería funcionar" pero:
NSDictionary *viewsDictionary = @{ @"scrollView": self.scrollView, @"imageView": self.imageView };
[self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:@"H:|[imageView(width)]"
options:0
metrics:@{@"width": @(self.imageView.image.size.width)}
views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:@"V:|[imageView(height)]"
options:0
metrics:@{@"height": @(self.imageView.image.size.height)}
views:viewsDictionary]];
Sin agregar una imageView en el guión gráfico, he encontrado perfectamente los siguientes trabajos:
-(UIImageView *)imageView
{
if (!_imageView) _imageView = [[UIImageView alloc] initWithFrame:CGRectZero];
return _imageView;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self.scrollView addSubview:self.imageView];
// Set the min and max:
self.scrollView.minimumZoomScale = 0.2;
self.scrollView.maximumZoomScale = 5.0;
self.scrollView.delegate = self;
// Set the content:
self.scrollView.zoomScale = 1.0; // reset zoomScale for new image
self.scrollView.contentSize = CGSizeMake(image.size.width/2, image.size.height/2);
self.imageView.frame = CGRectMake(0, 0, image.size.width/2, image.size.height/2);
self.imageView.image = image;
}
-(UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.imageView;
}