-1

I am working in an iOS 11.X app with Swift 4.1, fully programatic. Using Xcode 9.4

I am trying to have a view centered in the middle (horizontally), with a distance of 10 pts to the top of the view, with rotation support.

The app architecture has the regular stuff, as follows (no storyboard):

  • AppDelegate with a UIWindow object and a UINavigationController as root
  • The navigationController has a UIViewController() as rootViewController

Given that in landscape mode, the statusBar is not visible and to simplify the autolayout configuration, I decided to add a top anchor to the navigationBar as follows (code was simplified for clarity):

let navBar = self.navigationController!.navigationBar
let label = UILabel()
label.backgroundColor = .yellow
label.text = "TEXT"
label.textAlignment = NSTextAlignment.center
self.view.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.widthAnchor.constraint(equalToConstant: 50).isActive = true
label.heightAnchor.constraint(equalToConstant: 20).isActive = true
label.centerXAnchor.constraint(equalTo: label.superview!.centerXAnchor).isActive = true
label.topAnchor.constraint(equalTo: navBar.bottomAnchor, constant: 10).isActive = true

The result is as expected. It rotates nicely, and also works fine if the App is launched in landscape or portrait (both in the device and in the simulator).

PORTRAIT enter image description here

LANDSCAPE enter image description here

Now. FastForward 1 commit in GitHub, with no changes in the overall App View Architecture, and the same code, it crashes with the following message

*** NSGenericException', reason: 'Unable to activate constraint with anchors and because they have no common ancestor. Does the constraint or its anchors reference items in different view hierarchies? That's illegal.

Any idea of what may be going on here?. I downloaded the .zip from the previous GitHub commit to have it isolated and to re-confirm that it works!!. But the current commit fails without no changes to the code related to this UILabel.

The navBar is part of UIWindow->UILayoutContainerView->UINavigationBar

  • Why is Xcode complaining about a NSLayoutYAxisAnchor anchor? (I only have centerX and top anchors)
  • Why the label.topAnchor to the navBar.bottomAnchor works in the previous commit (even though they are not in the same view hierarchy)?
eharo2
  • 2,553
  • 1
  • 29
  • 39
  • "I downloaded the .zip from the previous GitHub commit" Why don't you tell us the URL, so we can do that too and see what you're talking about? – matt Jul 20 '18 at 19:38
  • It's very difficult to know what the issue is with that latest commit when we can't see what was changed. If you can update your post with more code and details of the latest commit, the community might be able to help more. – Derek Jul 20 '18 at 19:47
  • This should not have worked to begin with... You are adding the label to `self.view`, and the Navigation Bar is *not* part of `self.view`'s hierarchy. – DonMag Jul 20 '18 at 20:08
  • @matt. This is an internal project, but you will find the code in question in the DropBox link in the answer I added. – eharo2 Jul 20 '18 at 20:49
  • @Derek, I thought my question was self contained enough to convey the issue. It seems that I didn't explain enough. – eharo2 Jul 20 '18 at 20:49
  • Hi @DonMag. The label is added to the view as part of the viewController, and I know that they are not part of the same hierarchy, but it works. Check the project I added in the DropBox link. – eharo2 Jul 20 '18 at 20:49
  • 2
    You should be constraining your label to the `topLayoutGuide`, not the navigation bar. – dan Jul 20 '18 at 20:59
  • 2
    *"they are not part of the same hierarchy, but it works"* ... generally considered a not-so-good approach. Just because you can get something to "work" doesn't mean it will continue to do so. Much better to use proper constraints than to try and "force it" with invalid constraints. – DonMag Jul 20 '18 at 20:59
  • Thx @DonMag. topLayoutGuide (and safeAreaLayoutGuide for iOS 11) are what I was looking for. – eharo2 Jul 20 '18 at 21:29

1 Answers1

0

I was able to identify what causes the crash.

The only difference in the last commit is a small delay (before adding the UI elements and the constraints). 0.01 seconds are enough and it works fine as follows:

import UIKit
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        self.title = "APP TITLE"
        self.view.backgroundColor = .white
        Timer.scheduledTimer(withTimeInterval: 0.01, repeats: false) { (nil) in
            let navBar = self.navigationController!.navigationBar
            let label = UILabel()
            label.backgroundColor = .yellow
            label.text = "TEXT"
            label.textAlignment = NSTextAlignment.center
            self.view.addSubview(label)
            label.translatesAutoresizingMaskIntoConstraints = false
            label.widthAnchor.constraint(equalToConstant: 50).isActive = true
            label.heightAnchor.constraint(equalToConstant: 20).isActive = true
            label.centerXAnchor.constraint(equalTo: label.superview!.centerXAnchor).isActive = true
            label.topAnchor.constraint(equalTo: navBar.bottomAnchor, constant: 10).isActive = true
        }
    }
}

You will find a clean Xcode project with the working code here https://www.dropbox.com/s/vhd9emd8ixf5lbs/AutoLO.zip?dl=0

Comment the Timer.scheduledTimer(withTimeInterval: repeats:) lines and it will crash with the message:

*** Terminating app due to uncaught exception 'NSGenericException', reason: 'Unable to activate constraint with anchors NSLayoutYAxisAnchor:0x60c000267e80 "UILabel:0x7fe0ede0b960'TEXT'.top" and NSLayoutYAxisAnchor:0x60c000267f00 "UINavigationBar:0x7fe0edd06ce0.bottom" because they have no common ancestor. Does the constraint or its anchors reference items in different view hierarchies? That's illegal.

The explanation of why Xcode complains about a centerY / YAxis anchor when there is none, is a mystery to me.

eharo2
  • 2,553
  • 1
  • 29
  • 39
  • Well, first of all, `Timer.scheduledTimer` is not how you do a delay of this sort. Second, what's the purpose of the delay in the first place? That's a very weird thing to do. – matt Jul 20 '18 at 20:51
  • It may not be the most elegant way to do a delay, but adding the constraints after 0.01 seconds allows for the view to be loaded and this way, the system allows you to add an anchor relative to the navBar that is not part of the same view hierarchy. Without the delay, I have the crash related to the NSLayoutYAxisAnchor uncaught exception. Check the dropbox project. – eharo2 Jul 20 '18 at 21:15
  • 1
    The crash is because the view hasn't been added to the navigation controller's view hierarchy yet when `viewDidLoad` is called. Adding the delay pushes the addition of the constraints to the next run loop which is enough for it to be after the view is added in this case. You shouldn't be constraining things to the navigation bar, constrain the label to the view controller's layout guides which account for the navigation/status bars and you won't have to add the delay or care about whether or not the controller is in a navigation controller at all. – dan Jul 20 '18 at 21:24
  • This is the correct implementation: if #available(iOS 11.0, *) { let safeArea = self.view.safeAreaLayoutGuide label.topAnchor.constraint(equalTo: safeArea.topAnchor, constant: 10).isActive = true } else { let topGuide = self.topLayoutGuide label.topAnchor.constraint(equalTo: topGuide.bottomAnchor, constant: 10).isActive = true } – eharo2 Jul 20 '18 at 21:38