I’m using the standard UIResponder keyboard notifications to observe when the keyboard appears and adjust the content inset of a scroll view so that its content is visible when scrolled all the way down. This works fine, same as it always has. However, this app is a Message app extension, so the window of my view controller is not the same size as the main UIScreen. The keyboard notification coordinates are in terms of the main screen, but the frames of all of my views are in terms of the window. The window is pinned to the bottom of the display, but its frame starts at 0, 0. Is there a way for me to programatically determine the position of this UIWindow in the UIScreen frame? Here’s the code I’m using:
@objc func adjustKeyboard(for notification: Notification) {
guard let scrollView = scrollView,
let superview = scrollView.superview,
let window = scrollView.window,
let userInfo = notification.userInfo,
let frameValue = userInfo[UIResponder.keyboardFrameEndUserInfoKey]
as? NSValue
else { return }
let frame = frameValue.cgRectValue
let coordinateSpace = UIScreen.main.fixedCoordinateSpace
let frameInSuperview = superview.convert(frame, from: coordinateSpace)
// The window is at the bottom of the screen but its frame needs to be
// adjusted for that difference
let offset = scrollView.frame.maxY - frameInSuperview.minY
+ (coordinateSpace.bounds.height - window.frame.height)
animate(alongsideKeyboardNotification: notification, animations: {
print("Intersection height: \(offset)")
scrollView.contentInset.bottom = offset
scrollView.verticalScrollIndicatorInsets.bottom = offset
})
}
func animate(alongsideKeyboardNotification notification: Notification,
animations: @escaping () -> Void,
completion: ((Bool) -> Void)? = nil) {
let durationKey = UIResponder.keyboardAnimationDurationUserInfoKey
let curveKey = UIResponder.keyboardAnimationCurveUserInfoKey
guard let userInfo = notification.userInfo,
let duration = (userInfo[durationKey] as? NSNumber)?.doubleValue,
let curveValue = (userInfo[curveKey] as? NSNumber)?.intValue,
let curve = UIView.AnimationCurve(rawValue: curveValue) else {
animations()
return
}
UIView.animate(withDuration: duration,
delay: 0,
options: curve.asOptions,
animations: animations,
completion: completion)
}
extension UIView.AnimationCurve {
var asOptions: UIView.AnimationOptions {
switch self {
case .easeInOut:
return .curveEaseInOut
case .easeIn:
return .curveEaseIn
case .easeOut:
return .curveEaseOut
case .linear:
return .curveLinear
@unknown default:
return []
}
}
}
Ideally, I would not need line 19—which won’t work if, for instance, the window were at the top of the screen. I’d rather not hard-code it for that reason. Any ideas?