UIImage等比例缩放并顶部对齐

62

看起来aspect fit默认将图像与框架底部对齐。是否有一种方法可以在保持aspect fit的情况下覆盖对齐方式?

** 编辑 **

这个问题先于自动布局。实际上,自动布局是在2012年WWDC上发布的,就在问这个问题的同一周。


5
我知道这个已经有点过时了,但是对于像我一样找到这个线程的人来说,这个解决方案非常棒:https://github.com/reydanro/UIImageViewAligned - tommybananas
8个回答

24

简而言之,你不能使用UIImageView来做到这一点。

一种解决方案是子类化一个包含UIImageViewUIView,并根据图片大小更改其框架。例如,你可以在这里找到一个版本(链接)


1
如果您可以覆盖UIImageView(如下所示),则可以一次性完成。 - Dan Rosenstark
1
不完全是 OP 需要的效果。 - Mundi

15
要做到这一点,可以修改UIImageView图层的contentsRect。以下是我项目中的代码(UIImageView的子类),假设使用scaleToFill并将图像偏移,使其与默认的居中对齐方式相比,实现顶部、底部、左侧或右侧对齐。对于aspectFit,也可以采用类似的解决方案。
typedef NS_OPTIONS(NSUInteger, AHTImageAlignmentMode) {
    AHTImageAlignmentModeCenter = 0,
    AHTImageAlignmentModeLeft = 1 << 0,
    AHTImageAlignmentModeRight = 1 << 1,
    AHTImageAlignmentModeTop = 1 << 2,
    AHTImageAlignmentModeBottom = 1 << 3,
    AHTImageAlignmentModeDefault = AHTImageAlignmentModeCenter,
};

- (void)updateImageViewContentsRect {
    CGRect imageViewContentsRect = CGRectMake(0, 0, 1, 1);

    if (self.image.size.height > 0 && self.bounds.size.height > 0) {

        CGRect imageViewBounds = self.bounds;
        CGSize imageSize = self.image.size;

        CGFloat imageViewFactor = imageViewBounds.size.width / imageViewBounds.size.height;
        CGFloat imageFactor = imageSize.width / imageSize.height;

        if (imageFactor > imageViewFactor) {
            //Image is wider than the view, so height will match
            CGFloat scaledImageWidth = imageViewBounds.size.height * imageFactor;
            CGFloat xOffset = 0.0;
            if (BM_CONTAINS_BIT(self.alignmentMode, AHTImageAlignmentModeLeft)) {
                xOffset = -(scaledImageWidth - imageViewBounds.size.width) / 2;
            } else if (BM_CONTAINS_BIT(self.alignmentMode, AHTImageAlignmentModeRight)) {
                xOffset = (scaledImageWidth - imageViewBounds.size.width) / 2;
            }
            imageViewContentsRect.origin.x = (xOffset / scaledImageWidth);
        } else if (imageFactor < imageViewFactor) {
            CGFloat scaledImageHeight = imageViewBounds.size.width / imageFactor;

            CGFloat yOffset = 0.0;
            if (BM_CONTAINS_BIT(self.alignmentMode, AHTImageAlignmentModeTop)) {
                yOffset = -(scaledImageHeight - imageViewBounds.size.height) / 2;
            } else if (BM_CONTAINS_BIT(self.alignmentMode, AHTImageAlignmentModeBottom)) {
                yOffset = (scaledImageHeight - imageViewBounds.size.height) / 2;
            }
            imageViewContentsRect.origin.y = (yOffset / scaledImageHeight);
        }
    }
    self.layer.contentsRect = imageViewContentsRect;
}

Swift版本

///Works perfectly with AspectFill. Note there's no protection for obscure issues like (say) scaledImageWidth is zero.
class AlignmentImageView: UIImageView {

    enum HorizontalAlignment { case left, center, right }
    enum VerticalAlignment { case top, center, bottom }

    var horizontalAlignment: HorizontalAlignment = .center  { didSet { updateContentsRect() } }
    var verticalAlignment: VerticalAlignment = .center  { didSet { updateContentsRect() } }

    override var image: UIImage? { didSet { updateContentsRect() } }

    override func layoutSubviews() {
        super.layoutSubviews()
        updateContentsRect()
    }

    private func updateContentsRect() {
        var contentsRect = CGRect(origin: .zero, size: CGSize(width: 1, height: 1))

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

        let viewBounds = bounds
        let imageViewFactor = viewBounds.size.width / viewBounds.size.height
        let imageFactor = imageSize.width / imageSize.height

        if imageFactor > imageViewFactor {
            // Image is wider than the view, so height will match
            let scaledImageWidth = viewBounds.size.height * imageFactor
            var xOffset: CGFloat = 0.0

            if case .left = horizontalAlignment {
                xOffset = -(scaledImageWidth - viewBounds.size.width) / 2
            }
            else if case .right = horizontalAlignment {
                xOffset = (scaledImageWidth - viewBounds.size.width) / 2
            }

            contentsRect.origin.x = xOffset / scaledImageWidth
        }
        else {
            let scaledImageHeight = viewBounds.size.width / imageFactor
            var yOffset: CGFloat = 0.0

            if case .top = verticalAlignment {
                yOffset = -(scaledImageHeight - viewBounds.size.height) / 2
            }
            else if case .bottom = verticalAlignment {
                yOffset = (scaledImageHeight - viewBounds.size.height) / 2
            }

            contentsRect.origin.y = yOffset / scaledImageHeight
        }

        layer.contentsRect = contentsRect
    }

}

正确的答案! - Fattie

14

UIImageView底部的布局约束优先级设置为最低(即250),它将为您处理。


这对我有用,谢谢 @protspace(ios13.4 Xcode 11.3) - ptc

11

这将使图像填充宽度,并仅占用需要适应图像的高度 (就宽度而言)

Swift 4.2:

let image = UIImage(named: "my_image")!
let ratio = image.size.width / image.size.height
cardImageView.widthAnchor
  .constraint(equalTo: cardImageView.heightAnchor, multiplier: ratio).isActive = true

2
  1. 使用图片比例添加“长宽比”约束。
  2. 不要将UIImageView固定在底部。
  3. 如果您想动态更改UIImage,请记得更新长宽比约束。

2

我有类似的问题。 最简单的方法是创建自己的UIImageView子类。我为子类添加了3个属性,因此现在可以在不知道内部实现的情况下轻松使用:

@property (nonatomic) LDImageVerticalAlignment imageVerticalAlignment;
@property (nonatomic) LDImageHorizontalAlignment imageHorizontalAlignment;
@property (nonatomic) LDImageContentMode imageContentMode;

您可以在此处查看: https://github.com/LucasssD/LDAlignmentImageView

0

我在Interface Builder中通过设置UIImageView的高度约束来解决了这个问题,因为当图像大于屏幕尺寸时,图像会被“推”上去。

更具体地说,我通过高度约束将UIImageView设置为与其所在的视图相同的高度,然后在IB中使用间距约束来定位UIImageView。这样,UIImageView就具有了“Aspect Fit”的效果,同时仍然遵循我在IB中设置的顶部间距约束。


-3
如果你能够子类化 `UIImageView`,那么你可以重写 `image` 变量。
override var image: UIImage? {
    didSet {
        self.sizeToFit()
    }
}

在 Objective-C 中,你可以通过重写 setter 方法来实现相同的功能。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接