将按钮限制为 UIImageView 内的图像

Constrain button to image inside UIImageView

我有一个 UIImageView 并且用户可以设置图像以便 width/height 变化。我想在右上角添加一个 "x"- button,但不是来自 UIImageView,而是来自实际的 image

这是现在的样子。整个图就是UIImageView,左边可以看到image,右上角可以看到button.

目前我是这样约束的:

theStackView.addArrangedSubview(self.imageView)
imageContainerView.addSubview(wishImageView)
imageContainerView.addSubview(deleteImageButton)

imageContainerView.heightAnchor.constraint(equalToConstant: 60).isActive = true
imageContainerView.isHidden = true

wishImageView.leadingAnchor.constraint(equalTo: imageContainerView.leadingAnchor, constant: 20).isActive = true
wishImageView.topAnchor.constraint(equalTo: imageContainerView.topAnchor, constant: 3).isActive = true
wishImageView.bottomAnchor.constraint(equalTo: imageContainerView.bottomAnchor, constant: 3).isActive = true
wishImageView.trailingAnchor.constraint(equalTo: imageContainerView.trailingAnchor, constant: -20).isActive = true

deleteImageButton.widthAnchor.constraint(equalToConstant: 10).isActive = true
deleteImageButton.heightAnchor.constraint(equalToConstant: 10).isActive = true
deleteImageButton.topAnchor.constraint(equalTo: wishImageView.topAnchor, constant: 5).isActive = true
deleteImageButton.trailingAnchor.constraint(equalTo: wishImageView.trailingAnchor, constant: -5).isActive = true

这显然是错误的,但有没有办法将其限制为实际的 image

使用您的图片宽度来定位按钮。粗略了解并平滑 UI

 let imgWidth = iv.image?.size.width

    NSLayoutConstraint.activate([
        iv.leadingAnchor.constraint(equalTo: view.leadingAnchor),
        iv.trailingAnchor.constraint(equalTo: view.trailingAnchor),
        iv.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
        iv.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),

        btn.widthAnchor.constraint(equalToConstant: 20),
        btn.heightAnchor.constraint(equalToConstant: 20),
        btn.leadingAnchor.constraint(equalTo: iv.leadingAnchor , constant: (imgWidth ?? 0) > iv.frame.width ? iv.frame.width-20 : imgWidth ?? 0),
 //The issue happen when Image width is larger than the your imageview.Validate it for better result.
        btn.topAnchor.constraint(equalTo: iv.topAnchor,constant: 10)

    ])

我整理了一个示例项目,repo 是 here

基本上你需要做几件事:

  • 计算 您的 UIImageView 帧大小以及显示在 scaledAspectFit 中的 UIMage 帧大小。
  • 创建两个命名约束并在获得按钮框架后动态重新定位按钮。

首先,您需要记住帧可能要到 viewDidLayoutSubviews 才会真正设置。我创建了一个 UIImageView 扩展,可以轻松计算 UIImage 框架的实际位置。 (这是旧的但有效的代码。我相信它可以改进。)

extension UIImageView {
    public var scaleFactor:CGFloat {
        guard let image = self.image, self.frame != CGRect.zero  else {
            return 0.0
        }

        let frame = self.frame
        let extent = image.size
        let heightFactor = frame.height/extent.height
        let widthFactor = frame.width/extent.width

        if extent.height > frame.height || extent.width > frame.width {
            if heightFactor < 1 && widthFactor < 1 {
                if heightFactor > widthFactor {
                    return widthFactor
                } else {
                    return heightFactor
                }
            } else if extent.height > frame.height {
                return heightFactor
            } else {
                return widthFactor
            }
        } else if extent.height < frame.height && extent.width < frame.width {
            if heightFactor < widthFactor {
                return heightFactor
            } else {
                return widthFactor
            }
        } else {
            return 1
        }
    }

    public var imageSize:CGSize {
        if self.image == nil {
            return CGSize.zero
        } else {
            return CGSize(width: (self.image?.size.width)!, height: (self.image?.size.height)!)
        }
    }

    public var scaledSize:CGSize {
        guard let image = self.image, self.frame != CGRect.zero  else {
            return CGSize.zero
        }
        let factor = self.scaleFactor
        return CGSize(width: image.size.width * factor, height: image.size.height * factor)
    }
}

对于第二个项目符号,您需要创建两个 NSConstraint 类型的变量。为此,我改编了两年前的

var btnTop:NSLayoutConstraint!
var btnTrailing:NSLayoutConstraint!

并且在`viewDidLoad:

button.heightAnchor.constraint(equalToConstant: 20).isActive = true
button.widthAnchor.constraint(equalToConstant: 20).isActive = true

btnTop = button.topAnchor.constraint(equalTo: imageView.topAnchor, constant: 10)
btnTop.isActive = true
btnTrailing = button.trailingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: -10)
btnTrailing.isActive = true

请注意,您需要为每个约束编写两行代码!我从来没有弄清楚为什么,但是如果你试图用实际约束添加 isActive 属性 编译器不知道变量的正确类型。

现在,您将所有这些联系在一起 viewDidLayoutSubviews:

let scaledSize = imageView.scaledSize
var imageFrame = CGRect(origin: CGPoint.zero, size: scaledSize)
if scaledSize.width == imageView.frame.width {
    // image fills view along width, calculate Y constant
    imageFrame.origin.y = (imageView.frame.height - scaledSize.height) / 2
} else {
    // image fills view along height, calculate X constant
    imageFrame.origin.x = (imageView.frame.width - scaledSize.width) / 2
}
//btnTop.constant = imageFrame.width - 30
btnTop.constant = imageFrame.origin.y + 10
btnTrailing.constant = ((imageView.frame.width - imageFrame.width - imageFrame.origin.x) * -1) - 10

将按钮放在左上角 简单得多 - 我花了整整 20 分钟才得到正确的计算,使它变成右上角!

在我的测试项目中,我将这段代码封装在 repositionCloseButton() 中,只要应用程序显示新图像就会调用它。这应该适用于纵向和横向,以及纵向和横向图像 - 将 20x20 关闭按钮放置在距离图像右上角 10 点的位置。