0

Using Swift5.1.3, XCode11.3, iOS13.3,

I try to reposition a custom navigationBar titleView.

Creating the custom view and adding it to my navigationBar works fine. (see code below)

Here an example: Please only consider the DarkGray NavigationBar on top with a Name-Label and a yellow round Image. The label and image shall be moved in y-direction!

enter image description here

The example on the left, I have successfully running. The example on the right I try to achieve. But without luck so far.

There is one missing thing I am struggling with since 4 hours.

How do I adjust the y-position (or .topAnchor constant offset) of a custom navigationBar titleView ???

The crash-message says:

'Unable to activate constraint with anchors
<NSLayoutYAxisAnchor:0x6000033ac900 "UIStackView:0x7fdcced39ea0.top"> and
<NSLayoutYAxisAnchor:0x6000033644c0 "UIView:0x7fdcd412ba20.top"> because they
have no common ancestor.  Does the constraint or its anchors reference items
in different view hierarchies?  That's illegal.'

Here is my code (please note the comment with the many exclamation marks - that is the y-offset trial and crash position of my code):

override func viewWillAppear(_ animated: Bool) {

    super.viewWillAppear(animated)

    // ...

    // set up navigationItem and navigationController look and feeel
    navigationController?.set_iOS12_lookAndFeel()
    navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
    navigationItem.largeTitleDisplayMode = .always

    // create NavigationBar.titleView StackView (consisting of a label and a button)
    let titleStackView = UIStackView(frame: CGRect(origin: .zero, size: CGSize(width: self.view.bounds.width, height: 88.0)))
    titleStackView.isUserInteractionEnabled = true
    titleStackView.axis = .horizontal
    titleStackView.alignment = .center
    titleStackView.spacing = 10.0
    // stackView label
    let labelWidth: CGFloat = UIScreen.main.bounds.width - 16.0 - 10.0 - 36.0 - 16.0   // FullScreenWidth minus (Leading + Spacing + ButtonWidth + Trailing)
    let label = UILabel()
    label.font = AppConstants.Font.NavBar_TitleFont
    label.text = self.profileName
    label.textColor = .white
    label.tintColor = .white
    // position label
    label.translatesAutoresizingMaskIntoConstraints = false
    label.widthAnchor.constraint(equalToConstant: labelWidth).isActive = true
    // stackView button
    let buttonWidth: CGFloat = 36.0
    let button = UIButton(frame: CGRect(origin: .zero, size: CGSize(width: buttonWidth, height: buttonWidth)))
    button.setImage(self.profileImageView.image, for: .normal)
    button.isUserInteractionEnabled = true
    button.addTarget(self, action: #selector(self.callProfileBtnMethod), for: .touchUpInside)
    button.frame = CGRect(x: 0, y: 0, width: 36, height: 36)
    button.layer.cornerRadius = button.frame.size.width / 2
    button.layer.masksToBounds = false
    button.clipsToBounds = true
    // position button
    button.translatesAutoresizingMaskIntoConstraints = false
    button.widthAnchor.constraint(equalToConstant: buttonWidth).isActive = true
    button.heightAnchor.constraint(equalToConstant: buttonWidth).isActive = true
    // add label and button to stackView
    titleStackView.addArrangedSubview(label)
    titleStackView.addArrangedSubview(button)
    // position titleStackView
    titleStackView.translatesAutoresizingMaskIntoConstraints = false

    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    // Here the code crashes !!!!!!!
    titleStackView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 100.0).isActive = true
    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

    // position cockpitHeaderView (equal in size and position to titleStackView)
    let cockpitHeaderView = UIView(frame: CGRect(origin: .zero, size: CGSize(width: self.view.bounds.width, height: 88.0)))
    cockpitHeaderView.backgroundColor = .green
    cockpitHeaderView.isUserInteractionEnabled = true
    cockpitHeaderView.addSubview(titleStackView)
    cockpitHeaderView.leadingAnchor.constraint(equalTo: titleStackView.leadingAnchor, constant: 0.0).isActive = true
    cockpitHeaderView.topAnchor.constraint(equalTo: titleStackView.topAnchor, constant: 0.0).isActive = true
    cockpitHeaderView.trailingAnchor.constraint(equalTo: titleStackView.trailingAnchor, constant: 0.0).isActive = true
    cockpitHeaderView.bottomAnchor.constraint(equalTo: titleStackView.bottomAnchor, constant: 0.0).isActive = true
    // finally replace NavBar title by custom cockpitHeaderView
    self.title = ""
    self.navigationItem.titleView = cockpitHeaderView
}

How can I move the titleView correctly ???

iKK
  • 6,394
  • 10
  • 58
  • 131
  • Maybe you can try making your constraint relative to cockpitHeaderView instead. The error suggest that self.view and the stackview are in different hierachies and thus not related, since stackview is actually in the title view and self.view is referring to the content view, thus these 2 have no common parent views at all – shikai ng Jan 17 '20 at 01:54
  • I tried your solution and replaced the cockpitHeaderView's `topAnchor`-constraint to be equal to `titleStackView.topAnchor.constraint(equalTo: cockpitHeaderView.topAnchor, constant: 40.0).isActive = true`. In addition, I removed the cockpitHeaderView's bottom anchor. And it somewhat works and the titleStackView is shifted in y as expected. However, there are two problems: – iKK Jan 17 '20 at 08:52
  • The cklickable area of the titleStackView's button is no longer valid outside the old navigationItem area. And a second problem: when navigating to a detailVC and back, the titleStackView disappears completely. (probably again due to the fact that navigationItem as a fixed size somehow and everything outside its approx 40 pixel height gets clipped and also has no click event reception anymore., I also tried to add `self.navigationItem.titleView?.clipsToBounds = false` but no success ! Any other ideas? – iKK Jan 17 '20 at 08:52
  • Actually, If i were to do what you want to do, I would just avoid using the title view altogether and put the stackview within the content view instead, since you intent to flush it down to content view anyway and not upwards overlapping the title view. Is there any reason why you want to put stack view within the navigationItem title view? – shikai ng Jan 20 '20 at 07:20
  • Thank you, shikai. First this is exactly what I ended up doing: i.e. setting the navigationBar as smallTitle (with empty title-string) and the same color as my newly placed stackView at the right x/y coordinates ! – iKK Jan 20 '20 at 14:17
  • But then, My initial motivation came back - i.e. to combine my custom stackView with the navigationItem. The reason: This way the segue-navigation would be "for free". (you could animate the stackView in a similar way as navigationItems do when a present-to-detailVC happens, but this is also some work to do). In fact, what I ended up with is the following: – iKK Jan 20 '20 at 14:19
  • I use the native largeTitle NavigationBar-Title and only show the round image as a custom stackView placed as `self.navigationController?.view.addSubview(self.titleStackView)`. But if you do so, you must pay careful attention to the order of subview addition during your screen's life-cycle. I ended up adding the custom stackView as a subview to the navigationController.view inside `viewDidLoad` and ended up setting its constraints inside `viewDidLoad` AND also inside `viewDidAppear` (and under no circumstances inside `viewWillAppear`). – iKK Jan 20 '20 at 14:25
  • The reason I added it also to `viewDidAppear`is that this was necessary if a back-segue from a detail-VC happens. Then it needs to be freshly placed for some reason. With all this, I have achieved a custom NavigationItem (keeping all the segue animations for free) and still get the benefits of a custom-look-and-feel in my App. – iKK Jan 20 '20 at 14:25
  • Hope, @shikai ng, this answers your question about the necessity to do it with navigationItem... – iKK Jan 20 '20 at 14:26
  • Ahh well its great that you found a solution! I am still kind of confused what segue animation you are trying to achieve in the navigation item that could not be achieved in the content view for free because the content view actually slides along with the navigation item when you perform a show segue (though you might be trying to perform another segue and hence the loss of the free animation). I think you could probably put everything in viewDidAppear too and dont need to do it twice. Can just call layoutifneeded after adding the constraints. But glad that you managed to obtain a solution! – shikai ng Jan 21 '20 at 00:20
  • @shikai: To clarify the "tiny" difference: If you look at the largeTitle when starting up a screen, it animates from small to big. This is basically the tiny diffrence. And yes, of course I could do this all by myself - i.e. inside layoutIfNeeded or viewDidAppear, etc. Again, I am totally the your opinion that both ways would work. – iKK Jan 21 '20 at 07:28

0 Answers0