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

Posted

技术标签:

【中文标题】如何在这些 UIImageViews 之间保持图像的位置和大小不变?【英文标题】:How do I keep the position and size of images constant between these UIImageViews? 【发布时间】:2020-01-12 16:40:17 【问题描述】:

如你所见,我有这个霓虹磁带的场景。在界面构建器中,我创建了 3 个 UIImageView,一个用于卡带背景,2 个用于左右主轴。这个想法是这些主轴根据状态(倒带,ffw或播放)向后或向前旋转。如果我只在 iPhone 8 上运行它,我可以做到这一点。但是,当我决定要在 iPhone 11 Pro 上运行它时,会发生以下情况:

无论我尝试编造何种奇怪的约束组合,我都无法让心轴在设备上遵循其正确的位置/尺寸。我将不得不花一些时间查找和阅读有关自动布局野兽的指南,但在爬上那座山之前,我将不胜感激!

编辑:

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

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)。也许我已经挖掘得太深了,有一个更简单的方法来处理这个我没有看到......

【问题讨论】:

@matt 卡带图像通过自动布局调整大小:Safe Area.bottom = Cassette.bottom + 44,Safe Area.trailing = Cassette.trailing + 20,cassette.leading,Safe Area.leading + 20,cassette.top = Safe Area.top + 44。所以,根据我目前的理解,图像应该根据这些约束调整大小,不是吗?视图的内容模式是“Aspect Fit”。换句话说,卡带应始终保持距离顶部/底部 44 点和距离尾随/领先 20 点。 方面适合。因此,您必须使主轴表现得像宽高比匹配图像的一部分。您必须模拟图像大小调整的数学运算。 @matt 我原以为 Aspect Fit 会将主轴保持为球体。无论如何,我想我应该用一些计算值改变某些约束的乘数以达到预期的效果? @matt 我认为不可能在 Interface Builder 中实现这一点?即只能以编程方式实现? 您可以在 IB 中做的任何事情都可以通过编程方式进行。 IB 只是一个用于在 ios 开发中做某些事情(不是全部)的 GUI,比如添加和定位子视图。我认为以编程方式进行操作会更容易(也更快)。 【参考方案1】:

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

让我们为一个主轴(左主轴)执行此操作。您发布的卡带图像为 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 的结果:

如果我自己这么说的话,看起来太棒了。右边的纺锤作为练习留给读者。

【讨论】:

太棒了!这正是我一直在寻找的。我会将此标记为正确,但也会为两个主轴添加我完成的代码。感谢您的帮助;非常感谢!【参考方案2】:

感谢@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

【讨论】:

以上是关于如何在这些 UIImageViews 之间保持图像的位置和大小不变?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 UIScrollView 中的 UIImageViews 之间添加分隔符并保持分页滚动高度 480?

自动布局和 UIImageViews

UIStackView不改变宽度

在调整窗口时如何在Kivy中的图像之间保持固定的宽度?

iOS:在数组中存储指向 UIImageViews 的链接,然后使用它们

UIStackView 不改变宽度