Alineación de UIImageView con ajuste de aspecto

79

para mi UIImageView, elijo Aspect Fit (InterfaceBuilder), pero ¿cómo puedo cambiar la alineación vertical?

Raegtime
fuente
1
tratar con la parte superior y la parte inferior de la propiedad imageview
rithik
No funciona cuando tiene configurado el ajuste de aspecto.
Ethan Allen
5
Hay un proyecto en GitHub llamado UIImageViewAligned que hace esto perfectamente.
Daniel Storm

Respuestas:

40

[EDITAR: este código es un poco mohoso al ser de 2011 y todos, excepto yo, incorporé las modificaciones de @ ArtOfWarefare]

No puede hacer esto con UIImageView. Creé una subclase UIView simple MyImageViewque contiene un UIImageView. Código a continuación.

// MyImageView.h
#import <UIKit/UIKit.h>

@interface MyImageView : UIView {
    UIImageView *_imageView;
}

@property (nonatomic, assign) UIImage *image;

@end

y

// MyImageView.m

#import "MyImageView.h"

@implementation MyImageView

@dynamic image;

- (id)initWithCoder:(NSCoder*)coder
{
    self = [super initWithCoder:coder];
    if (self) {
        self.clipsToBounds = YES;
        _imageView = [[UIImageView alloc] initWithFrame:self.bounds];
        _imageView.contentMode = UIViewContentModeScaleAspectFill;
        [self addSubview:_imageView];
    }
    return self;
}

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        self.clipsToBounds = YES;
        _imageView = [[UIImageView alloc] initWithFrame:self.bounds];
        _imageView.contentMode = UIViewContentModeScaleAspectFill;
        [self addSubview:_imageView];
    }
    return self;
}

- (id)initWithImage:(UIImage *)anImage
{
    self = [self initWithFrame:CGRectZero];
    if (self) {
        _imageView.image = anImage;
        [_imageView sizeToFit];

        // initialize frame to be same size as imageView
        self.frame = _imageView.bounds;        
    }
    return self;
}

// Delete this function if you're using ARC
- (void)dealloc
{
    [_imageView release];
    [super dealloc];
}

- (UIImage *)image
{
    return _imageView.image;
}

- (void)setImage:(UIImage *)anImage
{
    _imageView.image = anImage;
    [self setNeedsLayout];
}

- (void)layoutSubviews
{
    if (!self.image) return;

    // compute scale factor for imageView
    CGFloat widthScaleFactor = CGRectGetWidth(self.bounds) / self.image.size.width;
    CGFloat heightScaleFactor = CGRectGetHeight(self.bounds) / self.image.size.height;

    CGFloat imageViewXOrigin = 0;
    CGFloat imageViewYOrigin = 0;
    CGFloat imageViewWidth;
    CGFloat imageViewHeight;


    // if image is narrow and tall, scale to width and align vertically to the top
    if (widthScaleFactor > heightScaleFactor) {
        imageViewWidth = self.image.size.width * widthScaleFactor;
        imageViewHeight = self.image.size.height * widthScaleFactor;
    }

    // else if image is wide and short, scale to height and align horizontally centered
    else {
        imageViewWidth = self.image.size.width * heightScaleFactor;
        imageViewHeight = self.image.size.height * heightScaleFactor;
        imageViewXOrigin = - (imageViewWidth - CGRectGetWidth(self.bounds))/2;
    }

    _imageView.frame = CGRectMake(imageViewXOrigin,
                                  imageViewYOrigin,
                                  imageViewWidth,
                                  imageViewHeight);
}

- (void)setFrame:(CGRect)frame
{
    [super setFrame:frame];
    [self setNeedsLayout];
}

@end
XJones
fuente
3
Dos pequeñas cosas que tuve que hacer para que esto funcionara: 1 - Implementé initWithCoder(básicamente copié el suyo initWithFramepero reemplacé el [super initWithFrame:]con [super initWithCoder:]) y 2 - agregué una línea if (!self.image) return;para layoutSubviewsque no se bloqueara con geometría no válida si una imagen no es ' t asignado cuando se llama. Con estos dos pequeños cambios funciona de maravilla.
ArtOfWarfare
nota al margen: con ARC, el deallocmétodo debe eliminarse.
Raptor
35

Si usa Storyboards, esto se puede lograr con restricciones ...

En primer lugar, una UIView con el marco / restricciones finales deseados. Agregue un UIImageView a UIView. Establezca contentMode en Relleno de aspecto. Haga que el marco de UIImageView tenga la misma proporción que la imagen (esto evita cualquier advertencia de Storyboard más adelante). Fije los lados a la UIView usando restricciones estándar. Fije la parte superior O inferior (dependiendo de dónde desee que se alinee) a UIView usando restricciones estándar. Finalmente, agregue una restricción de relación de aspecto a UIImageView (asegurándose de que la relación sea la imagen).

Michael Platt
fuente
@Michael Platt, esto suena tan confuso, entonces esto no puede funcionar en iOS 7, ya que requiere una equivalencia de relaciones de aspecto, que está disponible en iOS 8+. ¿Tiene algún código de ejemplo?
Özgür
2
También he implementado esto, funciona. Puede parecer confuso porque la solución describe la configuración automática de restricciones de diseño. Para otros desarrolladores, mi implementación usó imágenes cargadas desde la web, por lo que tuve que eliminar y volver a agregar la restricción de relación de aspecto UIImageView programáticamente en función del ancho / alto de la imagen.
Michael DePhillips
3
Creé la extensión UIImageView con la solución @MichaelPlatt. gist.github.com/megimix/d0bbea45bd3e1e4d615fbd5c30b54da7
megimix
15

Este es un poco complicado ya que no hay opción para establecer más reglas de alineación si ya seleccionó un modo de contenido ( .scaleAspectFit).

Pero aquí hay una solución a esto:

Primero debe cambiar el tamaño de la imagen de origen de forma explícita calculando las dimensiones (si fuera UIImageViewcon contentMode = .scaleAspectFit).

extension UIImage {

    func aspectFitImage(inRect rect: CGRect) -> UIImage? {
        let width = self.size.width
        let height = self.size.height
        let aspectWidth = rect.width / width
        let aspectHeight = rect.height / height
        let scaleFactor = aspectWidth > aspectHeight ? rect.size.height / height : rect.size.width / width

        UIGraphicsBeginImageContextWithOptions(CGSize(width: width * scaleFactor, height: height * scaleFactor), false, 0.0)
        self.draw(in: CGRect(x: 0.0, y: 0.0, width: width * scaleFactor, height: height * scaleFactor))

        defer {
            UIGraphicsEndImageContext()
        }

        return UIGraphicsGetImageFromCurrentImageContext()
    }
}

Luego, simplemente necesita llamar a esta función en su imagen original pasando el marco de su imageView y asignar el resultado a su UIImageView.imagepropiedad. Además, asegúrese de configurar su imageView deseado contentModeaquí ( o incluso en el Interface Builder ).

let image = UIImage(named: "MySourceImage")
imageView.image = image?.aspectFitImage(inRect: imageView.frame)
imageView.contentMode = .left
choofie
fuente
4

Intente configurar:

imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.clipsToBounds = YES;

Esto funcionó para mí.

Jonathan Molina
fuente
3

Usé UIImageViewAligned para cambiar la alineación de la imagen gracias al desarrollador

UIImageViewAligned

Milap Kundalia
fuente
2

Sé que este es un hilo antiguo, pero pensé en compartir lo que hice para cambiar fácilmente la región recortada de la parte superior a la inferior de la vista de imagen en Interface Builder, en caso de que alguien tuviera el mismo problema que yo. Tenía un UIImageView que llenaba la Vista de mi ViewController y estaba tratando de que la parte superior permaneciera igual, independientemente del tamaño de la pantalla del dispositivo.

  1. Apliqué el factor de forma retina 4 (Editor-> Aplicar factor de forma Retina 4).

  2. Clavé la altura y el ancho.

Ahora, cuando la pantalla cambia de tamaño, el UIImageView es en realidad del mismo tamaño y el controlador de vista solo recorta lo que está fuera de la pantalla. El origen del marco permanece en 0,0, por lo que la parte inferior y derecha de la imagen se recortan, no la parte superior.

Espero que esto ayude.

Alegría
fuente
1

Puede hacerlo primero escalando y luego cambiando el tamaño. Lo que debo mencionar aquí es que estaba condicionado por la altura. Quiero decir, tenía que tener la imagen de 34px de alto y sin importar el ancho.

Entonces, obtenga la relación entre la altura real del contenido y la altura de la vista (34 px) y luego escale el ancho también.

Así es como lo hice:

CGSize size = [imageView sizeThatFits:imageView.frame.size];

CGSize actualSize;
actualSize.height = imageView.frame.size.height;
actualSize.width = size.width / (1.0 * (size.height / imageView.frame.size.height));

CGRect frame = imageView.frame;
frame.size = actualSize;
[imageView setFrame:frame];

Espero que esto ayude.

Jorge
fuente
1

Llegué a la siguiente solución:

  1. Establezca el UIImageViewmodo de contenido en top:imageView.contentMode = .top
  2. Cambiar el tamaño de la imagen para que se ajuste a los límites de UIImageView

Para cargar y cambiar el tamaño de la imagen utilizo Kingfisher :

let size = imageView.bounds.size
let processor = ResizingImageProcessor(referenceSize: size, mode: .aspectFit)
imageView.kf.setImage(with: URL(string: imageUrl), options: [.processor(processor), .scaleFactor(UIScreen.main.scale)])
Murlakatam
fuente
Esto funciona pero la imagen se vuelve borrosa. Lo arreglé usando el tamaño de la pantalla del dispositivo y el factor de escala del dispositivo como este: let procesador = ResizingImageProcessor (referenceSize: view.frame.size, mode: .aspectFit); imageView.kf.setImage (con: url, opciones: [. procesador (procesador), .scaleFactor (UIScreen.main.scale)])
Alexander Maslew
Increíble. Gracias amigo. Quería alinear a la izquierda y esto funcionó
Kunal Gupta
0

Resolví esto subclasificando UIImageView y anulando el método setImage :. La subclase almacenaría primero sus valores originales de origen y tamaño para poder usar el tamaño del conjunto original como cuadro delimitador.

Configuré el modo de contenido en UIViewContentModeAspectFit. Dentro de setImage: tomé la relación de ancho a alto de la imagen y luego cambié el tamaño de la vista de la imagen para que se ajustara a la misma relación que la imagen. Después del cambio de tamaño, ajusté las propiedades de mi marco para establecer la vista de la imagen en el mismo lugar que estaba antes, y luego llamé al super setImage :.

Esto da como resultado una vista de imagen cuyo marco se ajusta para ajustarse exactamente a la imagen, por lo que el ajuste de aspecto funciona y las propiedades del marco de la vista de imagen están haciendo el trabajo pesado al colocar la vista de imagen donde debería estar para obtener el mismo efecto.

Aquí hay un código que usé:

En primer lugar, y lo encuentro bastante útil en general, es una categoría en UIView que facilita la configuración de las propiedades del marco en una vista a través de propiedades como izquierda, derecha, arriba, abajo, ancho, alto, etc.

UIImageView + FrameAdditions

@interface UIView (FrameAdditions)

@property CGFloat left, right, top, bottom, width, height;
@property CGPoint origin;

@end

@implementation UIView (FrameAdditions)

- (CGFloat)left {
    return self.frame.origin.x;
}

- (void)setLeft:(CGFloat)left {
    self.frame = CGRectMake(left, self.frame.origin.y, self.frame.size.width, self.frame.size.height);
}

- (CGFloat)right {
    return self.frame.origin.x + self.frame.size.width;
}

- (void)setRight:(CGFloat)right {
    self.frame = CGRectMake(right - self.frame.size.width, self.frame.origin.y, self.frame.size.width, self.frame.size.height);
}

- (CGFloat)top {
    return self.frame.origin.y;
}

- (void)setTop:(CGFloat)top {
    self.frame = CGRectMake(self.frame.origin.x, top, self.frame.size.width, self.frame.size.height);
}

- (CGFloat)bottom {
    return self.frame.origin.y + self.frame.size.height;
}

- (void)setBottom:(CGFloat)bottom {
    self.frame = CGRectMake(self.frame.origin.x, bottom - self.frame.size.height, self.frame.size.width, self.frame.size.height);
}

- (CGFloat)width {
    return self.frame.size.width;
}

- (void)setWidth:(CGFloat)width {
    self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, width, self.frame.size.height);
}

- (CGFloat)height {
    return self.frame.size.height;
}

- (void)setHeight:(CGFloat)height {
    self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, height);
}

- (CGPoint)origin {
    return self.frame.origin;
}

- (void)setOrigin:(CGPoint)origin {
    self.frame = CGRectMake(origin.x, origin.y, self.frame.size.width, self.frame.size.height);
}

@end

Esta es la subclase de UIImageView. No está completamente probado, pero debería transmitir la idea. Esto podría ampliarse para establecer sus propios modos nuevos de alineación.

BottomCenteredImageView

@interface BottomCenteredImageView : UIImageView

@end


@interface BottomCenteredImageView() {
    CGFloat originalLeft;
    CGFloat originalBottom;
    CGFloat originalHeight;
    CGFloat originalWidth;
}

@end

@implementation BottomCenteredImageView

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if(self) {
        [self initialize];
    }
    return self;
}

- (void)awakeFromNib {
    [self initialize];
}

- (void)initialize {
    originalLeft = self.frame.origin.x;
    originalHeight = CGRectGetHeight(self.frame);
    originalWidth = CGRectGetWidth(self.frame);
    originalBottom = self.frame.origin.y + originalHeight;
}

- (void)setImage:(UIImage *)image {
    if(image) {
        self.width = originalWidth;
        self.height = originalHeight;
        self.left = originalLeft;
        self.bottom = originalBottom;

        float myWidthToHeightRatio = originalWidth/originalHeight;
        float imageWidthToHeightRatio = image.size.width/image.size.height;
        if(myWidthToHeightRatio >= imageWidthToHeightRatio) {
            // Calculate my new width
            CGFloat newWidth = self.height * imageWidthToHeightRatio;
            self.width = newWidth;
            self.left = originalLeft + (originalWidth - self.width)/2;
            self.bottom = originalBottom;
        } else {
            // Calculate my new height
            CGFloat newHeight = self.width / imageWidthToHeightRatio;
            self.height = newHeight;
            self.bottom = originalBottom;
        }
        self.contentMode = UIViewContentModeScaleAspectFit;
        [super setImage:image];
    } else {
        [super setImage:image];
    }
}

@end
slemke
fuente
En lugar de usar UIImageView en su VC, use BottomCenteredImageView.
slemke
Esto no funcionó para mí ... cambió la relación de aspecto de mi vista para que coincida con la relación de aspecto de mi imagen, que no era lo que quería ... La respuesta que proporcionó XJones funcionó a la perfección con los pocos cambios que noté en el comentarios debajo de su respuesta.
ArtOfWarfare
0

Si necesita lograr AspectFit y eliminar los espacios vacíos

No olvide eliminar la restricción de ancho de su vista de imagen del guión gráfico y disfrute

class SelfSizedImageView: UIImageView {

override func layoutSubviews() {
    super.layoutSubviews()

    guard let imageSize = image?.size else {
        return
    }

    let viewBounds = bounds
    let imageFactor = imageSize.width / imageSize.height
    let newWidth = viewBounds.height * imageFactor

    let myWidthConstraint = self.constraints.first(where: { $0.firstAttribute == .width })
    myWidthConstraint?.constant = min(newWidth, UIScreen.main.bounds.width / 3)

    layoutIfNeeded()
}}
Maxim Golovlev
fuente
-6

Para alinear una imagen a escala, utilice el diseño automático. Ejemplo de imagen alineada a la derecha después de escalar para ajustar en UIImageView:

  1. Crea UIImageView que contiene la imagen
  2. Agregue restricciones automáticas para la derecha, arriba, abajo, ancho
  3. Establecer la imagen:
myUIImageView.contentMode = .ScaleAspectFit
myUIImageView.translatesAutoresizingMaskIntoConstraints = false
myUIImageView.image = UIImage(named:"pizza.png")

Su imagen escalada de ajuste de aspecto ahora está alineada a la derecha en UIImageView

+----------------------------------+
|                           [IMAGE]|
+----------------------------------+

Cambie las restricciones para alinear de manera diferente dentro de la vista de imagen.

pizzamonster
fuente
Esto no está muy bien, funciona, si está utilizando un ajuste de aspecto, la alineación depende en gran medida del tamaño real de la imagen
Abhishek Maurya