[@capacitor/keyboard] iOS: WebView frame permanently stuck at keyboard height after backgrounding (resize: native)
まだ誰も着手していません。
評価
- 難易度
- 3/5
- 見積もり時間
- 1〜2日
- 初心者へのやさしさ
- 68/100
- issue の種類
- バグ
- 明瞭さ
- おおむね明確
- 活発さ
- 静か
- 技術スタック
- ios, objective-c
- 領域
- mobile-dev
調査の方向性
iOSキーボードプラグインの実装で、-load、setKeyboardHeight:delay:、_updateFrame から開始し、app-did-become-active パスがキャッシュされた paddingBottom とどのように相互作用するかを追跡します。resize: "native" で報告されたバックグラウンド/フォアグラウンドのサイクルを再現し、WebView が黒い帯なしで完全な高さに戻ることを確認します。また、currentKeyWindow が nil になるケースと、issue で指摘されている未初期化の frame も調べます。
索引モデルが issue の本文から書いたものです。
説明
Plugin(s)
@capacitor/[email protected] (iOS)
Description
With the default resize: "native" policy, the WKWebView frame can get permanently stuck at its keyboard-shrunk height after the app is backgrounded and returned to. The web content then occupies only the top of the screen, with a black band below it exactly one keyboard-height tall.
The band is pure black rather than ios.backgroundColor, because CAPBridgeViewController.loadView() does view = webView — shrinking the WebView shrinks the view controller's root view, exposing the bare UIWindow.
Measured on a 956pt-tall device: web content ends at exactly 611pt, black band is 345pt. That is precisely the ResizeNative arithmetic in _updateFrame:
webView.frame.height = window.height - origin.y - paddingBottom // 956 - 0 - 345 = 611
Root cause
setKeyboardHeight:delay: assigns paddingBottom synchronously, but defers the frame update via performSelector:afterDelay::
- (void)setKeyboardHeight:(int)height delay:(NSTimeInterval)delay
{
if (self.paddingBottom == height) {
return; // (2) guard makes the desync permanent
}
self.paddingBottom = height; // (1) state updated NOW
...
[weakSelf performSelector:@selector(_updateFrame) withObject:nil afterDelay:delay ...]; // frame updated LATER
}
Backgrounding with the keyboard up fires keyboardWillHide, which calls setKeyboardHeight:0 delay:0.01. If the app suspends inside that 10ms window, the run loop stops and _updateFrame is never delivered — leaving paddingBottom == 0 while the frame is still short by the keyboard height.
It is then unrecoverable, because:
_updateFrameis reachable from exactly one call site,setKeyboardHeight:setKeyboardHeight:early-returns wheneverpaddingBottomalready equals the requested height — which it now always does for0setResizeMode:only flips the enum; it never touches the frame
So every subsequent keyboardWillHide is a no-op and the WebView stays short for the remaining life of the process. The guard compares the intended height against itself rather than against the actual frame, so the model believes it is already reconciled.
Reproduction / evidence
Reproduced on an iPhone 17 Pro Max simulator (iOS 26, Xcode 26.6) by injecting the exact stuck state — frame short by 345pt while paddingBottom is 0 — and performing a real background/foreground cycle (verified same PID, i.e. a genuine resume rather than a cold start):
didBecomeActive paddingBottom=0 frame BEFORE={{0, 0}, {440, 611}}
The frame stays at 611 indefinitely; no keyboard event can repair it.
User-level workaround: focus any text input and dismiss the keyboard. That drives paddingBottom 0 → 345 → 0, passing the guard in both directions, and the frame is restored.
Expected behavior
The WebView frame should return to full height once the keyboard is gone, regardless of whether the app was suspended while the deferred update was pending.
Suggested fix
Re-assert the frame when the app returns to the foreground. _updateFrame is already idempotent — it recomputes purely from the current window bounds and paddingBottom — so it is a no-op when state is consistent and a repair when a deferred update was dropped:
// in -load
[nc addObserver:self selector:@selector(onAppDidBecomeActive:)
name:UIApplicationDidBecomeActiveNotification object:nil];
- (void)onAppDidBecomeActive:(NSNotification *)notification
{
[self _updateFrame]; // deliberately not via setKeyboardHeight:, whose guard is the problem
}
Verified: with this in place the same background/foreground cycle restores the frame from 611 to 956, and the black band goes from 345pt to 0pt.
Alternatively (or additionally), the guard in setKeyboardHeight: could compare against the real webView.frame.size.height instead of the cached paddingBottom, so state can never silently diverge from the view.
Unrelated minor bug spotted nearby
In _updateFrame, f is declared uninitialized:
CGRect f, wf = CGRectZero; // only `wf` is initialized
UIWindow *window = [self currentKeyWindow];
if (window) {
f = [window bounds];
}
...
[self.webView setFrame:CGRectMake(wf.origin.x, wf.origin.y, f.size.width - wf.origin.x, f.size.height - wf.origin.y - self.paddingBottom)];
If currentKeyWindow returns nil, f holds stack garbage which is then written straight into the WebView frame. Worth initializing to CGRectZero and bailing when there is no window to measure.
Platforms
iOS
- 主要言語
- Java
- スター
- 687
- フォーク
- 691
- PR マージ指標
- 30日以内にマージされた PR はありません
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートなし
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
ionic-team/capacitor-plugins のほかの issue
-
triage
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
ionic-team/capacitor-plugins#2616 ·
-
[@capacitor/app] iOS: 'Expression implicitly coerced from String? to Any' warning in getAppLanguageオープンtriage
難易度 1/5 1時間未満 初心者へのやさしさ 88/100
ionic-team/capacitor-plugins#2604 ·
-
bug(network): Android networkStatusChange re-queries the active network inside callbacks and fires on every capability change対応中かも @kcjonson が 3 日前に担当しました。 オープンplatform: android
難易度 4/5 1〜2日 初心者へのやさしさ 25/100
ionic-team/capacitor-plugins#2621 ·
-
bug(network): iOS getStatus() returns the last reachability callback value instead of reading current flags対応中かも @kcjonson が 3 日前に担当しました。 オープンplatform: ios
難易度 2/5 1〜3時間 初心者へのやさしさ 35/100
ionic-team/capacitor-plugins#2620 ·
-
platform: android platform: ios
難易度 5/5 1週間以上 初心者へのやさしさ 24/100
ionic-team/capacitor-plugins#2619 ·
ionic-team/capacitor-plugins の issue をすべて見る
似ている issue
-
pdfium-drift
難易度 1/5 1時間未満 初心者へのやさしさ 88/100
Stirling-Tools/JPDFium#188 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
openremote/openremote#3487 · コメント 1 件 ·
-
Type/Bug U2
難易度 2/5 1〜3時間 初心者へのやさしさ 90/100
wso2/product-is#28675 ·
メンテナーはふだん 1 日以内に返信
-
a:bug
難易度 2/5 1〜3時間 初心者へのやさしさ 83/100
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 87/100
apache/rocketmq-dashboard#7351 ·
メンテナーはふだん 3 日以内に返信