2

I have this code below which runs when the keyboardWillShowNotification is called:

func keyboardWillShow(_ notification: Notification) {
    //ERROR IN THE LINE BELOW            
    keyboard = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as AnyObject).cgRectValue
    animaton = (notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as AnyObject).doubleValue

    UIView.animate(withDuration: 0.4, animations: { () -> Void in
       self.scrollView.frame.size.height = self.scrollViewHeight - self.keyboard.height
    }) 
}

I am getting an error on the second line saying: unexpectedly found nil while unwrapping an Optional value. Basically whenever I click on one of the textFields this notification for the keyboard will be called and the code in keyboardWillShow will run. I know I put if...let statements but I want to know why I am getting nil for this.

I am not sure how I am getting this error or how to debug it either. Is it because I am running it from the simulator?

Here is what printing the notification.userInfo gives:

Optional([AnyHashable("UIKeyboardFrameEndUserInfoKey"): NSRect: {{0, 315}, {320, 253}}, AnyHashable("UIKeyboardIsLocalUserInfoKey"): 1, AnyHashable("UIKeyboardBoundsUserInfoKey"): NSRect: {{0, 0}, {320, 253}}, AnyHashable("UIKeyboardAnimationCurveUserInfoKey"): 7, AnyHashable("UIKeyboardCenterBeginUserInfoKey"): NSPoint: {160, 694.5}, AnyHashable("UIKeyboardCenterEndUserInfoKey"): NSPoint: {160, 441.5}, AnyHashable("UIKeyboardFrameBeginUserInfoKey"): NSRect: {{0, 568}, {320, 253}}, AnyHashable("UIKeyboardAnimationDurationUserInfoKey"): 0.25])

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • Log `notification.userInfo`. See what keys actually exist. – rmaddy Sep 15 '16 at 20:31
  • Oh yeah let me try that. –  Sep 15 '16 at 20:32
  • I have added in my question what is printed out. It shows that the `UIKeyboardFrameEndUserInfoKey` is available. –  Sep 15 '16 at 20:34
  • When I comment out these two lines: `keyboard = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as AnyObject).cgRectValue` `animaton = (notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as AnyObject).doubleValue` The keyboard pops up correctly and doesn't give that nil error either. –  Sep 15 '16 at 20:37
  • 1
    Just cast it to NSValue instead of AnyObject – Leo Dabus Sep 15 '16 at 21:18
  • Sure let me try that. –  Sep 15 '16 at 21:21
  • When I replaced it by this line: `keyboard = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as NSValue).cgRectValue` it was giving me an error saying: `'Any?' is not convertible to 'NSValue'; did you mean to use 'as!' to force downcast` So I added the `!` after the `as` and it worked now! Can you explain the error I was getting before I added the `!` and why NSValue fixed the issue? –  Sep 15 '16 at 21:24
  • `let UIKeyboardFrameEndUserInfoKey: String` Description The key for an NSValue object containing a `CGRect` that identifies the end frame of the keyboard in screen coordinates – Leo Dabus Sep 15 '16 at 22:30
  • The second line you need to cast to `NSNumber` `let UIKeyboardAnimationDurationUserInfoKey: String` Description The key for an NSNumber object containing a double that identifies the duration of the animation in seconds. – Leo Dabus Sep 15 '16 at 22:31
  • Compare http://stackoverflow.com/questions/25451001/getting-keyboard-size-from-userinfo-in-swift which has answers updated for Swift 3. – Martin R Sep 16 '16 at 02:00

2 Answers2

2

From the docs:

let UIKeyboardFrameEndUserInfoKey: String 

Description

The key for an NSValue object containing a CGRect that identifies the end frame of the keyboard in screen coordinates

Your second key:

let UIKeyboardAnimationDurationUserInfoKey: String

Description The key for an NSNumber object containing a double that identifies the duration of the animation in seconds.

So you need to cast the first one to NSValue and the second one to NSNumber:

func keyboardWillShow(_ notification: Notification) {
    print("keyboardWillShow")
    guard let userInfo = notification.userInfo else { return }
    keyboard = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
    animaton = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
    // your code
}
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • Hello! What is the purpose of storing the value of the`UIKeyboardFrameEndUserInfoKey` key in a `NSValue`? Why not just store it in a `CGRect` itself? I read online that `NSValue` is a simple container for a single C or Objective-C data item. So is `CGRect` an Objective-C data item which is why we would wrap that in a `NSValue`? –  Sep 16 '16 at 06:44
1

(How to fix your issue is clearly written in Leo Dabus's answer, so I will try to explain the error I was getting before I added the ! .)

In Swift 3, as AnyObject has become one of the most risky operation. It's related to the worst new feature called as id-as-Any.

In this line of your code:

    keyboard = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as AnyObject).cgRectValue

The type of expression notification.userInfo?[UIKeyboardFrameEndUserInfoKey] is Any?. As you see, an Optional type Any? should not be safely converted to non-Optional AnyObject. But Swift 3 converts it with creating non-Optional _SwiftValue.

You can check this behaviour with inserting this code:

print(type(of: notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as AnyObject))

So, you are trying to apply non-optional-chaining .cgRectValue to _SwiftValue, which may be confusing the Swift 3's feature: "implicit type conversion _SwiftValue back to the Swift value".

Getting too long...


Do NOT use as AnyObject casting in Swift 3

OOPer
  • 47,149
  • 6
  • 107
  • 142
  • Hello, very sorry for the late response and thank you for the detailed answer! I have one question: you stated here: "As you see, an Optional type `Any?` should not be safely converted to non-Optional `AnyObject`. But Swift 3 converts it with creating non-Optional `_SwiftValue`" I don't understand this part: that swift 3 converts it to a non optional `_SwiftValue`. Do you mean that the value of `notification.userInfo?[UIKeyboardFrameEndUserInfoKey]` which is `Any?` gets converted to a non optional type `_SwiftValue`? Also what does `_SwiftValue` refer to here? –  Sep 16 '16 at 06:34
  • 1
    @1290, _Do you mean that the value of notification.userInfo?[UIKeyboardFrameEndUserInfoKey] which is `Any?` gets converted to a non optional type `_SwiftValue`?_ Absolutely yes. _what does `_SwiftValue` refer to here?_ It's a hidden type and not much revealed yet, - It's a reference type. - It can contain any value of Swift. - Swift runtime knows how to retrieve the original value contained in it and in may cases, automatically converts it to Swift types. – OOPer Sep 16 '16 at 10:06
  • Ohh I see now! Thank you so much for the clear explanation helped a lot. –  Sep 16 '16 at 17:18