Search code examples
iosswiftuiscrollviewautolayout

Set NSLayoutConstraint of view to top right


I'm at wits end trying to figure this out. In CSS, you can do something like this:

img {
  position: absolute;
  width: 50px;
  height: 50px;
  top: 0;
  right: 0;
}

In Swift, I am using the programatic interface to set layout constraints of an image. It looks like this:

let img = UIImageView(image: UIImage(named: "my-image"))
img.setTranslatesAutoresizingMaskIntoConstraints(false)
mainScrollView.addSubview(img)
mainScrollView.addConstraint(NSLayoutConstraint(item: img, attribute: .Width,    relatedBy: .Equal, toItem: nil,            attribute: .NotAnAttribute, multiplier: 1, constant: 50))
mainScrollView.addConstraint(NSLayoutConstraint(item: img, attribute: .Height,   relatedBy: .Equal, toItem: nil,            attribute: .NotAnAttribute, multiplier: 1, constant: 50))
mainScrollView.addConstraint(NSLayoutConstraint(item: img, attribute: .Top,      relatedBy: .Equal, toItem: mainScrollView, attribute: .Top,            multiplier: 1, constant: 0))
mainScrollView.addConstraint(NSLayoutConstraint(item: img, attribute: .Trailing, relatedBy: .Equal, toItem: mainScrollView, attribute: .Trailing,       multiplier: 1, constant: 0))

However for some bizarre reason it respects the width and height layout constraints, even the top one. but it's basically off screen to the left by 50. If I set the last .Trailing constant to 50, it actually moves it into view at the top left (yes I'm very confused by this).


Solution

  • The constraints should be applied to the view which is the nearest common ancestors of the view involved in the constraint. So I can see two errors in the code you posted:

    1. if you are setting the height or the width of a view, the view itself is the nearest common ancestor view so the constraint should be applied directly to it. So your code should be:

      item.addConstraint(NSLayoutConstraint(item: img, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 50))
      item.addConstraint(NSLayoutConstraint(item: img, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 50))
      
    2. Since the views involved in this constraint:

      NSLayoutConstraint(item: img, attribute: .Top, relatedBy: .Equal, toItem: mainView, attribute: .Top, multiplier: 1, constant: 0))
      

      are one the subview of the other, it should be applied to the parent view:

      mainView.addConstraint(NSLayoutConstraint(item: img, attribute: .Top, relatedBy: .Equal, toItem: mainView, attribute: .Top, multiplier: 1, constant: 0)))
      

    Regarding the problem with the Trailing attribute you should considering that when you are adding a constraint you are defining this formula:

    item1.attribute = item2.attribute + constant
    

    So if you are defining a constraint where both the attributes are Trailing you should to use a negative constant to have one view inside the other:

    --------------------------
    View1                    |
    -------------            |
    View2       |  - (-50) - |