3

I have a custom keyboard for a UITextField using the inputView. I can switch over to the native keyboard again, at which point the height of the keyboard can change (e.g. swiping up/down on autosuggest, changing keyboards). This is why my custom keyboard inputView needs to be able to change its height.

However, when the frame height of the inputView is change, it magically resets itself again!

(I'm doing this on iOS 8.4 simulator.)

Simplifying the problem to the most basic example:

func createInputView(#height:CGFloat) -> UIView {
    let view = ViewSubclass(frame: CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), height))
    view.backgroundColor = UIColor.redColor()
    let label = UILabel(frame: CGRectMake(0, 0, 100, 30))
    label.backgroundColor = UIColor.yellowColor()
    label.text = "Hello!"
    view.addSubview(label)
    return view
}

// halves the view's frame height
func changeFrameHeightForView(view: UIView) {
    view.frame = {
        var f = view.frame
        f.size.height = f.size.height / 2.0
        return f
    }()
}

override func viewDidLoad() {
    super.viewDidLoad()
    view1 = createInputView(height: 200)
    view2 = createInputView(height: 150)
    myTextField.inputView = view1
}

// to reproduce this scenario, you would first focus on the text field to open the keyboard, and then tap on the controller's view, which triggers the below action:

// example 1
@IBAction func viewTapped(sender: AnyObject) {
    let currentInputView = self.myTextField.inputView!
    println("starting height: \(currentInputView.frame.height)")
    // starting height: 200.0

    changeFrameHeightForView(currentInputView)
    println("height after frame changed: \(currentInputView.frame.height)")
    // height after frame changed: 100.0

    self.myTextField.reloadInputViews()
    println("height after reloadInputViews: \(currentInputView.frame.height)")
    // height after reloadInputViews: 100.0

    // if I stopped here, the view will update and be shorter, but it shows a grey area underneath - the keyboard itself hasn't changed height! So try to dismiss the keyboard and reload it again...

    myTextField.resignFirstResponder()
    println("height after resign first responder: \(currentInputView.frame.height)")
    // height after resign first responder: 200.0   // --> HOW DID IT GET REVERTED???

    myTextField.becomeFirstResponder()
    println("height after become first responder: \(currentInputView.frame.height)")
    // height after become first responder: 200.0

}

So the question for example 1 is: Why did the frames get reverted as soon as resignFirstResponder gets called?

I start thinking, maybe the text field is doing something to the inputView. So what if I nil it out. Then it shouldn't be able to affect the original view right? This is where it gets weird...

// example 2
@IBAction func viewTapped(sender: AnyObject) {
    let currentInputView = self.myTextField.inputView!
    println("starting height: \(currentInputView.frame.height)")
    // starting height: 200.0

    // remove the inputView and close the keyboard.
    self.myTextField.inputView = nil
    self.myTextField.reloadInputViews()
    myTextField.resignFirstResponder()

    // change the frame height
    changeFrameHeightForView(currentInputView)
    println("height after frame changed: \(currentInputView.frame.height)")
    // height after frame changed: 100.0

    // set it as the inputView again
    self.myTextField.inputView = currentInputView
    self.myTextField.reloadInputViews()
    println("height after reloadInputViews: \(currentInputView.frame.height)")
    // height after reloadInputViews: 100.0

    // open the keyboard
    myTextField.becomeFirstResponder()
    println("height after become first responder: \(currentInputView.frame.height)")
    // height after become first responder: 200.0    // --> HOW DID IT GET REVERTED??? Shouldn't the keyboard use the new frame height?

}

Moving onto example 3... Those with a keen eye will notice i have 2 input views set up. Here I will toggle the inputView between them. Interestingly, if a different view object gets assigned, it all works as expected:

// example 3
@IBAction func viewTapped(sender: AnyObject) {
    let currentInputView = self.myTextField.inputView!
    let otherView = (currentInputView == self.view1 ? self.view2 : self.view1)
    self.myTextField.inputView = otherView
    self.myTextField.reloadInputViews()
}

Apologies for the long question, but I've documented my findings in this investigation. I don't really want to create a brand new view every time the keyboard changes height.

Facebook messenger's custom keyboard is able to do something similar to this, so I'm hoping someone can shed some light on this.

thanks!

Lorenzo
  • 3,293
  • 4
  • 29
  • 56
Ken Ko
  • 1,517
  • 2
  • 15
  • 21

0 Answers0