iOS issue when restorting app from background (screen stays white) #7097
Replies: 23 comments 35 replies
-
We are experiencing exactly the same problem (we also migrated from cordova to capacitor). Unfortunately, I haven't found the root cause, yet. |
Beta Was this translation helpful? Give feedback.
-
Same problem here, latest capacitor behaves the same on iOS |
Beta Was this translation helpful? Give feedback.
-
Same problem on iOS with Capacitor 5.0.0 Issues reported on iOS 16 and iOS 17. Difficult to reproduce (after several hours and with dozens of other apps open to make pression on memory), XCode logs :
|
Beta Was this translation helpful? Give feedback.
-
I've been having this same issue on a Ionic Cordova project that hasn't been migrated to capacitor yet, so the issue doesn't seem to be capacitor specific. |
Beta Was this translation helpful? Give feedback.
-
If an application is rewriting the url (e.g. using If manually reloading the page ( Hope this helps. |
Beta Was this translation helpful? Give feedback.
-
Same problem here, reported by user but I couldn't reproduce it. Hope someone can look into a standard solution soon. |
Beta Was this translation helpful? Give feedback.
-
Same problem here. Do you have any idea how can I fix it? Many of my users reported this error. |
Beta Was this translation helpful? Give feedback.
-
Could this be related to the app trying to reload the current url but it fails because your SPA is set up to handle routing? |
Beta Was this translation helpful? Give feedback.
-
I am experiencing the same issue, using Ionic Angular. I can recreate the issue consistently using the kill WebContent process trick, but only on specific URLs. It seems to be the URLs that use Angular's positional arguments (
The error on resume is then:
If the URL is |
Beta Was this translation helpful? Give feedback.
-
For anyone using capacitorjs with sveltekit: I was facing this exact same issue. The root page would load fine on location.reload(), but any other page would result in a blank page. In a practical scenario this would lead to a non-responsive app on iOS after a period of inactivity. After some debugging I fixed it by a combination of two things:
The first one is because when the webview is reloaded, it somehow tries to load resources from the path that you're currently on, instead of the root path as indicated by the relative imports. The second one is because even though the loads will work fine, there is still some mismatch between the loaded files and the page that should be loaded on the path you're on. A workaround is to just redirect to the root '/' if the root component is loaded, but the path is not the root path, and things will work fine from there. This all seems to be a bit of a hack, so if anyone has a better solution please let me know :) |
Beta Was this translation helpful? Give feedback.
-
I have a similar problem on Android, stays white for a few seconds after screen unlock. |
Beta Was this translation helpful? Give feedback.
-
We are having the exact issue on IOS and it's breaks the user experience from time to time. A kind follow up from the dev on this would be greatly appreciated! |
Beta Was this translation helpful? Give feedback.
-
I'd like to also say that I'm experiencing the same issue. Hoping there is an official resolution to this soon! |
Beta Was this translation helpful? Give feedback.
-
One workaround is to force the app to restart from the "home" route when reloaded from any other route (as it happens after a crash) For example, with Vue router
and test if |
Beta Was this translation helpful? Give feedback.
-
Hi, |
Beta Was this translation helpful? Give feedback.
-
Hi, In case it helps anyone, I will share my solution. In my ....
import { App } from '@capacitor/app';
....
App.addListener('resume', async () => {
// refresh firebase token
// this is needed because the firebase auth token expires after 1 hour
// and if the app is in the background for more than 1 hour,
// when the app is resumed, the user got a white screen because the token was expired
const newToken = await this.authService.getIdToken({ forceRefresh: true });
await this.storage.set(LOCAL_STORAGE_KEYS.user_token, newToken);
}); Then in my authService public async getIdToken(options?: GetIdTokenOptions): Promise<string> {
return FirebaseAuthentication.getIdToken(options)
.then((result) => result.token)
.catch(() => '');
} |
Beta Was this translation helpful? Give feedback.
-
Getting the same issue. Screen gets white on ios when app is put in background for a long time. But I am getting this only for the ios. Not sure how to resolve this one. |
Beta Was this translation helpful? Give feedback.
-
I am also getting the Same issue, if i am opening the app twice i got the white screen at least 1 time, while app is in background. |
Beta Was this translation helpful? Give feedback.
-
I'm facing the same white screen issue. The app shows the white screen when it goes in the background for sometime. I forcibly have to kill the app for it to function normally again. |
Beta Was this translation helpful? Give feedback.
-
I'm also facing the same white screen issue. The app shows the white screen when it goes in the background for sometime. Tried above suggested solution but not working. |
Beta Was this translation helpful? Give feedback.
-
for anyone facing this problem, this is a hack to make the content reappear (put in MainActivity):
|
Beta Was this translation helpful? Give feedback.
-
I managed to restart my app on ios by editing the AppDelegate.swift file like this:
This will force restart the app when we back from minimized/background state for more than 15 minutes. Hope it helps. |
Beta Was this translation helpful? Give feedback.
-
We found the root cause is that:
This leads to a blank screen #2379. Some suggest changing the open func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
webView.reload()
exit(0)
} However, Inspired by @mazemaker's solution, we improved it with two key changes: 1. Use UserDefaults to store background timestampInstead of using an in-memory variable (which can be lost when WKWebView is killed), we persist the background entry time using UserDefaults. This ensures we can still access it even after a memory-related termination. 2. Add low-memory detectionIf a memory warning occurs while the app is backgrounded, we force the timestamp to appear older, ensuring a restart will trigger on resume. Code Changes (AppDelegate.swift only): import UIKit
import Capacitor
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let maxBackgroundTime: TimeInterval = 300 // 5 minutes in seconds
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
//...
// Listen to Memory Constraints to restart the app
// Observe for low-memory warnings.
NotificationCenter.default.addObserver(self,
selector: #selector(handleMemoryWarning),
name: UIApplication.didReceiveMemoryWarningNotification,
object: nil)
return true
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
// Allow Remote Notifications to be handled by the BackgroundRunnerPlugin
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
// Record the Background Enter Date
UserDefaults.standard.set(Date(), forKey: "backgroundEnterDate")
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
if let backgroundDate = UserDefaults.standard.object(forKey: "backgroundEnterDate") as? Date {
let elapsed = Date().timeIntervalSince(backgroundDate)
if elapsed > maxBackgroundTime {
// More than N minutes have passed.
// Reconstruct your WKWebView (or trigger an app state reset) as needed.
restartApplication()
}
}
// Clear the background enter date
UserDefaults.standard.removeObject(forKey: "backgroundEnterDate")
UserDefaults.standard.synchronize() // Optional, to flush the data right away
}
func applicationDidBecomeActive(_ e: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
// Called when the app was launched with a url. Feel free to add additional processing here,
// but if you want the App API to support tracking app url opens, make sure to keep this call
return ApplicationDelegateProxy.shared.application(app, open: url, options: options)
}
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
// Called when the app was launched with an activity, including Universal Links.
// Feel free to add additional processing here, but if you want the App API to support
// tracking app url opens, make sure to keep this call
return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler)
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
NotificationCenter.default.post(name: .capacitorDidRegisterForRemoteNotifications, object: deviceToken)
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
NotificationCenter.default.post(name: .capacitorDidFailToRegisterForRemoteNotifications, object: error)
}
func restartApplication() {
// Create a simple view controller as a placeholder during transition
let viewController = UIViewController()
let navCtrl = UINavigationController(rootViewController: viewController)
guard
let window = self.window,
let rootViewController = window.rootViewController
else {
return
}
navCtrl.view.frame = rootViewController.view.frame
navCtrl.view.layoutIfNeeded()
UIView.transition(with: window, duration: 0.3, options: .transitionCrossDissolve, animations: {
window.rootViewController = navCtrl
}) { _ in
// After transition, reload the app by recreating the main view controller
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
if let initialViewController = mainStoryboard.instantiateInitialViewController() {
window.rootViewController = initialViewController
// Post a notification that can be caught in the web view to reload the app state
NotificationCenter.default.post(name: Notification.Name("AppRestartRequired"), object: nil)
}
}
}
// Called when the app receives a low memory warning.
@objc func handleMemoryWarning() {
// Only kill the app if it's in the background.
if UIApplication.shared.applicationState == .background {
// Set an old date to guarantee a restart next time
let staleDate = Date(timeIntervalSinceNow: -(maxBackgroundTime * 2))
UserDefaults.standard.set(staleDate, forKey: "backgroundEnterDate")
UserDefaults.standard.synchronize() // Optional, to flush the data right away
print("🔴 Memory warning in background: setting stale timestamp \(staleDate)")
}
}
deinit {
NotificationCenter.default.removeObserver(self)
}
} |
Beta Was this translation helpful? Give feedback.
-
Hi everyone,
we originally managed our app with cordova. A few months ago we migrated to capacitor. However, we're having a really frustrating problem under iOS:
When you put the app in the background on iOS and then bring it back to the foreground after a while, the screen stays completely white and the app is no longer visible. The only way to fix it is to quit and restart the app. This makes the app very difficult to use for our iOS users. I think the problem is that the app isn't restoring properly from the frozen state.
Has anyone else had a similar problem? Can you give us any helpful tips on how to fix it? We'd really appreciate it if we could get this resolved as soon as possible. It's a very urgent issue.
Thanks for any help!
Beta Was this translation helpful? Give feedback.
All reactions