如何在这些 UIImageView 之间保持图像的位置和大小不变?

How do I keep the position and size of images constant between these UIImageViews?

如您所见,我有这个霓虹灯盒式磁带的场景。在界面生成器中,我创建了 3 个 UIImageView,一个用于盒式磁带的背景,两个用于左右主轴。这个想法是这些主轴根据状态(倒带、快进或播放)向后或向前旋转。如果我 运行 仅在 iPhone 8 上执行此操作,我可以做到这一点。但是,当我决定要在 iPhone 上执行此操作时 运行 11 Pro,出现以下情况:

无论我试图编造出怎样奇怪的限制组合,我都无法让主轴在设备上遵循它们正确的 position/size。我将不得不花一些时间寻找和阅读关于自动布局这个野兽的指南,但在爬上那座山之前,我将不胜感激任何帮助!

编辑:

这是我目前拥有的程序化版本,因此更具描述性:

override func viewDidLoad() {
    super.viewDidLoad()

    // Code to position/size cassette
    let cassette = UIImageView(image: UIImage(named: "cassette"))
    cassette.translatesAutoresizingMaskIntoConstraints = false
    cassette.contentMode = .scaleAspectFit
    view.addSubview(cassette)

    view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: cassette.bottomAnchor, constant: 44).isActive = true
    view.safeAreaLayoutGuide.trailingAnchor.constraint(equalTo: cassette.trailingAnchor, constant: 20).isActive = true
    cassette.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20).isActive = true
    cassette.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 44).isActive = true

    // Code to position/size spindles
    let spindleLeft = UIImageView(image: UIImage(named: "spindle"))
    spindleLeft.translatesAutoresizingMaskIntoConstraints = false
    spindleLeft.widthAnchor.constraint(equalToConstant: 74).isActive = true
    spindleLeft.heightAnchor.constraint(equalToConstant: 74).isActive = true
    cassette.addSubview(spindleLeft)

    spindleLeft.centerXAnchor.constraint(equalTo: cassette.centerXAnchor, constant: -130.0).isActive = true
    spindleLeft.centerYAnchor.constraint(equalTo: cassette.centerYAnchor, constant: -14.0).isActive = true

}

以下是 2 项资产:

我目前正在努力弄清楚是否有某种方法可以使用设备的 aspectRatio 以及我应该使用什么 UIImageView 内容模式(Aspect Fit、Aspect Fill)。也许我对整体挖掘得太深了,有一种我没有看到的更简单的方法来处理这个问题......

单纯的自动布局是做不到这一点的。如果它是原始图像的一部分,您必须测量主轴的位置,然后根据图像视图的实际大小以及通过其内容模式放置图像的位置以数学方式将主轴放在那里。

让我们对一个主轴(左侧主轴)执行此操作。您发布的磁带图像是 4824x2230。仔细观察发现,如果卡带图片全尺寸显示,主轴中心应该在(1294,1000)左右,边长500像素左右。

这等于说我们要主轴的图像占据一个框(相对于原始盒式图像)为(1045,750,500,500)的正方形。

好的,但这只是图像。我们必须相对于描绘它的图像视图定位主轴。

所以现在我将在图像视图中显示磁带,该图像视图的大小由自动布局调整为较小的尺寸。 (我可以发现 什么 它在 viewDidLayoutSubviews 中的大小,它在自动布局完成其工作后运行。)并让这个图像视图具有 Aspect Fit 的内容模式。图像将被调整大小,并且它的位置可能不在图像视图的原点。

所以您必须(在代码中)解决的问题是:一旦图像已调整大小并重新定位,现在相对于 图像视图 的主轴框架在哪里?然后,您只需将主轴图像视图放在那里。

这是来自viewDidLayoutSubviews的代码; cassette为片盒图像视图,spindle为主轴图像视图:

    let cassettebounds = cassette.bounds
    let cw = cassettebounds.width
    let ch = cassettebounds.height
    let w = 4824 as CGFloat
    let h = 2230 as CGFloat
    var scale = cw/w
    var imw = cw
    var imh = h*scale
    var imx = 0 as CGFloat
    var imy = (ch-imh)/2
    if imh > ch { // we got it backwards
        scale = ch/h
        imh = ch
        imw = w*scale
        imy = 0 as CGFloat
        imx = (cw-imw)/2
    }
    cassette.addSubview(spindle)
    spindle.frame.size.width = 500*scale
    spindle.frame.size.height = 500*scale
    spindle.frame.origin.x = imx + (1045*scale)
    spindle.frame.origin.y = imy + (750*scale)

这是 6s 和 11 Pro 的结果:

如果我自己这么说,看起来真不错。右主轴留作 reader.

的练习

感谢@matt 的出色回答。当需要的是一种更基本的方法时,我肯定是将太多的权力归功于自动布局。我已将他的答案标记为正确,并将提供可运行并支持所有尺寸屏幕的新代码:

override func viewDidLoad() {
    super.viewDidLoad()

    // Code to position/size cassette
    cassette = UIImageView(image: UIImage(named: "cassette"))
    cassette.translatesAutoresizingMaskIntoConstraints = false
    cassette.isUserInteractionEnabled = true
    cassette.contentMode = .scaleAspectFit
    view.addSubview(cassette)

    view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: cassette.bottomAnchor, constant: 44).isActive = true
    view.safeAreaLayoutGuide.trailingAnchor.constraint(equalTo: cassette.trailingAnchor, constant: 20).isActive = true
    cassette.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20).isActive = true
    cassette.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 44).isActive = true
}

override func viewDidLayoutSubviews() {
    spindleLeft = addSpindle(to: cassette, posX: 505, posY: 350)
    spindleRight = addSpindle(to: cassette, posX: 1610, posY: 350)
}

func addSpindle(to cassette: UIImageView, posX: CGFloat, posY: CGFloat) -> UIImageView {
    let cassetteBounds = cassette.bounds
    let cassetteWidth = cassetteBounds.width
    let cassetteHeight = cassetteBounds.height

    let cassetteImageWidth =  cassette.image!.size.width
    let cassetteImageHeight = cassette.image!.size.height

    var scale = cassetteWidth / cassetteImageWidth

    var spindleWidth = cassetteWidth
    var spindleHeight = cassetteImageHeight*scale
    var spindleX = 0 as CGFloat
    var spindleY = (cassetteHeight - spindleHeight)/2

    if spindleHeight > cassetteHeight {
        scale = cassetteHeight / cassetteImageHeight
        spindleHeight = cassetteHeight
        spindleWidth = cassetteImageWidth*scale
        spindleY = 0 as CGFloat
        spindleX = (cassetteWidth - spindleWidth)/2
    }

    let spindle = UIImageView(image: UIImage(named: "spindle"))
    cassette.addSubview(spindle)

    spindle.frame.origin.x = spindleX + (posX*scale)
    spindle.frame.origin.y = spindleY + (posY*scale)
    spindle.frame.size.width = spindle.image!.size.width*scale //299.0
    spindle.frame.size.height = spindle.image!.size.height*scale //299.0

    return spindle
}