Previous watchOS background budget overload preventing current background app refreshes - swift

I'm developing a watchOS app that executes regularly in the background using background refresh tasks. A while ago I put in a call to update the watch's complication at each background refresh. This quickly burned through all of the complication updates and, I believe, the background budget for the app. I assumed it would reset at the beginning of the next day, but this doesn't seem to be the case. Since then, even though I've removed the complication refresh code altogether the app does not consistently execute background refreshes. I've deleted the app and reinstalled it and waited about a week but it only executes these refreshes regularly when run through Xcode (on the device), not when run independently. Any ideas?
Update:
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
for backgroundTask in backgroundTasks {
switch backgroundTask {
case let connectivityTask as WKWatchConnectivityRefreshBackgroundTask:
connectivityTask.setTaskCompleted()
case let refreshTask as WKApplicationRefreshBackgroundTask:
MainInterfaceController.scheduleTask()
refreshTask.setTaskCompleted()
case let snapshotTask as WKSnapshotRefreshBackgroundTask:
snapshotTask.setTaskCompleted()
default:
backgroundTask.setTaskCompleted()
}
}
DataProcessor.sharedInstance.dataProcessing()
public static func scheduleTask() {
let fireDate = Date(timeIntervalSinceNow: 10.0)
WKExtension.shared().scheduleBackgroundRefresh(withPreferredDate: fireDate, userInfo: nil) { (error) in
if (error == nil) {
print("Background task scheduled")
}
}
}
And a task is scheduled when the application deactivates, kicking off the chain. This code was all working previously, which is the confusing part.

Related

Carplay Resume Music

I am creating a CarPlay music app, everything works fine except when I get a call in between my music is playing. Carplay pauses music in between but when I end phone call its not resuming back.
Please help if anyone has faced same issue
CarPlay is not actually playing or pausing the music, it is still your app on the user's device that is doing all the work and CarPlay is just presenting an interface to interact with your app which is what you do in your CarPlaySceneDelegate
Therefore, the handling of how your app resumes after the call is not part of CarPlay.
You can try the following in your player management module:
1. Listen for audio interruption notifications
NotificationCenter.default
.addObserver(self,
selector: #selector(handleInterruption(_:)),
name: AVAudioSession.interruptionNotification,
object: nil)
2. Check the status of the interruption and handle accordingly
#objc
private func handleInterruption(_ notification: Notification)
{
// Check if we have valid information from the notification
// with regards to the interruption
guard let info = notification.userInfo,
let typeValue = info[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSession.InterruptionType(rawValue: typeValue)
else
{
return
}
// We check if the interruption has begun
if type == .began
{
// Pause the AVPlayer in that case
player?.pause()
}
else if type == .ended
{
// We check if we have instructions on how to handle the interruptions
guard let optionsValue = info[AVAudioSessionInterruptionOptionKey] as? UInt else
{
return
}
// Convert the optionsValue into an AVAudioSession.InterruptionOptions
// which can be tested
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
// Check if the playback can resume after the interruption
if options.contains(.shouldResume)
{
// Request AVPlayer to resume the playback
player?.play()
}
}
}
Give this a try and see if it helps your situation
Update based on OP comments
The audio not re-starting is not related to CarPlay, if you app resumes audio after a call, this will be carried over to CarPlay.
With that being said, if you have not already, add this code when your player initializes:
UIApplication.shared.beginReceivingRemoteControlEvents()
Maybe my code does not work for your exact scenario but I believe you will need to use the AVAudioSession.interruptionNotification which is what get's called when a phone call, siri or similar things interrupt your audio.
When this interruption has ended, you need to restart your audio.
I can't show you a CarPlay example, but here is an example of the above code in action when Siri interrupts the audio, the lock screen shows the status of the audio being paused, when Siri is dismissed, the app resumes the player as you can see.
So I found solution: - I was missing one line of code,
"try AVAudioSession.sharedInstance() .setCategory(AVAudioSession.Category.playback)" this is must for maintaining audio session

Something calls a method when the app is in the background

I am using a timer to check certain conditions every minute. Timer code:

func startTimer() {
timer?.invalidate()
timer?.tolerance = 0.2
timer = Timer.init(fire: Day.dateWithNextMinute(), interval: 5, repeats: true) { [weak self] _ in
self?.checkToPass()
}
RunLoop.current.add(timer!, forMode: .default)
}
Everything works correctly.
But there were problems: for some users, the application began to crash when app is in the background, due to an unhandled value in the checkToPass() method (identified by the logs).
I can't figure out how this method can be called when the application is in the background. Is it possible that RunLoop has a side effect and somehow the body of the timer can be called (in the background)? Or maybe it's the timer?
No specific crash time was found. The app was just in the background.


Thanks for any ideas


Check if Screen is recording swift

I am trying to check if the screen is recording before allowing a following action, I initially tried to use ReplayKit to automatically record, but this isn't a viable solution because it doesn't allow for recording outside the app, so basically what I want to do is check if the user has began screen recording using the IOS Control Centre recorder, before allowing them to execute another piece of code.
Something like:
func handleScreen() {
var isRecording: Bool = false
if ScreenIsRecording { //(this is what i don't know how to check)
isRecording = true
}
if isRecording == true {
// execute this code.
}
}
I am open to other solutions of being able to execute screen recording, but it has to be able to record all screens not just the in-app screen.
Thanks
UIScreen includes the UIScreen.isCaptured property which you should be able to reference to determine if the screen is likely being recorded. However this will also return true if the device is being AirPlayed or otherwise broadcast:
A value of YES indicates the system is actively recording, mirroring,
or using AirPlay to stream the contents of the screen.
if UIScreen.mainScreen().isCaptured {
isRecording = true
}
I just tested this on iOS 16, and even though the documentation says that isCaptured should return true for AirPlay. It does not when I tested it!
Observe changes to isCaptured with NotificationCenter:
NotificationCenter.default.addObserver(self, selector: #selector(screenCaptureDidChange),
name: UIScreen.capturedDidChangeNotification,
object: nil)
Handle the notification:
#objc func screenCaptureDidChange() {
debugPrint("screenCaptureDidChange.. isCapturing: \(UIScreen.main.isCaptured)")
if !UIScreen.main.isCaptured {
//TODO: They stopped capturing..
} else {
//TODO: They started capturing..
debugPrint("screenCaptureDidChange - is recording screen")
}
}
This notification does NOT get fired when you start AirPlay, and if you start screen recording while AirPlaying, when you stop recording the notification will get fired but UIScreen.main.isCaptured is false even though AirPlay is still active.

How to add listener for all running applications

I want to display a list of all running application names.
Issue: It doesn't add an app that is running after the function is called. Therefore, it doesn't add the app name to the list
simultaneously.
Goal: I want to add a listener, so if a new app is running it will add it to the array simultaneously without restarting the app or recalling the function again.
func allRunningApplications() {
for runningApplication in NSWorkspace.shared.runningApplications {
let appName = runningApplication.localizedName
// Add App Name to Array
array.append(appName)
}
}
I mentioned the "did launch", et. al., notifications because you didn't explain why you wanted to monitor the set of running applications.
If you are only interested in whether a specific app has launched (or quit), it would probably be easier to use the NSWorkspace notifications:
(untested code)
let center = NSWorkspace.shared.notificationCenter
center.addObserver(forName: NSWorkspace.didLaunchApplicationNotification,
object: nil, // always NSWorkspace
queue: OperationQueue.main) { (notification: Notification) in
if let app = notification.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication {
if app.bundleIdentifier == "com.apple.Terminal" {
// User just launched the Terminal app; should we be worried?
}
}
}
Note that workspace notifications are posted to NSWorkspace's private notification center, not the default notification center, so remember to add your observers there.
You could poll the runningApplications property (check it every x seconds) to test, if there is a new application. But it's not recommended: https://developer.apple.com/documentation/appkit/nsworkspace/1534059-runningapplications
Similar to the NSRunningApplication class’s properties, this property
will only change when the main run loop is run in a common mode.
Instead of polling, use key-value observing to be notified of changes
to this array property.
So use key-value observing on NSWorkspace.shared.runningApplications
A good example can be found here: https://www.ralfebert.de/ios-examples/swift/property-key-value-observer/
For your code it should be something like this:
var observers = [NSKeyValueObservation]()
override func viewDidLoad() {
super.viewDidLoad()
observeModel()
}
func observeModel() {
self.observers = [
NSWorkspace.shared.observe(\.NSWorkspace.runningApplications, options: [.initial]) {(model, change) in
// runningApplications changed, so update your UI or something else
}
]
}
(untested code)
You can try using the notification centre of NSWorkspace.
self.workspace = [NSWorkspace new];
NSArray *myObserver;
myObserver = (NSArray*) [[[NSWorkspace sharedWorkspace] notificationCenter] addObserverForName: NSWorkspaceWillLaunchApplicationNotification object:nil queue:nil usingBlock:^(NSNotification *note)
{
if(note)
{
//do your action
}
}
];
NSWorkspaceWillLaunchApplicationNotification will notify you if any application is about to be launched.

Dispatch to run code on background without UI waiting?

I'm making an application in which I do download some stuffs once I connect. I fulfill my CoreData without downloading my pictures. Now, once I connected, I push my segue and get a main screen.
Now what I want is to be able to swap my screens and download the pictures at the same time.
I do download from a Singleton class this way:
let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
let tmp = DownloadManagerSingleton.sharedInstance
tmp.startDownloads()
dispatch_async(dispatch_get_main_queue(), { () -> Void in
print("This is run on the main queue, after the previous code in outer block")
})
})
I can't find any way to download on background mode without the UI being locked until the downloads are finished. Am I doing this wrong or is there another way to fulfill my goal ?