How to add a UITextField to UIToolbar with flexible width in iOS 11 using auto-layout
Asked Answered
S

1

10

I want to add a UITextField to a UITooolbar and let the UITextField expand its width as needed to fill the area, like a UIBarButtonSystemItemFlexibleSpace does.

Below is a snippet about how i normally set up a toolbar items in a view controller ...

// two UIBarButtonItems
UIBarButtonItem *left = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:nil];
UIBarButtonItem *right = [[UIBarButtonItem alloc] initWithTitle:@"Cancel" style:UIBarButtonItemStylePlain target:nil action:nil];

// the UIBarButtonItem with a UITextField as custom view
UITextField *textField = [[UITextField alloc] init];
textField.borderStyle = UITextBorderStyleRoundedRect;
UIBarButtonItem *text = [[UIBarButtonItem alloc] initWithCustomView:textField];

[self setToolbarItems:@[left, text, right]];

... which will then show up like this.

ios11_toolbar

Before iOS 11 i sub-classed UIToolbar and used the layoutSubviews method to get the width (CGRectGetWidth(itemView.frame)) for all 'non-flexible' items and calculate the available width for the textfields view frame. With iOS 11, Apple changed to auto-layout all bars as mentioned at WWDC2017 session 204 and now the CGRectGetWidth(itemView.frame) way will not provide a valid width until the view is visible which was also pointed out at iOS 11 UIBarButtonItem images not sizing.

How can i now add flexible sized UIBarButtonItems with customViews using constraints and auto-layout which will expand to fill the complete free area between two items?

EDIT 1:

If i add only the textfield the item will expand its width as expected.

ios11_toolbar_textfield_only

EDIT 2:

Using constraints within the UIToolbar directly seems to be impossible as the item views are non existent (until they are placed?) or do not have a superview.

UIBarButtonItem *left = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:nil];
UIView *leftView = [left valueForKey:@"view"];
UIBarButtonItem *right = [[UIBarButtonItem alloc] initWithTitle:@"Cancel" style:UIBarButtonItemStylePlain target:nil action:nil];
UIView *rightView = [right valueForKey:@"view"];

UITextField *textField = [[UITextField alloc] init];
textField.borderStyle = UITextBorderStyleRoundedRect;
UIBarButtonItem *text = [[UIBarButtonItem alloc] initWithCustomView:textField];

NSDictionary *views = NSDictionaryOfVariableBindings(leftView, textField, rightView);
NSString *formatString = @"|-[leftView]-[textField]-[rightView]-|";

NSArray *constraint = [NSLayoutConstraint constraintsWithVisualFormat:formatString options:NSLayoutFormatAlignAllCenterX metrics:nil views:views];
[NSLayoutConstraint activateConstraints:constraint];

[self setToolbarItems:@[left, text, right]];

EDIT 3:

With the idea on checking the view hierarchy for UIToolbar i ended up with the following piece of code to add constraints ...

// _UIToolbarContentView
//   + _UIButtonBarStackView
//       + _UIButtonBarButton
//       + UIView
//       + _UITAMICAdaptorView
//       + UIView
//

UIView *contentView = nil;
for (UIView *view in self.navigationController.toolbar.subviews) {
    if ([view.description containsString:@"ContentView"]) {
        contentView = view;
    }
}

UIView *stackView = nil;
for (UIView *view in contentView.subviews) {
    if ([view.description containsString:@"ButtonBarStackView"]) {
        stackView = view;
    }
}

// constrain views on stack
UIView *uiButtonBarButton = [stackView.subviews objectAtIndex:0];
[uiButtonBarButton.leadingAnchor constraintEqualToAnchor:stackView.leadingAnchor].active = YES;

UIView *uiTAMICAdaptorView = [stackView.subviews objectAtIndex:2];
[uiTAMICAdaptorView.leadingAnchor constraintEqualToAnchor:uiButtonBarButton.trailingAnchor].active = YES;
[uiTAMICAdaptorView.trailingAnchor constraintEqualToAnchor:stackView.trailingAnchor].active = YES;

... which will change the view like this:

enter image description here

Why is the UIBarButton getting resized and not the UITextField aka UITAMICAdaptorView?

Fixing the size for UIBarButton with a constraint ...

[uiButtonBarButton.widthAnchor constraintEqualToConstant:CGRectGetWidth(uiButtonBarButton.frame)].active = YES;

... looks like ...

enter image description here

... but unfortunately the frame of UIButtonBarButton is not valid until the item is placed.

So how to constrain the UIButtonBarButton in its width without knowing it. Something like 'notGreaterThanNecessary'?

Seligmann answered 1/10, 2017 at 22:40 Comment(5)
I had a similar question and wrote something as solution. Have a look at: stackoverflow.com/questions/46581263Melany
@Melany This is not exactly what i am looking for but the idea on traversing the toolbars subviews is great. I edited my question with the new knowledge.Seligmann
Starting iOS 11, UIBarButtonItems added to a UIToolBar no longer have a superView so we can't set their constraints when the view is loadedDebacle
@mnabaa As i unterstand, before iOS 11 these Items don't have a superView but they are still views and we can set their size. Starting iOS 11 these items views are sized and placed by using auto-layout which is only possible on views. As pointed in the question each item is a view placed in a stackView. But until now i could not find a way to change these 'system constraints' for margins, width and high.Seligmann
@Seligmann have you fixed this issue? If yes, how did you do that?Stimulus
S
3

Put a single UIView directly into a single UIBarButtonItem, this will become the full width of the UIToolBar.

Now you can add whatever subviews you want using layout constraints as normal. A horizontal UIStackView will work very nicely here.

Sumerian answered 9/3, 2018 at 2:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.