iosswiftautolayoutnslayoutconstraint

iOS Autolayout - unable to have height be lessThanOrEqualTo


I am using SnapKit for auto layout in my iOS app.

I need to show an image view at the very top of the screen. It will have an image of dynamic height and width. I need its top edge to be aligned with top of the view. I need it to be horizontally centred. I need it to have a left and right margin from the screen of greater than or equal to 10. I need its height to be dynamic based on the content with maximum height equal to 300. I need it to maintain its aspect ratio and reduce its width or height as needed automatically using auto layout.

I thought the below code should do the work:

let padding = 10.0
let preview = UIImageView()

preview.setContentHuggingPriority(.required, for: .vertical)
preview.setContentHuggingPriority(.required, for: .horizontal)

preview.backgroundColor = .red
preview.contentMode = .scaleAspectFit
let image = UIImage(named: "demo3")!
preview.image = image

preview.clipsToBounds = true
view.addSubview(preview)
preview.snp.makeConstraints { make in
    make.top.equalTo(view.safeAreaLayoutGuide.snp.top)
    make.centerX.equalToSuperview()
    make.left.right.greaterThanOrEqualToSuperview().inset(padding).priority(.required)
    make.height.lessThanOrEqualTo(300).priority(.required)
    make.width.equalTo(preview.snp.height).multipliedBy(image.size.width / image.size.height).priority(.required)
}

This seems to work fine for landscape images (width longer than height) but it breaks in portrait images:




Works in landscape images:

enter image description here







Fails in portrait images. Notice how the height is no longer lessThanOrEqualTo(300):

enter image description here

This is due to Xcode prints a warning that it was unable to satisfy the constraints and it had to break the height constraint:

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:0x600003928540@TuningViewController.swift#54 UIImageView:0x7f81b19101e0.centerX == UIView:0x7f81b183dc30.centerX>",
    "<SnapKit.LayoutConstraint:0x600003928180@TuningViewController.swift#55 UIImageView:0x7f81b19101e0.right >= UIView:0x7f81b183dc30.right - 12.0>",
    "<SnapKit.LayoutConstraint:0x6000039284e0@TuningViewController.swift#56 UIImageView:0x7f81b19101e0.height <= 300.0>",
    "<SnapKit.LayoutConstraint:0x6000039289c0@TuningViewController.swift#57 UIImageView:0x7f81b19101e0.width == UIImageView:0x7f81b19101e0.height * 0.666748046875>",
    "<NSLayoutConstraint:0x600003e37a20 'UIView-Encapsulated-Layout-Width' UIView:0x7f81b183dc30.width == 430   (active)>"
)

Will attempt to recover by breaking constraint 
<SnapKit.LayoutConstraint:0x6000039284e0@TuningViewController.swift#56 UIImageView:0x7f81b19101e0.height <= 300.0>

I would have expected it to reduce its width while maintaining the aspect ratio to satisfy the conditions. But it doesn't do so.


Solution

  • I think what you need here is replacing

    make.left.right.greaterThanOrEqualToSuperview().inset(padding).priority(.required)
    

    with

    make.left.greaterThanOrEqualToSuperview().inset(padding)
    make.right.lessThanOrEqualToSuperview().inset(padding)
    

    For me it works perfectly fine now!

    landscape portrait