iOS Autolayout and Uitoolbar/Uibarbuttonitems

iOS Autolayout and UIToolbar/UIBarButtonItems

Autolayout constraints only work with UIViews and their subclasses.

While UIToolbar allows some UIView based items (such as UISearchBar and UISegmentedControl) they may have to coexist with UIBarButtonItems which do not inherit from UIView.

Until autolayout can work with UIBarButtonItems, do as you have done.

Your alternative is to roll your own toolbar with widgets based only on UIViews.

Auto Layout iOS 11 Toolbar UIBarButtonItem with customView

As shown in the WWDC Video Updating Your App for iOS 11:

"And so now in iOS 11, UI toolbar and UI navigation bar both have
intricate and express support for auto layout."

So my first step was to use layout constraints on he custom view itself:

    UIBarButtonItem *barButtonItem = [[UIBarButtonItem alloc] initWithCustomView:customView];
[barButtonItem.customView.widthAnchor constraintEqualToConstant:375].active = YES;
[barButtonItem.customView.heightAnchor constraintEqualToConstant:44].active = YES;

This resulted in the toolbar showing the customView. The problem was that left and right of the view, there was a gap. And you could see it.
So I looked in the View Hierarchy Debugging tool and realized, there is a UIToolbarContentView on the toolbar. This contentView had the right size (especially width) and I began wondering. I looked at the only subview the contentView had and it was a UIBarButtonStackView. This stackView was somehow limiting my customView in terms of width.

So it looks like this:

contentView |<-fullWidth-------->|
stackView |<-reducedWidth->|
customView |<-reducedWidth->|

What made me curios was, that the customView is not a subview of the stackView. This is probably a consequence of the customView being included in UIBarButtonItem. Any (additional) constrains on the customView remained without change (or crashes because the views aren't in the same hierarchy).

After learing all this, I added an extension to the UIToolbar:

extension UIToolbar {
private var contentView: UIView? {
return subviews.find { (view) -> Bool in
let viewDescription = String(describing: type(of: view))
return viewDescription.contains("ContentView")
}
}

private var stackView: UIView? {
return contentView?.subviews.find { (view) -> Bool in
let viewDescription = String(describing: type(of: view))
return viewDescription.contains("ButtonBarStackView")
}
}

func fitContentViewToToolbar() {
guard let stackView = stackView, let contentView = contentView else { return }
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
stackView.widthAnchor.constraint(equalTo: contentView.widthAnchor).isActive = true

}
}

So what it does is this:
It gets the contentView from the subviews by comparing the names to "ContentView" and gets the stackView by doing the same on the contentView.
Probably a return subviews.first would do the same, but I wanted to be sure.

Then the layout constraints are set and voila: it works in the full width.

I hope someone may find this useful. If there's comments: I'm very open to feedback on this one. Maybe I missed something and all this isn't even necessary.

Edit: The 'find' function is an extension to Sequence. It does 'filter.first' and looks like this:

extension Sequence {
func find(_ isIncluded: (Self.Element) throws -> Bool) rethrows -> Self.Element? {
return try filter(isIncluded).first
}

}



Related Topics



Leave a reply



Submit