简体   繁体   English

AutoLayout约束以适合矩形内部的视图,并保留一定的纵横比(以编程方式)

[英]AutoLayout constraints to fit view inside rectangle, preserving a certain aspect ratio (programmatically)

I want to fit an image inside a rectangle that should have a specific aspect ratio. 我想将图像放置在应该具有特定长宽比的矩形内。 No matter what it is, it should find a form to fit inside the rectangle. 不管它是什么,它都应该找到适合矩形内部的形式。 I played around in storyboard and got this: 我在情节提要中玩了一下,得到了:

在此处输入图片说明

The ones with the dotted border have low priority (250). 带有虚线边框的优先级较低(250)。 This works in the storyboard. 这在情节提要中起作用。 However, I need to create these constraints programmatically, so I tried it like this (I'm using SnapKit , which simply provides better AutoLayout syntax. It should be self-explaining): 但是,我需要以编程方式创建这些约束,所以我这样尝试了(我使用的是SnapKit ,它只是提供了更好的AutoLayout语法。这应该是不言自明的):

let topView = UIView()
topView.translatesAutoresizingMaskIntoConstraints = false
topView.backgroundColor = .gray
view.addSubview(topView)

topView.snp.makeConstraints { (make) in
        make.top.equalToSuperview()
        make.left.equalToSuperview()
        make.trailing.equalToSuperview()
        make.height.equalTo(250)
        }

// This view should have a specific aspect ratio and fit inside topView
let holderView = UIView()
holderView.translatesAutoresizingMaskIntoConstraints = false
holderView.backgroundColor = .red
topView.addSubview(holderView)

holderView.snp.makeConstraints { (make) in
        make.center.equalToSuperview() // If I remove this one, there's no auto-layout issue, but then it's offset
        make.edges.equalToSuperview().priority(250) // sets leading, trailing, top and bottom
        make.edges.greaterThanOrEqualToSuperview().priority(1000)
        make.width.equalTo(holderView.snp.height).multipliedBy(3/2)
        }

If you paste this into an empty ViewController and start it up, you get these issues: 如果将其粘贴到一个空的ViewController中并启动它,则会出现以下问题:

2018-03-16 15:38:50.188867+0100 DemoProject[11298:850932] [LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want. 
Try this: 
    (1) look at each constraint and try to figure out which you don't expect; 
    (2) find the code that added the unwanted constraint or constraints and fix it. 

"<SnapKit.LayoutConstraint:0x6000000a7c80@ViewController.swift#24 UIView:0x7fcd82d12440.left == UIView:0x7fcd82d12640.left>",
"<SnapKit.LayoutConstraint:0x6000000a7ce0@ViewController.swift#25 UIView:0x7fcd82d12440.trailing == UIView:0x7fcd82d12640.trailing>",
"<SnapKit.LayoutConstraint:0x6000000a7d40@ViewController.swift#26 UIView:0x7fcd82d12440.height == 250.0>",
"<SnapKit.LayoutConstraint:0x6000000a7da0@ViewController.swift#35 UIView:0x7fcd8580dad0.centerX == UIView:0x7fcd82d12440.centerX>",
"<SnapKit.LayoutConstraint:0x6000000a7e00@ViewController.swift#35 UIView:0x7fcd8580dad0.centerY == UIView:0x7fcd82d12440.centerY>",
"<SnapKit.LayoutConstraint:0x6000000a8580@ViewController.swift#37 UIView:0x7fcd8580dad0.top >= UIView:0x7fcd82d12440.top>",
"<SnapKit.LayoutConstraint:0x6000000a8a60@ViewController.swift#37 UIView:0x7fcd8580dad0.right >= UIView:0x7fcd82d12440.right>",
"<SnapKit.LayoutConstraint:0x6000000a9360@ViewController.swift#38 UIView:0x7fcd8580dad0.width == UIView:0x7fcd8580dad0.height>",
"<NSLayoutConstraint:0x600000092cf0 'UIView-Encapsulated-Layout-Width' UIView:0x7fcd82d12640.width == 414   (active)>"


Will attempt to recover by breaking constraint <SnapKit.LayoutConstraint:0x6000000a9360@ViewController.swift#38 UIView:0x7fcd8580dad0.width == UIView:0x7fcd8580dad0.height>

This doesn't show up, when I remove the centering constraint make.center.equalToSuperview() . 当我删除居中约束make.center.equalToSuperview()时,这不会显示。 But then, it's misplaced. 但随后,它放错了位置。

What is different between the storyboard and my code? 情节提要和我的代码之间有什么区别? I don't really understand this. 我不太明白。 I also tried this using the default swift syntax, the result was exactly the same. 我还使用默认的swift语法尝试了此操作,结果完全相同。 So I don't think it's a problem with SnapKit 所以我认为SnapKit问题

Any ideas? 有任何想法吗? Thank you guys for any help. 谢谢大家的帮助。 Let me know if you need any more infos. 让我知道您是否需要更多信息。

EDIT: I mixed something up. 编辑:我混合了一些东西。 It's not about the image and its aspect ratio. 这与图像及其宽高比无关。 It's just about a UIView that should maintain a specific aspect ratio while fitting inside a rectangle. 它只是关于一个UIView ,它应该在安装在矩形内时保持特定的宽高比。 The actual image will just be put into that holderView . 实际图像将被放入该holderView Sorry 抱歉

OK - here is one way to do it. 好的-这是一种方法。

Take the "native" size of your subview, calculate the "aspect fit" ratio - that is, the ratio that will fit the width or height to the superview, and scale the other dimension appropriately. 取子视图的“本机”大小,计算“长宽比”比例-即适合宽度或高度到父视图的比例,并适当缩放其他尺寸。

Then, use centerXAnchor and centerYAnchor to position the subview, and widthAnchor and heightAnchor to size it. 然后,使用centerXAnchorcenterYAnchor定位子视图,并使用widthAnchorheightAnchor其大小。

Note: if you're trying to place an image , calculate the aspect fit size from the image size, put the image in an image view, set the image view scale mode to fill , and finally apply the constraints to the image view. 注意:如果要放置图像 ,请根据图像大小计算宽高比合适的大小,将图像放入图像视图,将图像视图缩放模式设置为fill ,最后将约束应用于图像视图。

You should be able to run this example as-is. 您应该能够按原样运行此示例。 Just play around with the "native" size values at the top to see how it fits the subview into the superview. 只需在顶部使用“本机”大小值即可查看它如何将子视图适合到超级视图中。

public class AspectFitViewController : UIViewController {

    // "native" size for the holderView
    let hViewWidth: CGFloat = 700.0
    let hViewHeight: CGFloat = 200.0

    let topView: UIView = {
        let v = UIView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.backgroundColor = UIColor.blue
        return v
    }()

    let holderView: UIView = {
        let v = UIView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.backgroundColor = UIColor.cyan
        return v
    }()

    public override func viewDidLoad() {
        super.viewDidLoad()
        view.bounds = CGRect(x: 0, y: 0, width: 400, height: 600)
        view.backgroundColor = .yellow

        // add topView
        view.addSubview(topView)

        // pin topView to leading / top / trailing
        topView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0.0).isActive = true
        topView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0).isActive = true
        topView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0.0).isActive = true

        // explicit height for topView
        topView.heightAnchor.constraint(equalToConstant: 250.0).isActive = true

        // add holderView to topView
        topView.addSubview(holderView)

        // center X and Y
        holderView.centerXAnchor.constraint(equalTo: topView.centerXAnchor, constant: 0.0).isActive = true
        holderView.centerYAnchor.constraint(equalTo: topView.centerYAnchor, constant: 0.0).isActive = true

        // holderView's width and height will be calculated in viewDidAppear
        // after topView has been laid-out by the auto-layout engine

    }

    public override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        let aspectWidth  = topView.bounds.size.width / hViewWidth
        let aspectHeight = topView.bounds.size.height / hViewHeight

        let aspectFit = min(aspectWidth, aspectHeight)

        let newWidth = hViewWidth * aspectFit
        let newHeight = hViewHeight * aspectFit

        holderView.widthAnchor.constraint(equalToConstant: newWidth).isActive = true
        holderView.heightAnchor.constraint(equalToConstant: newHeight).isActive = true

    }

}

Edit: 编辑:

After clarification... this can be accomplished by constraints only. 在澄清之后……这只能通过约束来实现。 The key is that "Priority 1000" top and leading constraints must be .greaterThanOrEqual to zero, and the bottom and trailing constraints must be .lessThanOrEqual to zero. 关键是“ Priority 1000” topleading约束必须为.greaterThanOrEqual等于零,而bottomtrailing约束则必须为.lessThanOrEqual等于零。

public class AspectFitViewController : UIViewController {

    // "native" size for the holderView
    let hViewWidth: CGFloat = 700.0
    let hViewHeight: CGFloat = 200.0

    let topView: UIView = {
        let v = UIView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.backgroundColor = UIColor.blue
        return v
    }()

    let holderView: UIView = {
        let v = UIView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.backgroundColor = UIColor.cyan
        return v
    }()

    public override func viewDidLoad() {
        super.viewDidLoad()
        view.bounds = CGRect(x: 0, y: 0, width: 400, height: 600)
        view.backgroundColor = .yellow

        // add topView
        view.addSubview(topView)

        // pin topView to leading / top / trailing
        topView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0.0).isActive = true
        topView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0).isActive = true
        topView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0.0).isActive = true

        // explicit height for topView
        topView.heightAnchor.constraint(equalToConstant: 250.0).isActive = true

        // add holderView to topView
        topView.addSubview(holderView)

        // center X and Y
        holderView.centerXAnchor.constraint(equalTo: topView.centerXAnchor, constant: 0.0).isActive = true
        holderView.centerYAnchor.constraint(equalTo: topView.centerYAnchor, constant: 0.0).isActive = true

        // aspect ratio size
        holderView.widthAnchor.constraint(equalTo: holderView.heightAnchor, multiplier: hViewWidth / hViewHeight).isActive = true

        // two constraints for each side...
        // the .equal constraints need .defaultLow priority
        // top and leading constraints must be .greaterThanOrEqual to 0
        // bottom and trailing constraints must be .lessThanOrEqual to 0

        let topA = NSLayoutConstraint(item: holderView, attribute: .top, relatedBy: .greaterThanOrEqual, toItem: topView, attribute: .top, multiplier: 1.0, constant: 0.0)
        let topB = NSLayoutConstraint(item: holderView, attribute: .top, relatedBy: .equal, toItem: topView, attribute: .top, multiplier: 1.0, constant: 0.0)

        let bottomA = NSLayoutConstraint(item: holderView, attribute: .bottom, relatedBy: .lessThanOrEqual, toItem: topView, attribute: .bottom, multiplier: 1.0, constant: 0.0)
        let bottomB = NSLayoutConstraint(item: holderView, attribute: .bottom, relatedBy: .equal, toItem: topView, attribute: .bottom, multiplier: 1.0, constant: 0.0)

        let leadingA = NSLayoutConstraint(item: holderView, attribute: .leading, relatedBy: .greaterThanOrEqual, toItem: topView, attribute: .leading, multiplier: 1.0, constant: 0.0)
        let leadingB = NSLayoutConstraint(item: holderView, attribute: .leading, relatedBy: .equal, toItem: topView, attribute: .leading, multiplier: 1.0, constant: 0.0)

        let trailingA = NSLayoutConstraint(item: holderView, attribute: .trailing, relatedBy: .lessThanOrEqual, toItem: topView, attribute: .trailing, multiplier: 1.0, constant: 0.0)
        let trailingB = NSLayoutConstraint(item: holderView, attribute: .trailing, relatedBy: .equal, toItem: topView, attribute: .trailing, multiplier: 1.0, constant: 0.0)

        topB.priority = .defaultLow
        bottomB.priority = .defaultLow
        leadingB.priority = .defaultLow
        trailingB.priority = .defaultLow

        NSLayoutConstraint.activate([
            topA, topB,
            bottomA, bottomB,
            leadingA, leadingB,
            trailingA, trailingB
            ])

    }

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM