ionic-team / ionic-team/capacitor-keyboard

[Bug]: iOS resize:native applies the WebView resize ~200ms AFTER the keyboard settles, and never animates it

Open
#79 1 comment 1 reaction 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.