ionic-team / ionic-team/capacitor-keyboard
[Bug]: iOS resize:native applies the WebView resize ~200ms AFTER the keyboard settles, and never animates it
- Dominant language
- TypeScript
- Stars
- 3
- Forks
- 21
- PR merge metrics
- No merged PRs in 30d
Description
## Bug Report
### Capacitor Version
```
💊 Capacitor Doctor 💊
Latest Dependencies:
@capacitor/cli: 8.5.0
@capacitor/core: 8.5.0
@capacitor/android: 8.5.0
@capacitor/ios: 8.5.0
Installed Dependencies:
@capacitor/android: not installed
@capacitor/cli: 8.4.0
@capacitor/core: 8.4.0
@capacitor/ios: 8.4.0
```
`@capacitor/keyboard@8.0.5`
### Platform(s)
iOS. Verified on iPhone 17 Pro Max, iOS 26.5 (physical device).
### Current Behavior
With `resize: KeyboardResize.Native`, the WebView resize is **scheduled as a delayed one-frame snap** rather than animated alongside the keyboard.
`Keyboard.m` → `onKeyboardWillShow:` ([L256](https://github.com/ionic-team/capacitor-keyboard/blob/main/ios/Sources/KeyboardPlugin/Keyboard.m)):
```objc
double duration = [[notification.userInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue] + 0.2;
[self setKeyboardHeight:(int)height delay:duration];
```
`UIKeyboardAnimationDurationUserInfoKey` is read, has `0.2` added to it, and is then used as a **delay** — not as an animation duration. `setKeyboardHeight:delay:` (L306) schedules:
```objc
[weakSelf performSelector:@selector(_updateFrame) withObject:nil afterDelay:delay inModes:@[NSRunLoopCommonModes]];
```
and `_updateFrame` (L356) applies the frame bare:
```objc
case ResizeNative:
[self.webView setFrame:CGRectMake(wf.origin.x, wf.origin.y, f.size.width - wf.origin.x, f.size.height - wf.origin.y - self.paddingBottom)];
break;
```
Grepping the file for `UIView animate` / `animateWithDuration` / `CATransaction` returns nothing — no animation API is used anywhere in the plugin.
Resulting timeline on keyboard open:
| t | |
|---|---|
| 0 → ~0.25s | keyboard slides up over a still-full-size WebView |
| ~0.25s | keyboard has settled |
| **~0.45s** | WebView snaps to its smaller size in one frame; web content reflows |
The visible effect is that the page **jumps ~200ms after the keyboard has already stopped moving**, while everything else on screen is static. `UIKeyboardAnimationCurveUserInfoKey` is never read at all.
Related but distinct: #48 (webview not resized when the keyboard is *resized*).
### Expected Behavior
The WebView resize should be applied inside the keyboard's own animation, using the notification's duration and curve, so the two travel together:
```objc
double duration = [[notification.userInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
UIViewAnimationCurve curve = [[notification.userInfo valueForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue];
[UIView animateWithDuration:duration
delay:0
options:(UIViewAnimationOptions)(curve << 16)
animations:^{ [self _updateFrame]; }
completion:nil];
```
### Code Reproduction
No minimal repro app attached — the behaviour is directly readable from the source above, and reproduces on any `resize: native` app with a full-screen form. Happy to build one if that's needed to progress this.
To observe it: focus an input on a page with visible content in the bottom third, and watch the content *after* the keyboard finishes animating.
### Other Technical Details
**Visibility depends on `autoBackdropColor`.** During the snap, the resized WebView's backing is briefly unpainted and the key window shows through. With the default `autoBackdropColor: "off"` and no window background set, that region is the `UIView` default — **true black** — so the snap reads as a black flash. Setting `autoBackdropColor: "dom"` paints `currentKeyWindow.backgroundColor` from the DOM body on every show, which makes the snap invisible even though it still happens. That masking is why this may be under-reported.
**I'm aware the `+ 0.2` is probably deliberate and I don't know what it's load-bearing for.** The same file carries `shouldIgnoreResizeForHeight` (iPad/QuickType), `stageManagerOffset`, and a `hideTimer` debounce on the dismiss path — all suggesting timing has been fought here before. The delay may exist to let the keyboard frame settle before measuring. So I'm filing this as an issue rather than a PR: I can't validate an animated version against iPad, Stage Manager, or keyboard-type switches, and a naive change there looks likely to regress cases you've already fixed.
### Additional Context
We hit this in a Capacitor app and tried to work around it downstream by setting `resize: "none"` and re-implementing the resize in `CAPBridgeViewController` inside `UIView.animate` with the notification's duration and curve. That **did** remove the jump on open, but broke dismissal three separate ways — the WebView stopped restoring to full height, leaving the page rendered at keyboard-up size. The plugin's own implementation correctly handles open, dismiss *and* keyboard-type switch; ours only ever handled open. Which is the argument for fixing it here rather than in userland.
We've since reverted to `resize: native` and mask the symptom with `autoBackdropColor: "dom"`.
Contributor guide
Research direction
Start in ios/Sources/KeyboardPlugin/Keyboard.m at onKeyboardWillShow:, setKeyboardHeight:delay:, and _updateFrame, then read the existing dismissal and keyboard-type-switch paths. Reproduce with resize: native on an iOS device; done means the WebView follows the keyboard animation without the delayed snap while open, dismiss, and keyboard-type changes still restore the correct frame.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ios, objective-c
- Domain
- mobile-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 58/100