inputAccessoryView, API error? _UIKBCompatInputView? UIViewNoIntrinsicMetric, simple code, can't figure out

870 views Asked by At

Help me in one of the two ways maybe:

  1. How to solve the problem? or
  2. How to understand the error message?

Project summary

So I'm learning about inputAccessoryView by making a tiny project, which has only one UIButton. Tapping the button summons the keyboard with inputAccessoryView which contains 1 UITextField and 1 UIButton. The UITextField in the inputAccessoryView will be the final firstResponder that is responsible for the keyboard with that inputAccessoryView

screenshot

The error message

API error: <_UIKBCompatInputView: 0x7fcefb418290; frame = (0 0; 0 0); layer = <CALayer: 0x60000295a5e0>> returned 0 width, assuming UIViewNoIntrinsicMetric

The code

is very straightforward as below

  1. The custom UIView is used as inputAccessoryView. It installs 2 UI outlets, and tell responder chain that it canBecomeFirstResponder.
class CustomTextFieldView: UIView {

    let doneButton:UIButton = {
        let button = UIButton(type: .close)
        return button
    }()
    
    let textField:UITextField = {
        let textField = UITextField()
        textField.placeholder = "placeholder"
        return textField
    }()
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        initSetup()
    }
    
    override init(frame:CGRect) {
        super.init(frame: frame)
        initSetup()
    }
    
    convenience init() {
        self.init(frame: .zero)
    }
    
    func initSetup() {
        addSubview(doneButton)
        addSubview(textField)
    }
    
    func autosizing(to vc: UIViewController) {
        frame = CGRect(x: 0, y: 0, width: vc.view.frame.size.width, height: 40)
        let totalWidth = frame.size.width - 40
        doneButton.frame = CGRect(x: totalWidth * 4 / 5 + 20,
                                  y: 0,
                                  width: totalWidth / 5,
                                  height: frame.size.height)
        textField.frame = CGRect(x: 20,
                                 y: 0,
                                 width: totalWidth * 4 / 5,
                                 height: frame.size.height)
    }
    
    override var canBecomeFirstResponder: Bool { true }
    
    override var intrinsicContentSize: CGSize {
        CGSize(width: 400, height: 40)
    } // overriding this variable seems to have no effect.
}
  1. Main VC uses the custom UIView as inputAccessoryView. The UITextField in the inputAccessoryView becomes the real firstResponder in the end, I believe.
class ViewController: UIViewController {
    
    let customView = CustomTextFieldView()
    var keyboardShown = false
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        customView.autosizing(to: self)
        
    
    }
    
    @IBAction func summonKeyboard() {
        print("hello")
        keyboardShown = true
        self.becomeFirstResponder()
        customView.textField.becomeFirstResponder()
    }

    override var canBecomeFirstResponder: Bool { keyboardShown }
    
    override var inputAccessoryView: UIView? {
        return customView
    }
}
  1. I've seen people on the internet says this error message will go away if I run on a physical phone. I didn't go away when I tried.
  2. I override intrinsicContentSize of the custom view, but it has no effect.
  3. The error message shows twice together when I tap summon.
  4. What "frame" or "layer" does the error message refer to? Does it refer to the custom view's frame and layer?
1

There are 1 answers

0
DonMag On

If we use Debug View Hierarchy we can see that _UIKBCompatInputView is part of the (internal) view hierarchy of the keyboard.

It's not unusual to see constraint errors / warnings with internal views.

Since frame and/or intrinsic content size seem to have no effect, I don't think it can be avoided (nor does it seem to need to be).

As a side note, you can keep the "Done" button round by using auto-layout constraints. Here's an example:

class CustomTextFieldView: UIView {
    
    let textField: UITextField = {
        let tf = UITextField()
        tf.font = .systemFont(ofSize: 16)
        tf.autocorrectionType = .no
        tf.returnKeyType = .done
        tf.placeholder = "placeholder"
        // textField backgroundColor so we can see its frame
        tf.backgroundColor = .yellow
        return tf
    }()
    
    let doneButton:UIButton = {
        let button = UIButton(type: .close)
        return button
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    
    func commonInit() -> Void {
        
        autoresizingMask = [.flexibleHeight, .flexibleWidth]
        
        [doneButton, textField].forEach { v in
            v.translatesAutoresizingMaskIntoConstraints = false
            addSubview(v)
        }
        NSLayoutConstraint.activate([
            
            // constrain doneButton
            //  Trailing: 20-pts from trailing
            doneButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20.0),
            //  Top and Bottom 8-pts from top and bottom
            doneButton.topAnchor.constraint(equalTo: topAnchor, constant: 8.0),
            doneButton.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8.0),
            //  Width equal to default height
            //  this will keep the button round instead of oval
            doneButton.widthAnchor.constraint(equalTo: doneButton.heightAnchor),
            
            // constrain textField
            //  Leading: 20-pts from leading
            textField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20.0),
            //  Trailing: 8-pts from doneButton leading
            textField.trailingAnchor.constraint(equalTo: doneButton.leadingAnchor, constant: -8.0),
            //  vertically centered
            textField.centerYAnchor.constraint(equalTo: centerYAnchor),
            
        ])

    }

}

class CustomTextFieldViewController: UIViewController {
    
    let customView = CustomTextFieldView()
    
    var keyboardShown = false
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func summonKeyboard() {
        print("hello")
        keyboardShown = true
        self.becomeFirstResponder()
        customView.textField.becomeFirstResponder()
    }
    
    override var canBecomeFirstResponder: Bool { keyboardShown }
    
    override var inputAccessoryView: UIView? {
        return customView
    }
}