3

I pasted the code sample from here: https://stackoverflow.com/a/46510833/784637 into a new Single View App to pin inputAccessoryView at the bottom of the screen:


class ViewController: UIViewController {

    override var canBecomeFirstResponder: Bool { return true }

    var _inputAccessoryView: UIView!

    override var inputAccessoryView: UIView? {

        if _inputAccessoryView == nil {

            _inputAccessoryView = CustomView()
            _inputAccessoryView.backgroundColor = UIColor.groupTableViewBackground

            let textField = UITextField()
            textField.borderStyle = .roundedRect

            _inputAccessoryView.addSubview(textField)

            _inputAccessoryView.autoresizingMask = .flexibleHeight

            textField.translatesAutoresizingMaskIntoConstraints = false

            textField.leadingAnchor.constraint(
                equalTo: _inputAccessoryView.leadingAnchor,
                constant: 8
            ).isActive = true

            textField.trailingAnchor.constraint(
                equalTo: _inputAccessoryView.trailingAnchor,
                constant: -8
            ).isActive = true

            textField.topAnchor.constraint(
                equalTo: _inputAccessoryView.topAnchor,
                constant: 8
            ).isActive = true

            // this is the important part :

            textField.bottomAnchor.constraint(
                equalTo: _inputAccessoryView.layoutMarginsGuide.bottomAnchor,
                constant: -8
            ).isActive = true
        }

        return _inputAccessoryView
    }

    override func loadView() {

        let tableView = UITableView()
        tableView.keyboardDismissMode = .interactive

        view = tableView
    }
}

class CustomView: UIView {

    // this is needed so that the inputAccesoryView is properly sized from the auto layout constraints
    // actual value is not important

    override var intrinsicContentSize: CGSize {
        return CGSize.zero
    }
}

When I run the simulator everything looks great, however the console reports this error when I tap on the UITextField:

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

I've tried overriding intrinsicContentSize to a non-zero value like CGSize(width: 50, height: 50) but am still getting this error. How can I resolve this?

user784637
  • 15,392
  • 32
  • 93
  • 156

1 Answers1

3

This is happening because you have all required constraints but are initially returning an intrinsic size of zero. So the layout system is attempting to lay your view out with a zero size, fails to satisfy your constraints so breaks the size of the text field. The easiest (maybe the best?) way to fix this is to reduce the priority of your constraints to something slightly less than required so the system can temporarily break them.

But before we look at code, how about an architectural suggestion: if you need CustomView, why not just create your text field in there, instead of your view controller being responsible for injecting the text field in what is essentially a dummy view.

class CustomView: UIView {

  private let textField = UITextField(frame: .zero)

  init(frame: CGRect) {
    backgroundColor = .groupTableViewBackground

    // do your textfield setup here
    addSubview(textField)
    textField.translatesAutoresizingMaskIntoConstraints = false
    textField.borderStyle = .roundedRect

    // set up your constraints
    let constraints: [NSLayoutConstraint] = [
      textField.topAnchor.constraint(equalTo: topAnchor, constant: 8),
      textField.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8),
      textField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8),
      textField.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8),
    ]

    // slightly reduce the priority of all of your constraints
    constraints.forEach { $0.priority = UILayoutPriority(rawValue: 999) }

    // activate them
    NSLayoutConstraint.activate(constraints)
  }
}

Now, in your view controller, you just have to declare your accessory view as:

override var inputAccessoryView = CustomView()

No need for private storage, implicitly unwrapped optionals, etc.

Procrastin8
  • 4,193
  • 12
  • 25
  • 2
    Thanks for the suggestion - I went ahead and reduced the priority of the constraints, but I'm still getting the same error. – user784637 Dec 27 '19 at 16:53