1

I have the following extension that I got from this SO question a few months ago, and I'd like to translate this to NSLayoutAnchor (or the more verbose layout APIs) due to the simple reason that the visual format does not support specifying the safe area layout guide.

The extension in the link is this one:

extension UIView {

    /// Adds constraints to this `UIView` instances `superview` object to make sure this always has the same size as the superview.
    /// Please note that this has no effect if its `superview` is `nil` – add this `UIView` instance as a subview before calling this.
    func bindFrameToSuperviewBounds() {
        guard let superview = self.superview else {
            print("Error! `superview` was nil – call `addSubview(view: UIView)` before calling `bindFrameToSuperviewBounds()` to fix this.")
            return
        }

        self.translatesAutoresizingMaskIntoConstraints = false
        superview.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[subview]-0-|", options: .directionLeadingToTrailing, metrics: nil, views: ["subview": self]))
        superview.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[subview]-0-|", options: .directionLeadingToTrailing, metrics: nil, views: ["subview": self]))
    }
}

My view hierarchy is:

UINavigationController -> root view controller: UIPageViewController -> First page: UIViewController -> UIScrollView -> UIImageView

When I call bindFrameToSuperviewBounds(), the result is the following:

enter image description here

Which looks mostly fine. The problem is that the image view is under the navigation bar, which I do not want. So in order to fix this, I attempted to rewrite bindFrameToSuperviewBounds() as this:

guard let superview = self.superview else {
            fatalError("Attempting to bound to a non-existing superview.")
        }

translatesAutoresizingMaskIntoConstraints = false
self.topAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.topAnchor).isActive = true
self.leadingAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.leadingAnchor).isActive = true
self.bottomAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.bottomAnchor).isActive = true
self.trailingAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.trailingAnchor).isActive = true

The result is then this:

enter image description here

This time it correctly doesn't go under the navigation bar, but well... You can see it gets stretched, which I do not want.

What's the right way to translate that visual layout API to something that can take the safe area layout guide into account?

For the sake of completion, the code that sets up the scroll view and image view is below:

override func viewDidLoad() {
    super.viewDidLoad()
    imageScrollView.addSubview(imageView)
    imageScrollView.layoutIfNeeded()
    imageView.bindFrameToSuperviewBounds()
    view.addSubview(imageScrollView)
    imageView.layoutIfNeeded()
    imageScrollView.backgroundColor = .white
    imageScrollView.bindFrameToSuperviewBounds()
    imageScrollView.delegate = self
}
Andy Ibanez
  • 12,104
  • 9
  • 65
  • 100

2 Answers2

1

I honestly would not bother with the bizarre "visual" layout things, they will be gone soon.

Does this help?

extension UIView {

    // put this view "inside" it's superview, simply stretched all four ways
    // amazingly useful

    func bindEdgesToSuperview() {

        guard let s = superview else {
            preconditionFailure("`superview` nil in bindEdgesToSuperview")
        }

        translatesAutoresizingMaskIntoConstraints = false
        leadingAnchor.constraint(equalTo: s.leadingAnchor).isActive = true
        trailingAnchor.constraint(equalTo: s.trailingAnchor).isActive = true
        topAnchor.constraint(equalTo: s.topAnchor).isActive = true
        bottomAnchor.constraint(equalTo: s.bottomAnchor).isActive = true
    }

}

Just use like this ..

    textOnTop = ..  UILabel or whatever
    userPhoto.addSubview(textOnTop)
    textOnTop.bindEdgesToSuperview()

easy !

(PS - don't forget, if it's an image: You almost certainly want to set as AspectFit, at all times in all cases.)

The handles for safe layout guide are like ...

.safeAreaLayoutGuide
Fattie
  • 27,874
  • 70
  • 431
  • 719
  • I should have probably mentioned that I am not using storyboards and I write my UI code. I guess I didn't phrase my question correctly. Your sample code does the translation, but it does not take into account the safe layout margins so the result is the same one as the first photo I posted. Also, considering the question can be reached by many people, you should definitely address everything that is wrong with the sample code I provided. Your code and I are essentially the same, with the difference I do try to make use of the safe layout guide. – Andy Ibanez Oct 22 '17 at 00:36
  • Hi @AndyIbanez, yes exactly, that was my point. **I know you are doing it in code. Honestly, >>>DO NOT USE<<< the whacky visual-language thing.** It is utterly stupid, doesn't work, and is going to be deprecated any day now! – Fattie Oct 22 '17 at 15:02
  • Oh, sorry I did not include `.safe .. etc` as an example of one of the constraints. Looks like you got it below! – Fattie Oct 22 '17 at 15:04
  • One final point! In all projects with all teams for all clients, we **never ever** use the "safe guide" stuff. Heh! Many other teams and projects also adopt this policy. For anyone googling here, you can just turn off the "safe area guide" rubbish in your opening storyboard under file inspector, hope it helps someone – Fattie Oct 22 '17 at 15:08
1

With the code provided by Fattie, I was able to arrive to this solution:

guard let superview = self.superview else {
    fatalError("Attempting to bound to a non-existing superview.")
}

translatesAutoresizingMaskIntoConstraints = false
self.topAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.topAnchor).isActive = true
self.leadingAnchor.constraint(equalTo: superview.leadingAnchor).isActive = true
self.bottomAnchor.constraint(equalTo: superview.bottomAnchor).isActive = true
self.trailingAnchor.constraint(equalTo: superview.trailingAnchor).isActive = true

(Now that I am only anchoring the the top to the safe layout are guide. Whether this gives problems in an iPhone X is yet to be seen).

Which gives the result I expect:

enter image description here

Andy Ibanez
  • 12,104
  • 9
  • 65
  • 100