Changing the frame of an inputAccessoryView in iOS 8

痞子三分冷 提交于 2019-11-28 17:03:35
JohnnyC

I've been banging my head against the wall on this one for quite some time, as the behavior changed from iOS 7 to iOS 8. I tried everything, until the most obvious solution of all worked for me:

inputAccessoryView.autoresizingMask = UIViewAutoresizingFlexibleHeight;

duh!

To sum up JohnnyC's answer: set your inpitAccessoryView's autoresizingMask to .flexibleHeight, calculate its intrinsicContentSize and let the framework do the rest.

Full code, updated for Swift 3:

class InputAccessoryView: UIView, UITextViewDelegate {

    let textView = UITextView()

    override var intrinsicContentSize: CGSize {
        // Calculate intrinsicContentSize that will fit all the text
        let textSize = textView.sizeThatFits(CGSize(width: textView.bounds.width, height: CGFloat.greatestFiniteMagnitude))
        return CGSize(width: bounds.width, height: textSize.height)
    }

    override init(frame: CGRect) {
        super.init(frame: frame)

        // This is required to make the view grow vertically
        autoresizingMask = .flexibleHeight

        // Setup textView as needed
        addSubview(textView)
        textView.translatesAutoresizingMaskIntoConstraints = false
        addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[textView]|", options: [], metrics: nil, views: ["textView": textView]))
        addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[textView]|", options: [], metrics: nil, views: ["textView": textView]))

        textView.delegate = self

        // Disabling textView scrolling prevents some undesired effects,
        // like incorrect contentOffset when adding new line,
        // and makes the textView behave similar to Apple's Messages app
        textView.isScrollEnabled = false
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // MARK: UITextViewDelegate

    func textViewDidChange(_ textView: UITextView) {
        // Re-calculate intrinsicContentSize when text changes
        invalidateIntrinsicContentSize()
    }

}

The issue is that in iOS 8, an NSLayoutConstraint that sets the inputAccessoryView's height equal to its initial frame height is installed automatically. In order to fix the layout problem, you need to update that constraint to the desired height and then instruct your inputAccessoryView to lay itself out.

- (void)changeInputAccessoryView:(UIView *)inputAccessoryView toHeight:(CGFloat)height {
    for (NSLayoutConstraint *constraint in [inputAccessoryView constraints]) {
        if (constraint.firstAttribute == NSLayoutAttributeHeight) {
            constraint.constant = height;
            [inputAccessoryView layoutIfNeeded];
            break;
        }
    }
}
Andrii Chernenko

Here's a complete, self-contained solution (thanks @JohnnyC and @JoãoNunes for pointing me in the right direction, @stigi for explaining how to animate intrinsicContent changes):

class InputAccessoryView: UIView {

    // InputAccessoryView is instantiated from nib, but it's not a requirement
    override func awakeFromNib() {
        super.awakeFromNib()        
        autoresizingMask = .FlexibleHeight
    }

    override func intrinsicContentSize() -> CGSize {
        let exactHeight = // calculate exact height of your view here
        return CGSize(width: UIViewNoIntrinsicMetric, height: exactHeight)
    }

    func somethingDidHappen() {
        // invalidate intrinsic content size, animate superview layout        
        UIView.animateWithDuration(0.2) {
            self.invalidateIntrinsicContentSize()
            self.superview?.setNeedsLayout()
            self.superview?.layoutIfNeeded()
        }
    }
}

100% working and very simple solution is to enumerate all constraints and set new height value. Here is some C# code (xamarin):

foreach (var constraint in inputAccessoryView.Constraints)
{
    if (constraint.FirstAttribute == NSLayoutAttribute.Height)
    {
        constraint.Constant = newHeight;
    }
}

Unfortunately, iOS8 adds a private height constraint to the inputAccessoryView, and this constraint is not public.

I recommend recreating the accessory view when its frame should change, and call reloadInputViews so that the new one is installed.

This is what I do, and it works as expected.

Yep, iOS8 adds a private height constraint to the inputAccessoryView.

Taking into account that recreating whole inputAccessoryView and replace old one is can be really expensive operation, you can just remove constraints before reload input views

[inputAccessoryView removeConstraints:[inputAccessoryView constraints]];
[textView reloadInputViews];

Just another workaround

To fix this I used inputAccessoryView.autoresizingMask = UIViewAutoresizingFlexibleHeight;

But of course this caused my textview to collapse. So adding a constraint to the toolbar and updating it when I have to, or adding the constraint to the textview itself and update it worked for me.

frist, get inputAccessoryView and set nil

UIView *inputAccessoryView = yourTextView.inputAccessoryView;
yourTextView.inputAccessoryView = nil;
[yourTextView reloadInputViews];

then set frame and layout

inputAccessoryView.frame = XXX
[inputAccessoryView setNeedsLayout];
[inputAccessoryView layoutIfNeeded];

last set new inputAccessoryView again and reload

yourTextView.inputAccessoryView = inputAccessoryView;
[yourTextView reloadInputViews];
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!