19

When I change the height of inputAccessoryView in iOS 8, the inputAccessoryView not go to the right origin, but covers the keyboard.

enter image description here

Here are some code snippets:

in table view controller

- (UIView *)inputAccessoryView {
    if (!_commentInputView) {
        _commentInputView = [[CommentInputView alloc] initWithFrame:CGRectMake(0, 0, [self width], 41)];
        [_commentInputView setPlaceholder:NSLocalizedString(@"Comment", nil) andButtonTitle:NSLocalizedString(@"Send", nil)];
        [_commentInputView setBackgroundColor:[UIColor whiteColor]];
        _commentInputView.hidden = YES;
        _commentInputView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleBottomMargin;
    }

    return _commentInputView;
}

in CommentInputView

#when the textview change height
- (void)growingTextView:(HPGrowingTextView *)growingTextView willChangeHeight:(float)height {
    if (height > _textView_height) {
        [self setHeight:(CGRectGetHeight(self.frame) + height - _textView_height)];
        [self reloadInputViews];
    }
}

in UIView Category from ios-helpers

- (void)setHeight: (CGFloat)heigth {
    CGRect frame = self.frame;
    frame.size.height = heigth;
    self.frame = frame;
}
Brian
  • 14,610
  • 7
  • 35
  • 43
Yijun
  • 433
  • 1
  • 3
  • 9

6 Answers6

19

Finally, i found the answer. In ios8, apple add a NSContentSizeLayoutConstraints to inputAccessoryView and set a constant with 44. You can't remove this constaint, because ios8 use it to calculate the height of inputAccessoryView. So, the only solution is to change value of this constant.

Example

in ViewDidAppear

- (void)viewDidAppear:(BOOL)animated {
    if ([self.inputAccessoryView constraints].count > 0) {
        NSLayoutConstraint *constraint = [[self.inputAccessoryView constraints] objectAtIndex:0];
        constraint.constant = CommentInputViewBeginHeight;
    }
}

change inputAccessoryView height when the textview height changed

- (void)growingTextView:(HPGrowingTextView *)growingTextView willChangeHeight:(float)height {

    NSLayoutConstraint *constraint = [[self constraints] objectAtIndex:0];
    float new_height = height + _textView_vertical_gap*2;

    [UIView animateWithDuration:0.2 animations:^{
        constraint.constant = new_height;
    } completion:^(BOOL finished) {
        [self setHeight:new_height];
        [self reloadInputViews];
    }];
}

That is.

Yijun
  • 433
  • 1
  • 3
  • 9
  • Can you confirm where `- (void)growingTextView:(HPGrowingTextView *)growingTextView willChangeHeight:(float)height` is in your code? You're calling `[self constraints]` and `[self reloadInputViews]` which means `self` should be the first responder and the `inputAccessoryView`? however the textview must be first responder if it's editing? – Magoo May 18 '15 at 08:38
  • @Magoo yes, self is the first responder, the inputAccessoryView. I use a textView which can auto increase height, called HPGrowingTextView. the - (void)growingTextView:(HPGrowingTextView *)growingTextView willChangeHeight:(float)height method will be called when the textView change it height. – Yijun May 20 '15 at 09:45
  • I know it's a bit of a stretch, but would you be able to put up the classes / source somewhere? I'm not sure how the inputAccessoryView can be the first responder when you have a textView that's currently editing, and therefore how `[self reloadInputViews];` isn't ignored? – Magoo May 20 '15 at 12:07
  • what is CommentInputViewBeginHeight? – Ganesh G Oct 06 '18 at 15:10
9

One way you can update the constraint mentioned in Yijun's answer when changing the height of the inputAccessoryView is by overwriting setFrame: on your inputAccessoryView. This doesn't rely on the height constraint being the first in the array.

- (void)setFrame:(CGRect)frame {
    [super setFrame:frame];

    for (NSLayoutConstraint *constraint in self.constraints) {
        if (constraint.firstAttribute == NSLayoutAttributeHeight) {
            constraint.constant = frame.size.height;
            break;
        }
    }
}
stu
  • 461
  • 4
  • 6
3

The first answer didn't totally solve my problem but gave me a huge hint. Apple did add a private constraint to the accessory view, but you cannot find it in the constraint list of the accessory view. You have to search for it from its superview. It killed my a few hours.

Wizard
  • 389
  • 1
  • 2
  • 12
  • http://stackoverflow.com/questions/30401879/resize-inputaccessoryview-dynamically-in-ios-8/30454462#30454462 – Bannings Jun 18 '15 at 01:48
  • @Bannings does not work for me by overriding addConstraint() since the constraint is added to the superview. – Wizard Jun 18 '15 at 21:42
  • 1
    Same. Here's my implementation for getting around this: `- (NSLayoutConstraint *)hiddenInputAccessoryViewHeightConstraint { for (NSLayoutConstraint *constraint in self.myInputAccessoryView.superview.constraints) if (constraint.constant == self.myInputAccessoryView.frame.size.height && constraint.firstAttribute == NSLayoutAttributeHeight && constraint != self.myInputAccessoryViewHeightConstraint) return constraint; return nil; }` – Nathan Hosselton Mar 04 '16 at 18:26
2

After reading the answer above, which is a great find, I was concerned that relying on the constraint you need to change being [0] or firstObject is an implementation detail that's likely to change under us in the future.

After doing a bit of debugging, I found that the Apple-added constraints on the accessory input view seem to have a priority of 76. This is a crazy low value and not one of the listed enums in the documentation for priority.

Given this low priority value it seems like a cleaner solution to simply conditionally add/remove another constraint with a high priority level, say UILayoutPriorityDefaultHigh when you want to resize the view?

Cocoadelica
  • 3,006
  • 27
  • 30
1

For Xcode 11.2 and swift 5 this function will update inputAccessoryView constraints even in animation block

func updateInputContainerConstraints() {
    if let accessoryView = inputAccessoryView,
        let constraint = accessoryView.superview?.constraints.first(where: { $0.identifier == "accessoryHeight" }) {
        constraint.isActive = false
        accessoryView.layoutIfNeeded()
        constraint.constant = accessoryView.bounds.height
        constraint.isActive = true
        accessoryView.superview?.addConstraint(constraint)
        accessoryView.superview?.superview?.layoutIfNeeded()
    }
}
Roman Filippov
  • 2,289
  • 2
  • 11
  • 17
0

Try this: _vwForSendChat is the input accessory view _txtViewChatMessage is the textview inside input accessory view

-(void)textViewDidChange:(UITextView *)textView {
CGFloat fixedWidth = textView.frame.size.width;
CGSize newSize = [textView sizeThatFits:CGSizeMake(fixedWidth, MAXFLOAT)];
CGRect newFrame = textView.frame;
newFrame.size = CGSizeMake(fmaxf(newSize.width, fixedWidth), newSize.height);
if (newFrame.size.height < 40) {
    _vwForSendChat.frame = CGRectMake(0, 0, self.view.frame.size.width, 40);
} else {
    if (newFrame.size.height > 200) {
        _vwForSendChat.frame = CGRectMake(0, 0, self.view.frame.size.width, 200);
    } else {
       _vwForSendChat.frame = CGRectMake(0, 0, self.view.frame.size.width, newFrame.size.height);
    }
}
[self.txtViewChatMessage reloadInputViews];
}
Harshit
  • 59
  • 1
  • 6