iOS app black screen when app coming from background - ios

I am trying to find out what is the cause of a black screen that a comparatively small set of users are facing. Most of them are coming from the background. The app uses ReSwift for data refreshing, although that does not trigger the re run of the didFinishLaunchingWithOptions. I also thought might be OOM (Out Of Memory), but if I provoque it and test it, the app just crashes. For what is worth, I have also observed that is happening to 6s and SE (2nd edition) users. What can I try next?
This is the code that runs on changing its status:
func applicationWillEnterForeground(_ application: UIApplication) {
let internetConnectionStatus = InternetConnectionStatus()
_ = internetConnectionStatus.isOk().subscribe(onNext: { connected in
if !connected {
self.showFloatr(withBody: "ctkit.error.no-internet")
}
})
}
func applicationDidBecomeActive(_ application: UIApplication) {
if let keyWindow = UIApplication.shared.keyWindow,
keyWindow.rootViewController is AuthPasswordRecoveryViewController ||
keyWindow.rootViewController is AuthNavigationViewController {
return
}
UIApplication.shared.applicationIconBadgeNumber = 0
UIApplication.shared.registerForRemoteNotifications()
}
The affected users are running iOS 15.

Related

Check when a system alert is shown in iOS

I have the following usecase.
I want to show an overlay over the current window when the application moves to the task manager.
Currently I am doing the following:
func applicationWillResignActive(_ application: UIApplication) {
guard let window = application.keyWindow else { return }
ApplicationPrivacyProtector.showOverlay(inKeyWindow: window)
}
func applicationDidBecomeActive(_ application: UIApplication) {
guard let window = application.keyWindow else { return }
ApplicationPrivacyProtector.hideOverlay(inKeyWindow: window)
}
This works fine but
applicationWillResignActive is also called when a system alert (eg. faceID, touchID, etc.) is shown which triggers this overlay view as well.
Is there a way to distinguish between system alert and application moves to taskmanager?

BGTaskScheduler fails with no background task exists with identifier or it may have already been ended

Using iOS 13.3.1 Xcode 11.3.1 Swift 5
Code says this, compiles but doesn't run correctly. When I background my app I get an error message that says "fails with no background task exists with identifier 1 or it may have already been ended".
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
BGTaskScheduler.shared.register(forTaskWithIdentifier: "ch.blah.refresh", using: nil) { (task) in
self.handleAppRefresh(task: task as! BGAppRefreshTask)
}
return true
}
func applicationDidEnterBackground(_ application: UIApplication) {
scheduleAppRefresh()
}
var request: BGAppRefreshTaskRequest!
func scheduleAppRefresh() {
request = BGAppRefreshTaskRequest(identifier: "ch.blah.refresh")
request.earliestBeginDate = Date(timeIntervalSinceNow: 60)
do {
try BGTaskScheduler.shared.submit(request)
} catch {
print("Could not schedule app refresh: \(error)")
}
}
func handleAppRefresh(task: BGAppRefreshTask) {
scheduleAppRefresh()
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
queue.addOperation {
for i in 1 ... 1000000 {
print("\(i)")
}
}
let lastOp = queue.operations.last
lastOp?.completionBlock = {
task.setTaskCompleted(success: !lastOp!.isCancelled)
}
task.expirationHandler = {
queue.cancelAllOperations()
}
}
Yes, I did add the key to Info.plist
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>ch.blah.refresh</string>
</array>
I am getting several failures due to iOS 13. What am I missing?
I did try downloading the Apple WWDC code to take a look. But it has the same problem, it appears.
Based on the error you're getting, it's possible one of these are true:
trying to run your app in simulator (which doesn't support BG tasks, you have to run on your device)
you didn't add the Background Modes capabilities in Xcode's project settings
Also, please note, in my experience I cant get BGAppRefreshTaskRequest to work unless I turn on BOTH "Background fetch" and "Background Processing". When I turn on just "Background fetch" nothing happens for me.
A second tip could be here:
request.earliestBeginDate = Date(timeIntervalSinceNow: 60)
This is scheduled 1 minute away. I had terrible luck getting anything scheduled immediately like this to fire. If I scheduled my bg task to run within the next 5 minutes, it wouldn't, but I scheduled it to run later tonight, it worked pretty consistently. This could just be anecdotal but something worth trying.
There is an Xcode console command that Apple recommends for firing your BG tasks on demand, but I've never gotten it to work, you may have better luck:
e -l objc -- (void)[[BGTaskScheduler sharedScheduler]
_simulateExpirationForTaskWithIdentifier:#"com.your.task.identifier"]
try this:
let queue = OperationQueue.main

Why might my iOS app be stuck on splash screen for Apple reviewers but not for me?

I'm building a new iOS app and upon submission to the App Store for review, it was rejected, with the reviewer saying:
We discovered one or more bugs in your app when reviewed on iPad running iOS 12.3 on Wi-Fi.
Specifically, the app became stuck on the splash screen. No other content loaded.
However, I personally could not reproduce this issue.
I've tried clean installs on all the simulators with Xcode, as well as a physical iPhone XS Max on iOS 12.3, and a physical iPad Pro 11in. running iOS 12.3, and the app has always been able to move past the splash/launch screen with no problems, and quickly. All of my installs are done through Xcode, but I did try installs through TestFlight as well and again could not reproduce the issue.
Edit: Should mention that I also tried with no network connection and the app loads successfully.
Code
I only use storyboard for the launch screen, which just contains a single centered image of the app logo. Other than that, all of my UI is done programmatically. In AppDelegate didFinishLaunchingWithOptions I install a root viewcontroller and the first thing it does is check for Firebase authentication by Auth.auth().addStateDidChangeListener. If there is no user, then I switch to a login view controller immediately.
Since anyone using the app for the first time is not logged in, I can't seem to understand why the app would hang on the splash screen unless somehow the Firebase auth listener does not make any progress.
(Edited to include) Actual code below.
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
// Firebase
FirebaseApp.configure()
// Set custom root view controller
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = RootViewController()
window?.makeKeyAndVisible()
return true
}
class RootViewController: UIViewController {
private var current: UIViewController
init() {
self.current = SplashViewController()
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
addChild(current)
current.view.frame = view.bounds
view.addSubview(current.view)
current.didMove(toParent: self)
}
...
}
class SplashViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
let image = UIImageView(image: UIImage(named: "1024.png")!)
image.contentMode = .scaleAspectFit
image.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(image)
image.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
image.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
image.widthAnchor.constraint(equalToConstant: 128).isActive = true
image.heightAnchor.constraint(equalToConstant: 128).isActive = true
checkAuthenticationAndChangeScreens()
}
private func checkAuthenticationAndChangeScreens() {
Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
UserDefaults.standard.set(user.uid, forKey: "userEmail")
PushNotificationManager().registerForPushNotifications {
PartnerManager.shared.fetchPartnerEmail() {
AppDelegate.shared.rootViewController.switchToMainScreen()
}
}
} else {
AppDelegate.shared.rootViewController.switchToLogOut()
UIApplication.shared.unregisterForRemoteNotifications()
}
}
}
}
// Class PushNotificationManager
func registerForPushNotifications(completion: #escaping () -> Void) {
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) {
granted, error in
guard granted else { return }
DispatchQueue.main.async {
Messaging.messaging().delegate = self //Firebase Messaging
UIApplication.shared.registerForRemoteNotifications()
self.updateFirestorePushTokenIfNeeded() // Makes a single write to Firestore
completion()
}
}
}
// Class PartnerManager
func fetchPartnerEmail(completion: #escaping () -> Void) {
let userUID = UserDefaults.standard.string(forKey: "userEmail")
db.collection("users").document(userUID).getDocument() {
(document, error) in
if let partnerEmail = document!.get("partnerEmail") as? String {
UserDefaults.standard.set(partnerEmail, forKey: "partner")
}
completion()
}
}
Something weird
One last thing that has me puzzled is that while my submission to the App Store was rejected citing the app being stuck on the splash screen (along with a screenshot of the splash screen), my submission of the build for Beta testing was rejected for a different reason - that I did not provide sign in details as I should have, but here they provided a screen shot of my sign in screen. But this would mean that the app progressed beyond the splash screen for the Beta testing review.
This leads me to think that perhaps the issue is with some kind of testing environment at Apple.
Is something different in the Apple review testing environment? How might I better attempt to reproduce this stuck-splash-screen issue? And why might the Beta testing reviewer seem to be able to load the app properly while the App Store reviewer cannot?
That may be because your UI update is not being done on main thread.
Try:
DispatchQueue.main.async {
//Update rootViewController now
}
I had once done a similar mistake.
Its a random issue as UI update is not guaranteed as its not explicitly on main thread.

HomeKit - How to update accessory isReachable value, when i come back from background to foreground?

Situation: i am developing an app, which manages HomeKit accessories. For example i do that:
Accessory is power on, and i see it via app. Also in foreground mode HMAccessoryDelegate method:
func accessoryDidUpdateReachability(HMAccessory)
works fine and i can handle status of my accessory.
I switch app to background mode.
I turn off accessory (i mean completely power off) so it must be unreachable.
I switch app to foreground mode, but accessory is still reachable.
method func accessoryDidUpdateReachability(HMAccessory) — not called.
value accessory.isReachable not updated.
Example of code when i go to foreground:
func applicationDidBecomeActive(_ application: UIApplication) {
if let home = HomeStore.sharedStore.home {
for accessory in home.accessories {
print(accessory.isReachable) //not updated
for service in accessory.services {
for characteristic in service.characteristics {
characteristic.readValue { (error) in //updated
if error == nil {
let notification = Notification(name: Notification.Name(rawValue: "UpdatedCharacteristic"))
NotificationCenter.default.post(notification)
}
}
}
}
}
}
}
Question: how to update isReachable values of accessories, when i come back from background mode to foreground?
You can create a function in the ViewController that implements HMHomeManagerDelegate:
func startHomeManager() {
manager = HMHomeManager()
manager.delegate = self
// do something here
}
and add a call to startHomeManager() to your viewDidLoad(). That will refresh your HMHome object. Then call this func in your AppDelegate:
func applicationWillEnterForeground(_ application: UIApplication) {
viewController.startHomeManager()
viewController.didRestartHomeManager()
}
A bonus is that you can call startHomeManager() for pull to refresh, etc.

How to fix IUnityGraphicsMetal error when I run Xcode project

I work to integrate an amazing AR model from Unity into Swift 4.
I finally did that using Unity 2018 (version 2018.2.4) and Swift 4.1 (Xcode 9.4.1) but when I run the project on my device (iPhone 7 Plus) is crushing and the error is coming from the path MyProjectName/Unity/Classes/Unity/IUnityGraphicsMetal.h
The error is: Thread 1: EXC_BAD_ACCESS (code=1, address=0x8).
I exported the project from Unity selecting Graphics API as Metal and also as OpenGLES3, none of this helped me.
Also in XCode -> Edit Scheme -> Run -> Options Tab -> Metal API Validation -> I set this one to Disabled, and I still get the same error.
I also update ARKit Plugin (in Unity) to the latest version (1.5) hoping that will fix the problem with UnityGraphicsMetal but apparently not.
Can anyone help me please to fix this error or to guide me to the wright way ?
Here I have 3 screenshots with the errors which maybe can help you more.
Thank you if you are reading this !
EDIT:
Here is the source code for my ViewController which is managing the bridge between Swift and Unity:
import UIKit
class ViewController: UIViewController {
#IBOutlet var rotateSwitch: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.startUnity()
NotificationCenter.default.addObserver(self, selector: #selector(handleUnityReady), name: NSNotification.Name("UnityReady"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleUnityToggleRotation(_:)), name: NSNotification.Name("UnityToggleRotation"), object: nil)
}
}
#objc func handleUnityReady() {
showUnitySubView()
}
#objc func handleUnityToggleRotation(_ n: NSNotification) {
if let isOn = n.userInfo?["isOn"] as? NSNumber {
rotateSwitch.isOn = isOn.boolValue
}
}
#IBAction func handleSwitchValueChanged(sender: UISwitch) {
UnityPostMessage("NATIVE_BRIDGE", "PlayHologram", sender.isOn ? "start" : "stop")
}
func showUnitySubView() {
if let unityView = UnityGetGLView() {
// insert subview at index 0 ensures unity view is behind current UI view
view?.insertSubview(unityView, at: 0)
unityView.translatesAutoresizingMaskIntoConstraints = false
let views = ["view": unityView]
let w = NSLayoutConstraint.constraints(withVisualFormat: "|-0-[view]-0-|", options: [], metrics: nil, views: views)
let h = NSLayoutConstraint.constraints(withVisualFormat: "V:|-75-[view]-0-|", options: [], metrics: nil, views: views)
view.addConstraints(w + h)
}
}
}
Here is the AppDelegate file:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var application: UIApplication?
#objc var currentUnityController: UnityAppController!
var isUnityRunning = false
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
self.application = application
unity_init(CommandLine.argc, CommandLine.unsafeArgv)
currentUnityController = UnityAppController()
currentUnityController.application(application, didFinishLaunchingWithOptions: launchOptions)
// first call to startUnity will do some init stuff, so just call it here and directly stop it again
startUnity()
stopUnity()
return true
}
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.
if isUnityRunning {
currentUnityController.applicationWillResignActive(application)
}
}
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.
if isUnityRunning {
currentUnityController.applicationDidEnterBackground(application)
}
}
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 isUnityRunning {
currentUnityController.applicationWillEnterForeground(application)
}
}
func applicationDidBecomeActive(_ application: 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.
if isUnityRunning {
currentUnityController.applicationDidBecomeActive(application)
}
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
if isUnityRunning {
currentUnityController.applicationWillTerminate(application)
}
}
func startUnity() {
if !isUnityRunning {
isUnityRunning = true
currentUnityController.applicationDidBecomeActive(application!)
}
}
func stopUnity() {
if isUnityRunning {
currentUnityController.applicationWillResignActive(application!)
isUnityRunning = false
}
}
}
This seems to be a tricky problem, here's a few things which may help, please try them:
In the Unity Editor -> Project Settings -> Player, there is also a checkbox option called "Metal API Validation", you said that you only set the Xcode option to disabled, perhaps this one needs to be disabled as well. Or a combination of on/off for the two.
Instead of selecting Metal and OpenGLES3, try ticking the checkbox "Auto Graphics API". In one of my projects, I left it on manual with Metal selected, but it wouldn't render at all on iOS, fixed it by turning on automatic.
In Color Gamut, have both sRGB and P3 Wide-colour selected. The iPhone 7 and newer all have displays rated for P3 wide-colour.
Hope this helps!

Resources