Wyrównanie UIImageView z Aspect Fit


Odpowiedzi:


40

[EDYTUJ - ten kod jest trochę spleśniały, istota z 2011 r. I wszystkie oprócz mnie włączone mody @ ArtOfWarefare]

Nie możesz tego zrobić w / UIImageView. Utworzyłem prostą podklasę UIView, MyImageViewktóra zawiera UIImageView. Kod poniżej.

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

@interface MyImageView : UIView {
    UIImageView *_imageView;
}

@property (nonatomic, assign) UIImage *image;

@end

i

// 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

3
Dwie małe rzeczy miałem zrobić, aby tej pracy 1 - I wdrożone initWithCoder(ja w zasadzie skopiowaniu initWithFrameale otrzymuje [super initWithFrame:]z [super initWithCoder:]) i 2 - dodałem w kolejce if (!self.image) return;do layoutSubviewstak, że nie będzie katastrofy z nieprawidłowej geometrii jeżeli ISN obraz” t przypisane, gdy jest wywołane. Z tymi dwoma małymi zmianami działa niesamowicie.
ArtOfWarfare

uwaga: w przypadku ARC deallocmetoda musi zostać usunięta.
Raptor,

35

Jeśli używasz scenorysów, można to osiągnąć za pomocą ograniczeń ...

Najpierw UIView z żądaną ostateczną klatką / ograniczeniami. Dodaj UIImageView do UIView. Ustaw contentMode na Aspect Fill. Spraw, aby ramka UIImageView miała ten sam współczynnik co obraz (pozwala to uniknąć późniejszych ostrzeżeń Storyboard). Przypnij boki do UIView, używając standardowych wiązań. Przypnij górę LUB dół (w zależności od tego, gdzie chcesz ją wyrównać) do UIView, używając standardowych ograniczeń. Na koniec dodaj ograniczenie współczynnika proporcji do UIImageView (upewniając się, że współczynnik jest taki jak obraz).


@Michael Platt, To brzmi tak zagmatwane, więc nie może to działać w iOS 7, ponieważ potrzebujesz równoważności współczynników proporcji, która jest dostępna w iOS 8+? Czy masz przykładowy kod?
Özgür

2
Zaimplementowałem to również, działa. Może się to wydawać zagmatwane, ponieważ rozwiązanie opisuje automatyczne ustawianie ograniczeń układu. W przypadku innych programistów moja implementacja wykorzystywała obrazy ładowane z sieci, więc musiałem usunąć i ponownie dodać ograniczenie współczynnika proporcji UIImageView programowo na podstawie szerokości / wysokości obrazu.
Michael DePhillips

3
Stworzyłem rozszerzenie UIImageView z rozwiązaniem @MichaelPlatt. gist.github.com/megimix/d0bbea45bd3e1e4d615fbd5c30b54da7
megimix

15

Jest to trochę trudne, ponieważ nie ma opcji ustawiania dalszych reguł wyrównania, jeśli już wybrałeś tryb zawartości ( .scaleAspectFit).

Ale oto obejście tego problemu:

Najpierw musisz jawnie zmienić rozmiar obrazu źródłowego, obliczając wymiary (jeśli byłby w UIImageViewwith 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()
    }
}

Następnie wystarczy wywołać tę funkcję na oryginalnym obrazie, przekazując ramkę imageView i przypisując wynik do swojej UIImageView.imagewłaściwości. Upewnij się również, że ustawiłeś contentModetutaj pożądany imageView ( lub nawet w Interface Builder )!

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

4

Spróbuj ustawić:

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

To zadziałało dla mnie.



2

Wiem, że to stary wątek, ale pomyślałem, że podzielę się tym, co zrobiłem, aby łatwo zmienić przycięty region z góry na dół widoku obrazu w Interface Builder, na wypadek, gdyby ktoś miał ten sam problem, co ja. Miałem UIImageView, który wypełnił widok mojego ViewController i próbowałem, aby górna część pozostała taka sama, niezależnie od rozmiaru ekranu urządzenia.

  1. Zastosowałem współczynnik kształtu siatkówki 4 (Edytor-> Zastosuj współczynnik kształtu Retina 4).

  2. Przypiąłem wysokość i szerokość.

Teraz, gdy ekran zmienia rozmiar, UIImageView ma w rzeczywistości ten sam rozmiar, a kontroler widoku po prostu przycina to, co jest poza ekranem. Początek ramki pozostaje na 0,0, więc dolna i prawa część obrazu jest przycinana, a nie górna.

Mam nadzieję że to pomoże.


1

Możesz to zrobić, najpierw skalując, a następnie zmieniając rozmiar. Warto tutaj wspomnieć, że byłem uwarunkowany wzrostem. Chodzi mi o to, że musiałem mieć obraz o wysokości 34 pikseli i bez względu na szerokość.

Zatem uzyskaj stosunek między rzeczywistą wysokością treści a wysokością widoku (34 piksele), a następnie skaluj również szerokość.

Oto jak to zrobiłem:

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];

Mam nadzieję że to pomoże.


1

Doszedłem do następującego rozwiązania:

  1. Ustaw UIImageViewtryb treści na top:imageView.contentMode = .top
  2. Zmień rozmiar obrazu, aby dopasować go do granic UIImageView

Aby załadować i zmienić rozmiar obrazu, używam Kingfishera :

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)])

To działa, ale obraz staje się zamazany. Naprawiłem to, używając rozmiaru ekranu urządzenia i współczynnika skalowania urządzenia w następujący sposób: niech procesor = ResizingImageProcessor (referenceSize: view.frame.size, mode: .aspectFit); imageView.kf.setImage (z: url, opcje: [. procesor (procesor), .scaleFactor (UIScreen.main.scale)])
Alexander Maslew

Niesamowite. Dzięki stary. Chciałem wyrównać lewą stronę i to zadziałało
Kunal Gupta

0

Rozwiązałem to, podklasując UIImageView i zastępując metodę setImage:. Podklasa najpierw zapisze swoje oryginalne wartości pochodzenia i rozmiaru, aby mogła użyć oryginalnego rozmiaru zestawu jako obwiedni.

Ustawiłem tryb treści na UIViewContentModeAspectFit. Wewnątrz setImage: chwyciłem stosunek szerokości do wysokości obrazu, a następnie zmieniłem rozmiar widoku obrazu, aby dopasować go do tego samego współczynnika co obraz. Po zmianie rozmiaru dostosowałem właściwości klatki, aby ustawić widok obrazu w tym samym miejscu, w którym był wcześniej, a następnie wywołałem super setImage :.

Powoduje to, że widok obrazu, którego ramka jest dopasowana dokładnie do obrazu, działa tak, że dopasowanie proporcji działa, a właściwości ramki widoku obrazu robią duże wrażenie, umieszczając widok obrazu tam, gdzie powinien, aby uzyskać ten sam efekt.

Oto kod, którego użyłem:

Po pierwsze, i ogólnie uważam, że jest to całkiem przydatne, jest kategorią w UIView, która ułatwia ustawianie właściwości ramki w widoku za pomocą właściwości takich jak left, right, top, bottom, width, height itp.

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

To jest podklasa UIImageView. Nie jest w pełni przetestowany, ale powinien przekazać pomysł. Można to rozszerzyć, aby ustawić własne nowe tryby wyrównania.

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

Zamiast używać UIImageView w VC, użyj metody BottomCenteredImageView.
slemke

To nie zadziałało dla mnie ... zmieniło proporcje mojego widoku, aby dopasować proporcje mojego obrazu, co nie było tym, czego chciałem ... Odpowiedź XJones działała pięknie z kilkoma zmianami, które zauważyłem w komentarze pod jego odpowiedzią.
ArtOfWarfare

0

Jeśli chcesz osiągnąć aspekt, dopasuj i pozbądź się pustych przestrzeni

Nie zapomnij usunąć ograniczenia szerokości widoku obrazu ze scenorysu i ciesz się

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()
}}

-6

Aby wyrównać obraz w celu dopasowania, użyj układu automatycznego. Przykładowy obraz wyrównany do prawej po skalowaniu do dopasowania w UIImageView:

  1. Utwórz UIImageView, który przechowuje obraz
  2. Dodaj ograniczenia automatyczne dla prawej, górnej, dolnej, szerokości
  3. Ustaw obraz:
myUIImageView.contentMode = .ScaleAspectFit
myUIImageView.translatesAutoresizingMaskIntoConstraints = false
myUIImageView.image = UIImage(named:"pizza.png")

Twój skalowany obraz z dopasowaniem proporcji jest teraz wyrównany do prawej w UIImageView

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

Zmień ograniczenia, aby wyrównać je inaczej w widoku obrazu.


To nie jest gorące, działa, jeśli używasz dopasowania proporcji, wyrównanie w dużym stopniu zależy od rzeczywistego rozmiaru obrazu
Abhishek Maurya
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.