Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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'?

like image 241
grethi Avatar asked Oct 01 '17 22:10

grethi


1 Answers

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.

like image 92
trapper Avatar answered Oct 13 '22 00:10

trapper