Post

Replies

Boosts

Views

Activity

Reply to How to use BGAppRefreshTask without server update?
It's true that the typical BG update involves a URL session to get web data, but it doesn't have to. In the App Delegate, you need to set up your app. Here is the code I have in my didFinishLaunchingWithOptions in my app delegate: (Note that self.logSB is my logging, similar to a Print command.) &#9;&#9;&#9;&#9;switch UIApplication.shared.backgroundRefreshStatus {         case .available:             self.logSB.debug("backgroundRefreshStatus is AVAILABLE")         case .denied:             self.logSB.debug("backgroundRefreshStatus is DENIED")         case .restricted:             self.logSB.debug("backgroundRefreshStatus is RESTRICTED")         default:             self.logSB.debug("backgroundRefreshStatus is unknown")         }         if #available(iOS 13.0, *) {             self.logSB.verbose("iOS 13  Registering for Background duty")             BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.WH.myApp.myIdentifier", using: nil) { task in                 self.handleAppRefresh(task: task as! BGAppRefreshTask)                 self.logSB.verbose("iOS 13  Refresh requested")             }         } else {             self.logSB.verbose("iOS < 13  Registering for Background duty")             UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum)             //                UIApplication.shared.setMinimumBackgroundFetchInterval(200)         } You can also see that I support the older background session method. That's what my app started with before iOS 13 came along. You could probably drop that. Up at the AppDelegate class level: &#9;&#9;&#9;&#9; var backgroundSessionCompletionHandler: (() -> Void)? &#9;&#9;     var backgroundSynchTask: UIBackgroundTaskIdentifier = .invalid The pre-iOS 13 fetch:     func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {         let dateFormatter = DateFormatter()         dateFormatter.dateStyle = DateFormatter.Style.medium         dateFormatter.timeStyle = DateFormatter.Style.long         var convertedDate = dateFormatter.string(from: Date())         if #available(iOS 13.0, *) {             self.logSB.info("iOS 13 Ignoring Background Fetch called at \(convertedDate)")             completionHandler(.newData)         } else {             self.logSB.info("Background Fetch called at \(convertedDate)")             let myState = UIApplication.shared.applicationState             switch myState {             case .background :                 self.logSB.info("The app state was found to be Background at \(convertedDate)")             case .inactive:                 self.logSB.info("The app state was found to be Inactive at \(convertedDate)")             case .active:                 self.logSB.info("The app state was found to be Active at \(convertedDate)")             default:                 self.logSB.info("The app state was found to be Unknown at \(convertedDate)")             }             Central().fetch {                    convertedDate = dateFormatter.string(from: Date())                 self.logSB.info("Calling the Fetch completion handler at \(convertedDate)\n\n")                 completionHandler(.newData)             } &#9;&#9;&#9;&#9;}     } The Central().fetch function is what goes and gets current web data, but it doesn't have to. It could be anything. And finally:     func applicationDidEnterBackground(_ application: UIApplication) { &#9;      let dateFormatter = DateFormatter()         dateFormatter.dateStyle = DateFormatter.Style.medium         dateFormatter.timeStyle = DateFormatter.Style.long         let convertedDate = dateFormatter.string(from: Date())         self.logSB.info("The app entered the background at \(convertedDate)")         if #available(iOS 13.0, *) {             scheduleAppRefresh()         }     }     @available(iOS 13.0, *)     func scheduleAppRefresh() {         logSB.info("Scheduling the AppRefresh in iOS 13 !!")         let request = BGAppRefreshTaskRequest(identifier: "com.WH.myApp.myIdentifier")         request.earliestBeginDate = Date(timeIntervalSinceNow: 2 * 60)         do {             try BGTaskScheduler.shared.submit(request)         } catch {             logSB.error("Could not schedule app refresh: \(error)")         }     }     @available(iOS 13.0, *)     func handleAppRefresh(task: BGAppRefreshTask) {         logSB.info("Handling the AppRefresh in iOS 13")         scheduleAppRefresh()         let queue = OperationQueue()         queue.maxConcurrentOperationCount = 1         let appRefreshOperation =  Central().fetch2         queue.addOperation(appRefreshOperation)         let lastOperation = queue.operations.last         lastOperation?.completionBlock = {             task.setTaskCompleted(success: !(lastOperation?.isCancelled ?? false))         }         task.expirationHandler = {             queue.cancelAllOperations()         } &#9;      let dateFormatter = DateFormatter()         dateFormatter.dateStyle = DateFormatter.Style.medium         dateFormatter.timeStyle = DateFormatter.Style.long         let convertedDate = dateFormatter.string(from: Date())         self.logSB.info("Completed the iOS 13 App Refresh at \(convertedDate)")     } 
Oct ’20
Reply to Why "would [my app] like to find and connect to devices on your local network"?
SOLVED !! I gave up trying to figure out what was triggering the alert. So to clear Review, I relented and added the info.plist item: Privacy - Local Network Usage Description ...|... String ...|... "my text" Guess what? That squelches the alert entirely! My alert text is never seen. My app is not added to the list of apps that have asked for Local Network access. This fixed both of my affected apps. Seems like a bug. Agreed?
Oct ’20
Reply to Why "would [my app] like to find and connect to devices on your local network"?
If I were in your shoes I’d: Set up a test device running iOS 14. Reset the alert state (FAQ-13). Run and exercise its functionality to see if you can trigger the alert. OK, I've done that and discovered that both my apps, once installed after updating to iOS 14, trigger the alert. So, does accessing data on the web - in a URLSession or by using SafariServices - trigger the alert? The presentation implies that it does not but your (very useful) list leads me to wonder. The only other thing I can think of is that both my apps determine the user's location. It seems like this should be handled fully by the location privacy setting?
Oct ’20
Reply to Why "would [my app] like to find and connect to devices on your local network"?
I just discovered that my app had "Application supports iTunes file sharing" set to "YES". No idea how that happened. I've turned it off. Could that do it? I can't reproduce the problem except by submitting another build for review. [update] Another app of mine has the same setting turned on and there is no alert problem with that app. I gather it's a default setting. I've also discovered that my troubled app contained some c code with print statements. That particular code never executes (and I've deleted it). Could a print command that never runs cause the app to go look for a printer?
Oct ’20
Reply to Active app being sent to background - why?
How can I diagnose this? Unexpected demotion to the background continues to plague me. It happens only rarely and may only affect some users. My app is the active one, but suddenly is sent to the background. I've tried to use logging to sort it out but I see no evidence of low power, no memory warning, nothing except the .applicationWillResignActive event. In the most recent event, background operation commenced and app refreshes continued normally. But I need the app to stay active when that's what the user expects!
Aug ’20
Reply to Why is reading HomeKit sensor data not possible in background
I went through this several years ago and came to the dead end that HomeKit activity is only available to an app that is active in the foreground. I had to develop some fairly elaborate ways to work around this. I submitted feedback at the time but the bottom line was the security edict that background entitlement was not possible. Today I received a cryptic Feedback from Apple on that old request: "After reviewing your feedback, we have some additional information for you: The HomeKit entitlement is public now. Please close your feedback if this is no longer an issue for you. Thanks!" I haven't been able to figure out yet what this means!
Jul ’20
Reply to Active app being sent to background - why?
My disappearing app problem still happens with the timer tolerance set to 180, my timer code below.When my app enters "NightMode", I disable the Idle timer to keep the app active and in the foreground all night, and I turn on the proximity monitor to allow the screen to go black when the phone is placed face down. UIApplication.shared.isIdleTimerDisabled = true UIDevice.current.isProximityMonitoringEnabled = trueA user's log shows that the .isIdleTimerDisabled = true was correct at 1:36AM and again at 1:46AM. The log then goes blank, meaning the app was "dead". My app is heavily logged for debugging and won't do hardly anything without making a log entry. After 1:46 there was nothing about any of these events, which should all generate log entries:applicationWillResignActive applicationDidEnterBackground applicationWillTerminateThe app came back to life and logging in the background at 4:11AM this morning. It reported that the .isIdleTimerDisabled = false at that time. How did it get reset? What the heck happened to my app after 1:46AM?Starting my timer for NightMode: weak var timer: Timer? func startTimer() { timer?.invalidate() // just in case you had existing `Timer`, `invalidate` it before we lose our reference to it if #available(iOS 10.0, *) { timer = Timer.scheduledTimer(withTimeInterval: 600.0, repeats: true) { [weak self] _ in self?.timer?.tolerance = 180 // Upped from 80 on 5/27/20 let dateFormatter = DateFormatter() // format the date for output dateFormatter.dateStyle = DateFormatter.Style.medium dateFormatter.timeStyle = DateFormatter.Style.long let convertedDate = dateFormatter.string(from: Date()) self?.logSB.warning("The timer triggered at \(convertedDate)") } } else { // Fallback on earlier versions } }
Jun ’20
Reply to How to force a fresh install?
"This is now way beyond me."I know how that feels!"... the question is not 'how to force a complete reinstall' but rather 'why is the system blacklisting me'."Agreed, I've managed to do a clean re-install but the root problem remains. I submitted a ticket on that topic."I think you need to respond sooner than 20 seconds with a completion handler and I'm not sure you always want to indicate .newData if there was no new data."My understanding is that I have 30 seconds. Picking 20 is a bit of a kludge, a dropping of the guillotine blade. I would prefer to send a completion handler back when everything else is actually done (which may be much sooner). Or wrap up gracefully if the other stuff cannot complete in 30 seconds. But that stuff confuses me and I never worked through it all.Always reporting "newData" is the recommended trick to keep background fetch requests coming in as frequently as possible. It's not really cheating, since it will indeed be new data as long as it completes.
Jun ’20
Reply to How to force a fresh install?
Just to elaborate a bit more about how my fetches start and wrap up. I see now that I may have not properly set it up in iOS13. Not sure. See what you think. Maybe I need to invalidate any URL session still running?It works fine when everything gets done quickly but may not be graceful if/when the fetch does not complete in time. Note that one affected user I'm in contact with is on iOS 12 and another is on iOS 13. The iOS 12 user did the nuclear reinstall and now has normal function, ie. frequent background sessions. iOS 12 versionIn my AppDelegate: func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -&gt; Void) { let dateFormatter = DateFormatter() // format the date for output dateFormatter.dateStyle = DateFormatter.Style.medium dateFormatter.timeStyle = DateFormatter.Style.long var convertedDate = dateFormatter.string(from: Date()) if #available(iOS 13.0, *) { self.logSB.info("iOS 13 Ignoring Background Fetch called at \(convertedDate)") completionHandler(.newData) } else { Central().fetch { // Goes and gets fresh data convertedDate = dateFormatter.string(from: Date()) self.logSB.info("Calling the Fetch completion handler at \(convertedDate)\n\n") completionHandler(.newData) } // End call of Central().Fetch } // End else } // Close funcIn my Central file: func fetch(_ completion: @escaping () -&gt; Void) { let dateFormatter = DateFormatter() dateFormatter.dateStyle = DateFormatter.Style.medium dateFormatter.timeStyle = DateFormatter.Style.medium let convertedDate = dateFormatter.string(from: Date()) self.logSB.info("Running the CENTRAL fetch at \(convertedDate)") self.getWebFetchesBackground() // This launches the URL sessions let timer = Timer.scheduledTimer(withTimeInterval: 20.0, repeats: false) { (timer) in completion() // This sends a completion back to the AppDelegate after 20 seconds. } }iOS 13 versionIn my AppDelegate: @available(iOS 13.0, *) func handleAppRefresh(task: BGAppRefreshTask) { logSB.info("Handling the AppRefresh in iOS 13") scheduleAppRefresh() let queue = OperationQueue() queue.maxConcurrentOperationCount = 1 let appRefreshOperation = Central().fetch2 queue.addOperation(appRefreshOperation) let lastOperation = queue.operations.last lastOperation?.completionBlock = { task.setTaskCompleted(success: !(lastOperation?.isCancelled ?? false)) } task.expirationHandler = { // After all operations are cancelled, the completion block below is called to set the task to complete. queue.cancelAllOperations() } let dateFormatter = DateFormatter() dateFormatter.dateStyle = DateFormatter.Style.medium dateFormatter.timeStyle = DateFormatter.Style.long let convertedDate = dateFormatter.string(from: Date()) self.logSB.info("Completed the iOS 13 App Refresh at \(convertedDate)") }In my Central file: func fetch2() { let dateFormatter = DateFormatter() dateFormatter.dateStyle = DateFormatter.Style.medium dateFormatter.timeStyle = DateFormatter.Style.medium let convertedDate = dateFormatter.string(from: Date()) self.logSB.info("Running the NEW CENTRAL fetch at \(convertedDate)") self.getWebFetchesBackground() }
Jun ’20
Reply to How to force a fresh install?
"Also, have you checked UIBackgroundRefreshStatus to be sure it is still set?"I have recently incorprated logging the status that into didFinishLaunchingWithOptions in my AppDelegate. It appears to be "available" for the affected users, at least when the app starts.As for satisfying the system that my fetches wrap up properly, I guess I'm not sure. I get background fetches all day long just fine, so in general I feel the code is OK. Other users too. But if there is something about the affected users that is causing a problem with getting done in time (slow internet?), I suppose my app might be getting blacklisted. I need a way to check that and more importantly to reset it when a new build is installed
Jun ’20
Reply to How to force a fresh install?
" 1) regarding keychain: ... keychain entries do not get deleted when an app is deleted from the device. "So this is a potential source of my problem, if the system has placed something there that interferes with my app."All other files get deleted when the app is deleted from the device. I do not know if iCloud key-value file gets deleted, but I think not."Definitely not in the short run, and I think not ever. (The cloud file, I mean, nothing local.) This enables you to trash an app and reinstall it at a later date while retaining some of the old information." 3)&gt; Turning off background app refresh before reinstalling the appI am not familiar with 'background app refresh' - what is that?"Check out your Settings&gt;&gt;General&gt;&gt;Background App Refresh to find a list of apps on your phone using this feature. It allows a small amount of resources for apps to operate in the background to fetch fresh web data, for instance." 4) ThIs this a race issue where the app reinstalls and starts faster than the app can download something in the background? " No, it's a matter of the system never allocating those meager background resources despite all the pieces being in place. Once the system cuts off an app, it seems impossible to get going again. It happens every 20 minutes or so when things are rolling.
Jun ’20
Reply to How to force a fresh install?
Logging out is not required. The user took that upon himself after it became clear that new builds of the app (delivered via TestFlight or the app store) did not solve the problem. Turning off background app refresh before reinstalling the app accomplishes the same thing - it blows away all old settings - and is far less disruptive. Of course it's still a pain for users and I'd love to find another way.The root problem is that users can find themselves never being allocated background time for the app to perform web fetches. There is seemingly nothing that can be done internally in the app to restore background fetches once that state is reached. I would love to learn otherwise.
Jun ’20
Reply to How to check for background fetch eligibility?
I'm still trying to diagnose why there is little or no allocation of background time for background fetches for a few troubled users. I uncovered a big clue today by taking a user thru a nuclear re-install: Trash the app, sign out of AppleID, reinstall, open the app, log back into AppleID. This lost all settings but restored normal function of background fetches.My app uses iCloud storage of key-value data to backup the users settings. This preserves settings thru a normal re-install and also allows the app to be portable to the user's iPad or AppleTV.Something toxic can also stored there, apparently. Are there any tools for resetting the iCloud data, or for examining it to determine where the toxic information lives? I'm not sure how to proceed.As an aside, one user does not put his phone on a charger overnight and experienced erratic behavior. I looked into checking the power situation:let dateFormatter = DateFormatter() .dateStyle = DateFormatter.Style.medium .timeStyle = DateFormatter.Style.long let convertedDate = dateFormatter.string(from: Date()) UIDevice.current.isBatteryMonitoringEnabled = true var batteryState: UIDevice.BatteryState { UIDevice.current.batteryState } switch batteryState { case .unknown: self?.logSB.debug("The batteryState is UNKNOWN at \(convertedDate)") case .unplugged: self?.logSB.debug("The batteryState is UNPLUGGED at \(convertedDate)") case .charging: self?.logSB.debug("The batteryState is CHARGING at \(convertedDate)") case .full: self?.logSB.debug("The batteryState is FULL at \(convertedDate)") default: self?.logSB.debug("The batteryState is unknown at \(convertedDate)") } var batteryLevel: Float { UIDevice.current.batteryLevel } self?.logSB.debug("The battery level is \(batteryLevel) at \(convertedDate)") let myState = UIApplication.shared.applicationState switch myState { case .background : self?.logSB.info("The app state was found to be Background at \(convertedDate)") case .inactive: self?.logSB.info("The app state was found to be Inactive at \(convertedDate)") case .active: self?.logSB.info("The app state was found to be Active at \(convertedDate)") default: self?.logSB.info("The app state was found to be Unknown at \(convertedDate)") }
May ’20