Background Tasks

RSS for tag

Request the system to launch your app in the background to run tasks using Background Tasks.

Background Tasks Documentation

Pinned Posts

Posts under Background Tasks tag

144 Posts
Sort by:
Post not yet marked as solved
2 Replies
161 Views
Hi, I have a watchOS app that records audio for an extended period of time and because the mic is active, continues to record in background mode when the watch face is off. However, when a call comes in or Siri is activated, recording stops because of an audio interruption. Here is my code for setting up the session: private func setupAudioSession() { let audioSession = AVAudioSession.sharedInstance() do { try audioSession.setCategory(.playAndRecord, mode: .default, options: [.overrideMutedMicrophoneInterruption]) try audioSession.setActive(true, options: .notifyOthersOnDeactivation) } catch { print("Audio Session error: \(error)") } } Before this I register an interruption handler that holds a reference to my AudioEngine (which I start and stop each time recording is activated by the user): _audioInterruptionHandler = AudioInterruptionHandler(audioEngine: _audioEngine) And here is how this class implements recovery: fileprivate class AudioInterruptionHandler { private let _audioEngine: AVAudioEngine public init(audioEngine: AVAudioEngine) { _audioEngine = audioEngine // Listen to interrupt notifications NotificationCenter.default.addObserver(self, selector: #selector(handleAudioInterruption(notification:)), name: AVAudioSession.interruptionNotification, object: nil) } @objc private func handleAudioInterruption(notification: Notification) { guard let userInfo = notification.userInfo, let interruptionTypeRawValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt, let interruptionType = AVAudioSession.InterruptionType(rawValue: interruptionTypeRawValue) else { return } switch interruptionType { case .began: print("[AudioInterruptionHandler] Interruption began") case .ended: print("[AudioInterruptionHandler] Interruption ended") print("Interruption ended") do { try AVAudioSession.sharedInstance().setActive(true) } catch { print("[AudioInterruptionHandler] Error resuming audio session: \(error.localizedDescription)") } default: print("[AudioInterruptionHandler] Unknown interruption: \(interruptionType.rawValue)") } } } Unfortunately, it fails with: Error resuming audio session: Session activation failed Is this even possible to do on watchOS? This code worked for me on iOS. Thank you, -- B.
Posted
by trzy.
Last updated
.
Post not yet marked as solved
2 Replies
189 Views
Hi all, My application requires to create a WebSocket server on an iOS application for other devices can connect and transfer data with my application. I used Vapor library to create a socket server and it works well when the application is in the foreground. I am trying to keep the server alive when my app moves to the background or the suspended state so that my app and other devices can continue to communicate with each other. Is there any ways to achieve that? I tried to turn on a mode: "Audio, Airplay, and Picture in Picture" in background modes section in Signing & Capabilities and then my application can still communicate with clients when it is background mode. But my application is an application for user can edit image and send it to other devices through sockets and it does not have audio, airplay,.. feature. Is it ok to publish the app to the app store in the future? Thank you!
Posted
by lgminh.
Last updated
.
Post not yet marked as solved
1 Replies
205 Views
Hello, I have an app that receives critical alarms. This is usually done through remote push notifications from the server, but to add redundancy I'd like to add a MQTT connection as well. There are scenarios where internet connection might be missing (but there is a local WiFi connection to the server) hence I'd like to deliver the alarms directly from server to client without going out via the Internet. The problem is that according to all restrictions on iOS, the MQTT connection will not be maintained in the background and disconnect occurs within 20-30 sec after going in the background and shutting the screen. I'm aware of all the background modes that iOS allows but none fall within this scenario. Is there a way to maintain a MQTT connection (or some other type of network connection) in the background on iOS?
Posted
by codenea1.
Last updated
.
Post marked as solved
6 Replies
373 Views
My App keeps crashing in the background and I don't know why. I'm using SwiftData and SwiftUI. I'm setting up a .backgroundTask like this: import SwiftUI import SwiftData import TipKit @main struct MyAppName: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate @State var navManager = NavigationManager.load() @State var alerter: Alerter = Alerter() var sharedModelContainer: ModelContainer = { do { return try ModelContainer(for: DataController.schema, configurations: [DataController.modelConfig]) } catch { fatalError("Could not create ModelContainer: \(error)") } }() var body: some Scene { WindowGroup { SetupView() .accentColor(.myAccentColor) .environment(alerter) .alert(isPresented: $alerter.isShowingAlert) { alerter.alert ?? Alert(title: Text(verbatim: "")) } .task { try? Tips.configure([ .displayFrequency(.immediate), .datastoreLocation(.applicationDefault) ]) } .onAppear { setUpAppDelegate() } } .modelContainer(sharedModelContainer) .backgroundTask(.appRefresh(Const.backgroundAppRefreshId)) { @MainActor in let container = sharedModelContainer await RefreshManager.handleBackgroundVideoRefresh(container) } .environment(navManager) } func setUpAppDelegate() { appDelegate.navManager = navManager } } The RefreshManager.handleBackgroundRefresh(...) goes on to load data and then insert models for them via SwiftData. It only happens occasionally and afaik only in the background. Weirdly enough the issue seems to be there even when I only print something in the background task. Even when I don't schedule/set a background task at all. How can that be? The crashes started in the version that included the .backgroundTask, although perhaps it's related to something else. I'm still trying to further narrow it down. This is the crash report that I'm getting: Exception Type: EXC_BREAKPOINT (SIGTRAP) Exception Codes: 0x0000000000000001, 0x00000001a1bb98c0 Termination Reason: SIGNAL 5 Trace/BPT trap: 5 Terminating Process: exc handler [809] Triggered by Thread: 0 Thread 0 name: Thread 0 Crashed: 0 libswiftCore.dylib 0x00000001a1bb98c0 _assertionFailure(_:_:file:line:flags:) + 264 (AssertCommon.swift:144) 1 libswiftCore.dylib 0x00000001a1c27d14 swift_unexpectedError + 664 (ErrorType.swift:188) 2 _SwiftData_SwiftUI 0x000000024310cd78 one-time initialization function for empty + 300 (ModelContainer+Extensions.swift:5) 3 libdispatch.dylib 0x00000001ab16add4 _dispatch_client_callout + 20 (object.m:576) 4 libdispatch.dylib 0x00000001ab16c654 _dispatch_once_callout + 32 (once.c:52) 5 _SwiftData_SwiftUI 0x000000024310cdf8 one-time initialization function for empty + 124 (ModelContainer+Extensions.swift:12) 6 libdispatch.dylib 0x00000001ab16add4 _dispatch_client_callout + 20 (object.m:576) 7 libdispatch.dylib 0x00000001ab16c654 _dispatch_once_callout + 32 (once.c:52) 8 _SwiftData_SwiftUI 0x0000000243122170 key path getter for EnvironmentValues.modelContext : EnvironmentValues + 140 (<compiler-generated>:0) 9 libswiftCore.dylib 0x00000001a1ce4628 RawKeyPathComponent._projectReadOnly<A, B, C>(_:to:endingWith:) + 1012 (KeyPath.swift:1701) 10 libswiftCore.dylib 0x00000001a1ce3ddc KeyPath._projectReadOnly(from:) + 1036 (KeyPath.swift:331) 11 libswiftCore.dylib 0x00000001a1ce8348 swift_getAtKeyPath + 24 (KeyPath.swift:2029) 12 SwiftUI 0x00000001a7af4814 EnvironmentBox.update(property:phase:) + 872 (Environment.swift:273) 13 SwiftUI 0x00000001a782a074 static BoxVTable.update(ptr:property:phase:) + 396 (DynamicPropertyBuffer.swift:294) 14 SwiftUI 0x00000001a78297b0 _DynamicPropertyBuffer.update(container:phase:) + 104 (DynamicPropertyBuffer.swift:215) 15 SwiftUI 0x00000001a887fb78 closure #1 in closure #1 in DynamicBody.updateValue() + 104 (DynamicProperty.swift:447) 16 SwiftUI 0x00000001a887fbb8 partial apply for closure #1 in closure #1 in DynamicBody.updateValue() + 28 (<compiler-generated>:0) 17 libswiftCore.dylib 0x00000001a1bcc068 withUnsafeMutablePointer<A, B>(to:_:) + 28 (LifetimeManager.swift:82) 18 SwiftUI 0x00000001a887f9dc closure #1 in DynamicBody.updateValue() + 408 (DynamicProperty.swift:446) 19 SwiftUI 0x00000001a887f5c0 DynamicBody.updateValue() + 712 (DynamicProperty.swift:445) 20 SwiftUI 0x00000001a71e8bf8 partial apply for implicit closure #1 in closure #1 in closure #1 in Attribute.init<A>(_:) + 32 (<compiler-generated>:0) 21 AttributeGraph 0x00000001cbd4c240 AG::Graph::UpdateStack::update() + 512 (ag-graph-update.cc:578) 22 AttributeGraph 0x00000001cbd42f38 AG::Graph::update_attribute(AG::data::ptr<AG::Node>, unsigned int) + 424 (ag-graph-update.cc:719) 23 AttributeGraph 0x00000001cbd42810 AG::Graph::input_value_ref_slow(AG::data::ptr<AG::Node>, AG::AttributeID, unsigned int, unsigned int, AGSwiftMetadata const*, unsigned char&, long) + 720 (ag-graph.cc:1429) 24 AttributeGraph 0x00000001cbd423a4 AGGraphGetValue + 228 (AGGraph.mm:701) 25 SwiftUI 0x00000001a887f548 DynamicBody.updateValue() + 592 (DynamicProperty.swift:444) 26 SwiftUI 0x00000001a71e8bf8 partial apply for implicit closure #1 in closure #1 in closure #1 in Attribute.init<A>(_:) + 32 (<compiler-generated>:0) 27 AttributeGraph 0x00000001cbd4c240 AG::Graph::UpdateStack::update() + 512 (ag-graph-update.cc:578) 28 AttributeGraph 0x00000001cbd42f38 AG::Graph::update_attribute(AG::data::ptr<AG::Node>, unsigned int) + 424 (ag-graph-update.cc:719) [...] 107 SwiftUI 0x00000001a7cf79fc static App.main() + 132 (App.swift:114) 108 MyAppName 0x0000000100e6d120 static MyAppNameApp.$main() + 52 (MyAppNameApp.swift:0) 109 MyAppName 0x0000000100e6d120 main + 64 110 dyld 0x00000001c67c2d84 start + 2240 (dyldMain.cpp:1298) The report also says key path getter for EnvironmentValues.modelContext, which seems odd. Any idea where I could start to look for the issue? I'm currently just trying things out, pushing them to TestFlight and waiting for crashes to happen. As soon as I can narrow it down further I'll update this.
Posted
by fer0n.
Last updated
.
Post not yet marked as solved
5 Replies
391 Views
My background audio app stops updating its Live Activity after the iPhone locks, and doesn't resume updating the activity after tapping the screen or even after FaceID unlocks the device (without opening the lock screen). My live activity requests a ContentState update & iOS updates the content for the activity as below: Task{ log.debug("LiveActivityManager.updateLiveActivity() with new ContentState") await liveActivity.update( ActivityContent(state:contentState, staleDate:nil) ) } Below what my log looks like: <<<<SWIPE LOCK SCREEN DOWN>>>> DEBUG: LiveActivityManager.updateLiveActivity() with new ContentState iOS: Updating content for activity 0A519263-1E46-4BB6-BA4F-F3DDBC081AB4 DEBUG: LiveActivityManager.updateLiveActivity() with new ContentState iOS: Updating content for activity 0A519263-1E46-4BB6-BA4F-F3DDBC081AB4 <<<<PRESS LOCK BUTTON->Lock iPhone>>>> INFO: --------protectedDataWillBecomeUnavailableNotification-------- DEBUG: LiveActivityManager.updateLiveActivity() with new ContentState iOS: Updating content for activity 0A519263-1E46-4BB6-BA4F-F3DDBC081AB4 DEBUG: LiveActivityManager.updateLiveActivity() with new ContentState DEBUG: LiveActivityManager.updateLiveActivity() with new ContentState DEBUG: LiveActivityManager.updateLiveActivity() with new ContentState <<<<LOOK AT & TAP LOCK SCREEN->Unlock iPhone without swiping up>>>> INFO: --------protectedDataDidBecomeAvailableNotification----------- DEBUG: LiveActivityManager.updateLiveActivity() with new ContentState DEBUG: LiveActivityManager.updateLiveActivity() with new ContentState DEBUG: LiveActivityManager.updateLiveActivity() with new ContentState As shown in the log, normally iOS updates the content for my activity after my liveActivity.update request. This works fine in the Dynamic Island and when after switching apps and swiping down to see the lock screen without locking the phone. However, once I lock the phone, iOS stops updating the Live Activity content, and doesn't resume updates until after the app regains the foreground at least once. Has anyone else encountered this behavior? Is this a setting that I'm missing, or a bug?
Posted
by zabelc.
Last updated
.
Post not yet marked as solved
4 Replies
347 Views
XCode 15.3, watchOS 10.4 SDK Our health-related app is exploring adding an additional feature that would make use of extended runtime sessions to guide users through an activity. A user starts a session by tapping a button on the watchOS app's interface. If a session is already running, pressing that button should cause the current session to invalidate, and a new session should be created so we can start again from the beginning. TLDR: Running new sessions works as expected on the simulator but not on a real device. Why? We currently have a barebones implementation that includes logging in the extended runtime session delegate functions, on the button push, and when the new session is about to start. Here's what our logging output for the simulator looks like when we try to invalidate the current session and run another one: Invalidating extended session Optional("<WKExtendedRuntimeSession: 0x60000262d7a0> state=2, sessionID=4894EB1D-96F7-4921-8263-378E304E719F, expirationDate=2024-03-20 13:01:10 +0000") at 12:01:21 PM Extended runtime session <WKExtendedRuntimeSession: 0x60000262d7a0> state=3, sessionID=4894EB1D-96F7-4921-8263-378E304E719F, expirationDate=2024-03-20 13:01:10 +0000 invalidated for reason WKExtendedRuntimeSessionInvalidationReason(rawValue: 0) with error nil at 12:01:21 PM Requesting new extended session in application state 0 at 12:01:22 PM Starting new extended session Optional("<WKExtendedRuntimeSession: 0x600002644540> state=0, sessionID=B34ECCFF-A0DA-45C1-9FD1-FD0F335FBE02, expirationDate=(null)") in application state 0 at 12:01:23 PM Did start extended session <WKExtendedRuntimeSession: 0x600002644540> state=2, sessionID=B34ECCFF-A0DA-45C1-9FD1-FD0F335FBE02, expirationDate=2024-03-20 13:01:23 +0000 at 12:01:23 PM As you can see, we successfully invalidate a running session, a button press requests a new one, and then we create and start a new runtime session instance which ends in the running (2) state. We can repeat this over and over without issue. The exact same code running on an actual device produces this: Invalidating extended session Optional(\"<WKExtendedRuntimeSession: 0x15db5750> state=2, sessionID=889FE2E8-0FDA-4826-9094-4D48094FEBED, expirationDate=2024-03-20 12:53:55 +0000\") at 11:56:04AM Extended runtime session <WKExtendedRuntimeSession: 0x15db5750> state=3, sessionID=889FE2E8-0FDA-4826-9094-4D48094FEBED, expirationDate=2024-03-20 12:53:55 +0000 invalidated for reason WKExtendedRuntimeSessionInvalidationReason(rawValue: 0) with error nil at 11:56:04AM Requesting new extended session in application state 0 at 11:56:05AM Starting new extended session Optional(\"<WKExtendedRuntimeSession: 0x15e0aba0> state=0, sessionID=D5020337-D20B-48BE-B2EE-EE44BE580AEC, expirationDate=(null)\") in application state 0 at 11:56:06AM Extended runtime session <WKExtendedRuntimeSession: 0x15e0aba0> state=3, sessionID=D5020337-D20B-48BE-B2EE-EE44BE580AEC, expirationDate=(null) invalidated for reason WKExtendedRuntimeSessionInvalidationReason(rawValue: 1) with error nil at 11:56:06AM The difference is in the last line: starting the session was unsuccessful and it was immediately invalidated, with the reason WKExtendedRuntimeSessionInvalidationReason(rawValue: 1) which maps to the enum sessionInProgress. This is surprising, since we just invalidated and dereferenced the old runtime session. What else can we do to tear down this session? In fact, no other extended runtime sessions can be created and started successfully until the device is rebooted. One note is that on the simulator we do get the following warning right after invalidating the running session: -[WKExtendedRuntimeSession _invalidateWithError:]_block_invoke_2:527: Got error Error Domain=com.apple.CarouselServices.SessionErrorDomain Code=3 "Session not running <CSLSession: 0x600003b4eca0; pid: 8833; dismissed: NO; ended: YES; duration: 3600.0; autoEnd: NO; launchable: NO; mutuallyExclusive: YES; managed: YES; persisted: NO; requiresFGActiveInitiation: YES; lastForeground: 2024-03-20 12:01:21 +0000> |CSLSSession = { | sessionID: 4894EB1D-96F7-4921-8263-378E304E719F; bundleID: com.bundle; type: "physical therapy"; running: NO; paused: NO; expirationDate: 2024-03-20 13:01:10 +0000; supportsAOT: NO; lastStartWasScheduled: NO; remote: NO; |}" UserInfo={NSLocalizedDescription=Session not running <CSLSession: 0x600003b4eca0; pid: 8833; dismissed: NO; ended: YES; duration: 3600.0; autoEnd: NO; launchable: NO; mutuallyExclusive: YES; managed: YES; persisted: NO; requiresFGActiveInitiation: YES; lastForeground: 2024-03-20 12:01:21 +0000> |CSLSSession = { | sessionID: 4894EB1D-96F7-4921-8263-378E304E719F; bundleID: com.bundle; type: "physical therapy"; running: NO; paused: NO; expirationDate: 2024-03-20 13:01:10 +0000; supportsAOT: NO; lastStartWasScheduled: NO; remote: NO; |}} This appears to be thrown by some part of Carousel, for which no public documentation exists, and it clearly doesn't disrupt the expected behavior on the simulator. I don't know if this is being thrown on the device, since our logging wouldn't be able to pick it up. Please let me know if we are approaching this incorrectly or if there are any known solutions to this issue.
Posted
by bcappdev.
Last updated
.
Post not yet marked as solved
0 Replies
151 Views
Hello, I'm developing a voice communication App using Livekit SDK. Everything works fine in the foreground, AudioSession is activated and audio transmitted. However, I would like to add a feature, I would like my app to receive audio even when it's in background or terminated. I know I can run code when the App is in that state by sending a background push notification, but the only thing that is not working in that case is the AudioSession activation. It fails with error "Session activation failed", no more clues. I tried every combination of category and mode, but no success. Bacground modes in XCode have been activated: -Audio, AirPlay, and Picture in Picture -Background Processing Is this a limit of Livekit? I would be grateful if someone can point me into the right direction.
Posted
by enrico-g.
Last updated
.
Post not yet marked as solved
4 Replies
203 Views
Can't we set up a network server that constantly runs in the background, even in the applications we will develop for corporate companies? @eskimo
Posted Last updated
.
Post not yet marked as solved
0 Replies
206 Views
hi I recently developed an iOS APP which will advertise in background, as I checked the developer document, device name and service uuid will be ignored when iOS APP goes to background. But I found an APP named Berkanan which can broadcast in backgound with device name and service uuid, and its device name length exceeds 8 bytes, does anyone know how it implements? thanks!
Posted
by Pandak.
Last updated
.
Post not yet marked as solved
0 Replies
278 Views
We have been using the BGTask (specifically a BGProcessingTask) reliably for the last couple of years for our app. Up until now they wake up automatically while the screen is off, the iPad is plugged in, and the app is running (that is, in the background), but never while the screen is on (that is, never when the scenePhase == .active). For the last month or so, I've noticed that they are triggering now while the screen is displayed. How is this possible??? Did something change with a recent version of iOS? It's violating Apple's own documentation, which describes the BGProcessingTask as: "A time-consuming processing task that runs while the app is in the background."
Posted Last updated
.
Post marked as solved
1 Replies
274 Views
I'm working on a app that can communicate, send and receive data from our own MFi scanner. Ideally, this app can receives data and remains communication even when it's in background, but I can only runs a background task for maximum 30 sec. Along with this main app, we also have a keyboard extension as an interface that can publish collected data to other app that user prefers with string format. However, It seems like Apple doesn't allow to implement UIApplication.shared.beginBackgroundTask method in extension class, is there any alternative that worth to try? Also, can I extend app background task time elapsed to at least 30 minutes? If I can get an official response would be great!
Posted
by Rimbaud.
Last updated
.
Post not yet marked as solved
0 Replies
10k Views
I regularly see questions, both here on DevForums and in my Day Job™ at DTS, that are caused by a fundamental misunderstanding of how background execution works on iOS. These come in many different variants, for example: How do I keep my app running continuously in the background? If I schedule a timer, how do I get it to fire when the screen is locked? How do I run code in the background every 15 minutes? How do I set up a network server that runs in the background? How can my app provide an IPC service to another one of my apps while it’s in the background? How can I resume my app in the background if it’s been ‘force quit’ by the user? The short answer to all of these is You can’t. iOS puts strict limits on background execution. Its default behaviour is to suspend your app shortly after the user has moved it to the background; this suspension prevents the process from running any code. There’s no general-purpose mechanism for: Running code continuously in the background Running code at some specific time in the background Running code periodically at a guaranteed interval Resuming in the background in response to a network or IPC request However, iOS does provide a wide range of special-purpose mechanisms for accomplishing specific user goals. For example: If you’re building a music player, use the audio background mode to continue playing after the user has moved your app to the background. If you’re building a timer app, use a local notification to notify the user when your timer has expired. If you’re building a video player app, use AVFoundation’s download support. Keep in mind that the above is just a short list of examples. There are many other special-purpose background execution mechanisms, so you should search the documentation for something appropriate to your needs. IMPORTANT Each of these mechanisms fulfils a specific purpose. Do not attempt to use them for some other purpose. Before using a background API, read clause 2.5.4 of the App Review Guidelines. Additionally, iOS provides some general-purpose mechanisms for background execution: To resume your app in the background in response to an event on your server, use a background notification (aka a ‘silent’ push). For more information, see Pushing background updates to your App. To request a small amount of background execution time to refresh your UI, use BGAppRefreshTaskRequest. To request extended background execution time, typically delivered overnight when the user is asleep, use BGProcessingTaskRequest. To prevent your app from being suspended for a short period of time so that you can complete some user task, use a UIApplication background task. For more information on this, see UIApplication Background Task Notes. To download or upload a large HTTP resource, use an NSURLSession background session. All of these mechanisms prevent you from abusing them to run arbitrary code in the background. As an example, consider the NSURLSession resume rate limiter. For more information about these limitations, and background execution in general, I strongly recommend that you watch WWDC 2020 Session 10063 Background execution demystified. It’s an excellent resource. Specifically, this talk addresses a common misconception about the app refresh mechanism (BGAppRefreshTaskRequest and the older background fetch API). Folks assume that app refresh will provide regular background execution time. That’s not the case. The system applies a range of heuristics to decide which apps get app refresh time and when. This is a complex issue, one that I’m not going to try to summarise here, but the take-home message is that, if you expect that the app refresh mechanism will grant you background execution time, say, every 15 minutes, you’ll be disappointed. In fact, there are common scenarios where it won’t grant you any background execution time at all! Watch the talk for the details. When the user ‘force quits’ an app by swiping up in the multitasking UI, iOS interprets that to mean that the user doesn’t want the app running at all. So: If the app is running, iOS terminates it. iOS also sets a flag that prevents the app from being launched in the background. That flag gets cleared when the user next launches the app manually. This gesture is a clear statement of user intent; there’s no documented way for your app to override the user’s choice. Note In some circumstances iOS will not honour this flag. The exact cases where this happens are not documented and have changed over time. Finally, if you have questions about background execution that aren’t covered by the resources listed here, please open a new thread on DevForums with the details. Tag it appropriately for the technology you’re using; if nothing specific springs to mind, use Background Tasks. Also, make sure to include details about the specific problem you’re trying to solve because, when it comes to background execution, the devil really is in the details. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com" Change history: 2024-03-21 Added a discussion of ‘force quit’. 2023-05-11 Added a paragraph that explains a common misconception about the app refresh mechanism. Made other minor editorial changes. 2021-08-12 Added more entries to the common questions list, this time related to networking and IPC. Made minor editorial changes. 2021-07-26 Extended the statement about what’s not possible to include “running code periodically at a guaranteed interval”. 2021-07-22 First posted.
Posted
by eskimo.
Last updated
.
Post not yet marked as solved
1 Replies
734 Views
I’m working on an independent watchOS app which is primarily designed to to collect and periodically send location updates to a server. The UI features a toggle that allows the user to turn this capability on or off at their discretion. The typical use case scenario would be for the user to turn the toggle on in the morning, put the app in the background and then go about their day. Given the limitations and restrictions regarding background execution on watchOS, in an ideal situation, I would be able to upload the stored location updates about every 15-20 minutes. With an active complication on the watch face, it’s my understanding that this should be possible. I’ve implemented background app refresh and indeed, I do see this reliably being triggered every 15-20 minutes or so. In my handle(_:) method, I process the WKApplicationRefreshBackgroundTask like this: func handle(_ backgroundTasks: Set&lt;WKRefreshBackgroundTask&gt;) { backgroundTasks.forEach { task in switch task { case let appRefreshBackgroundTask as WKApplicationRefreshBackgroundTask: // start background URL session to upload data; watchOS will perform the request in a separate process so that it will continue to run even if our app gets // terminated; when the system is done transferring data, it will call this method again and backgroundTasks will contain an instance of // WKURLSessionRefreshBackgroundTask which will be processed below startBackgroundURLSessionUploadTask() scheduleNextBackgroundAppRefresh() appRefreshBackgroundTask.setTaskCompletedWithSnapshot(false) case let urlSessionTask as WKURLSessionRefreshBackgroundTask: // add urlSessionTask to the pendingURLSessionRefreshBackgroundTasks array so we keep a reference to it; when the system completes the upload and // informs us via a URL session delegate method callback, then we will retrieve urlSessionTask from the pendingURLSessionRefreshBackgroundTasks array // and call .setTaskCompletedWithSnapshot(_:) on it pendingURLSessionRefreshBackgroundTasks.append(urlSessionTask) // create another background URL session using the background task’s sessionIdentifier and specify our extension as the session’s delegate; using the same // identifier to create a second URL session allows the system to connect the session to the upload that it performed for us in another process let configuration = URLSessionConfiguration.background(withIdentifier: urlSessionTask.sessionIdentifier) let _ = URLSession(configuration: configuration, delegate: self, delegateQueue: nil) default: task.setTaskCompletedWithSnapshot(false) } } } And here is how I'm creating and starting the background URL session upload task: func startBackgroundURLSessionUploadTask() { // 1. check to see that we have locations to report; otherwise, just return // 2. serialize the locations into a temporary file // 3. create the background upload task let configuration = URLSessionConfiguration.background(withIdentifier: Constants.backgroundUploadIdentifier) configuration.isDiscretionary = false configuration.sessionSendsLaunchEvents = true let backgroundUrlSession = URLSession(configuration: configuration, delegate: self, delegateQueue: nil) let request: URLRequest = createURLRequest() // this is a POST request let backgroundUrlSessionUploadTask = backgroundUrlSession.uploadTask(with: request, fromFile: tempFileUrl) backgroundUrlSessionUploadTask.countOfBytesClientExpectsToSend = Int64(serializedData.count) // on average, this is ~1.5 KB backgroundUrlSessionUploadTask.countOfBytesClientExpectsToReceive = Int64(50) // approximate size of server response backgroundUrlSessionUploadTask.resume() } Note that I'm not setting the .earliestBeginDate property on the backgroundUrlSessionUploadTask because I'd like the upload to start as soon as possible without any delay. Also, this same class (my WatchKit application delegate) conforms to URLSessionTaskDelegate and I have implemented urlSession(_:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:) and urlSession(_:task:didCompleteWithError:). In my testing (on an actual Apple Watch Ultra running watchOS 9.3.1), I've observed that when the system performs the background app refresh, I always receive a callback to myhandle(_:) method. But when I start the background URL session upload task (in startBackgroundURLSessionUploadTask()), I was expecting that when the upload completes, I'd receive another call to myhandle(_:) method with an instance of WKURLSessionRefreshBackgroundTask but this doesn't seem to happen consistently. Sometimes I do see it but other times, I don't and when I don't, the data doesn't seem to be getting uploaded. On a side note, most of the time, startBackgroundURLSessionUploadTask() gets called as a result of my code handling a background app refresh task. But when the user turns off the toggle in the UI and I stop the location updates, I need to report any stored locations at that time and so I call startBackgroundURLSessionUploadTask() to do that. In that specific case, the upload seems to work 100% of the time but I definitely don't see a callback to my handle(_:) method when this occurs. Am I wrong in expecting that I should always be getting a callback to handle(_:) when a background URL session upload task completes? If so, under what circumstances should this occur? Thanks very much!
Posted
by bmt22033.
Last updated
.
Post not yet marked as solved
1 Replies
752 Views
Hello, Our team is working on a mobile app that uses Nearby Interaction framework (UWB technology) to get real-time high-accuracy ranging information with our third-party UWB-enabled device. So far everything works fine, background sessions included as explained in wwdc2022/10008 video. We are using Bluetooth LE to exchange the UWB parameters as recommended by Apple. Our next goal is to go for a full hands-free configuration of the UWB session. Let's think of an access control use case where UWB is used to measure the distance between the user and the door (credentials exchanged via Bluetooth). What we want to achieve is to start the UWB session without requiring the user to take the phone out of his wallet and open the access control app. What it works for us today is if the user starts the app, then switches off the screen and puts the phone into his pocket. The app is still running in background, so the Bluetooth Scan keeps working, the Bluetooth session starts, the UWB parameters exchanged and the UWB session started in the background, all good. But what if the user killed the app or never started it after reboot? How can we force the app to start from killed state when the BLE / UWB third-party accessory comes into proximity? iBeacon seems like a promising approach, but according to the forums, in iOS 16 the app will not be restarted from killed state. Is this correct? Any idea / suggestion about how to let the OS start our app when the user approaches the BLE / UWB accessory and the app is in killed state? Thanks in advance for your time. Regards.
Posted
by gorka-mk.
Last updated
.
Post not yet marked as solved
1 Replies
306 Views
I'm working on a screen where the goal is for the user to walk for 6 minutes while the app times them and measures the distance walked. I'm using CMPedometer to track the walking distance and a repeating 1-second Timer to count down the time. This works fine as long as the app is in the foreground, but I'd like my user to be able to lock their phone and put it away while they walk. I used UIApplication.shared.beginBackgroundTask, but it doesn't provide enough time. It usually only gives me around 30 seconds. I also tried calling UIApplication.shared.beginBackgroundTask again or calling it once every time the timer ticks, with no better result. How can I accomplish my goal here?
Posted
by flarosa.
Last updated
.
Post not yet marked as solved
2 Replies
303 Views
We are developing a SwiftUI iOS app which behaves as a client to a MJPEG server on a local network. This means that our app should read images sent from a special hardware we are developing using HTTP protocol. The testing is working fine, but our concern is that when the app goes to the background or the phone is locked, the HTTP connection gets closed after one minute. However, we need to have this HTTP connection alive all the time because we are developing a safety monitoring app that should continue its monitoring state even when the phone is locked. We are constantly running object detection AI algorithm on the transferred images from the hardware. Is there a solution to our concern. Could we have example code to a solution?
Posted
by FaridHage.
Last updated
.
Post not yet marked as solved
1 Replies
298 Views
Is there a possibility to develop an iOS app that is connected to an external camera connected through lightning or USB-C port and receives video stream. We need to be able to get this video stream even while the app is in the background or if the phone is locked. We could have the camera connected wirelessly through the lightning port. Is there an available library or a sample app featuring such functionalities. Thanks.
Posted
by FaridHage.
Last updated
.
Post not yet marked as solved
7 Replies
368 Views
I have an app which uses Cocoa Pods to use MQTTClient in connecting to io.adafruit. I use the app to get the various data point stored at io.adafruit and to even send data to adafruit to control a connected unit. I am using .xcworkspace to upload the app on my phone and everything works fine. I am hoping to wake my app every so ofter to get some data points from io.adafruit and if certain conditions exist, I want to send a notification to the user. I have added Background Modes to my app's Signing & Capabilities (Background fetch) and in my INFO I have Permitted background task scheduler identifiers with the String identifier LEVELIT.Refresh. The following is my code I am using in my AppDelegate.h #import <UIKit/UIKit.h> #import <CoreData/CoreData.h> #import <MQTTClient/MQTTClient.h> #import <UserNotifications/UserNotifications.h> @interface AppDelegate: UIResponder <UIApplicationDelegate, UNUserNotificationCenterDelegate,UIAlertViewDelegate,MQTTSessionDelegate>{ MQTTSession *session3; } @property (nonatomic, retain) MQTTSession *session3; @property (nonatomic, strong) NSPersistentContainer *persistentContainer; @end And this is the code I use in my AppDelegate.m #import "AppDelegate.h" #import <BackgroundTasks/BackgroundTasks.h> #import <CloudKit/CloudKit.h> #import <UserNotifications/UserNotifications.h> static NSString* TaskID = @"LEVELIT.refresh"; @interface AppDelegate() @property (nonatomic, retain) NSString *myUserName; @property (nonatomic, retain) NSString *myUserKey; @property (nonatomic, retain) NSString *myTrips; @property (nonatomic, retain) NSString *atrip; @property (nonatomic, retain) NSString *trip; @property (nonatomic, retain) NSString *gettrip; @property(strong) void (^expirationHandler)(void); @end @implementation AppDelegate @synthesize session3,window; - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { if (@available(iOS 13.0, *)) { [[BGTaskScheduler sharedScheduler] registerForTaskWithIdentifier:TaskID usingQueue:nil launchHandler:^(BGAppRefreshTask *task) { [self handleAppRefreshTask:task]; }]; } else { // Fallback on earlier versions } UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter]; [center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert + UNAuthorizationOptionSound) completionHandler:^(BOOL granted, NSError * _Nullable error) { }]; UNNotificationCategory* generalCategory = [UNNotificationCategory categoryWithIdentifier:@"GENERAL" actions:@[] intentIdentifiers:@[] options:UNNotificationCategoryOptionCustomDismissAction]; NSUserDefaults *defaults2 = [NSUserDefaults standardUserDefaults]; if([[[defaults2 dictionaryRepresentation] allKeys] containsObject:@"myUserNameFile"]){ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; self.myUserName = [defaults objectForKey:@"myUserNameFile"]; NSLog(@"myUserName1:%@",self.myUserName); self.myUserKey = [defaults objectForKey:@"myUserKeyFile"]; NSLog(@"myUserKey1:%@",self.myUserKey); } return YES; } } -(void)schedualLocalNotifications{ } - (void)setTaskCompletedWithSuccess:(BOOL)success;{ NSLog(@"Completed"); } - (void)applicationDidEnterBackground:(UIApplication *)application { NSLog(@"Entering background"); if (@available(iOS 13.0, *)) { BGAppRefreshTaskRequest *request = [[BGAppRefreshTaskRequest alloc] initWithIdentifier:TaskID]; request.earliestBeginDate = [NSDate dateWithTimeIntervalSinceNow:15*60]; NSError *error; BOOL success = [[BGTaskScheduler sharedScheduler] submitTaskRequest:request error:&error]; if (success == TRUE) { } } //BREAKPOINT } -(void)handleAppRefreshTask:(BGAppRefreshTask *)task API_AVAILABLE(ios(13.0)){ //do things with task NSLog(@"Process started!"); task.expirationHandler = ^{ NSLog(@"WARNING: expired before finish was executed."); }; MQTTCFSocketTransport *transport = [[MQTTCFSocketTransport alloc] init]; transport.host = @"io.adafruit.com"; transport.port = 1883; self.session3 = [[MQTTSession alloc] init]; self.session3.userName = self.myUserName; self.session3.password = self.myUserKey; self.session3.transport = transport; self.session3.delegate = self; self.session3.keepAliveInterval = 30; // new stuff NSString *feeds = [self.myUserName stringByAppendingString:@"/feeds/"]; self.atrip = [feeds stringByAppendingString:@"trip"]; self.gettrip = [self.atrip stringByAppendingString:@"/get"]; [session3 connectWithConnectHandler:^(NSError *error) { if(!error){ [self.session3 subscribeToTopic:self.atrip atLevel:1 subscribeHandler:^(NSError *error, NSArray *gQoss){ if (error) { NSLog(@"Subscription failed %@", error.localizedDescription); } else { NSLog(@"Subscription sucessfull! Granted Qos: %@", gQoss); NSData* data = [@"0" dataUsingEncoding:NSUTF8StringEncoding]; [self.session3 publishData:data onTopic:self.gettrip retain:NO qos:0 publishHandler:nil]; } }]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(45* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [task setTaskCompletedWithSuccess:YES]; }); self.session3.keepAliveInterval =30; } else {NSLog(@"[connectWithConnectHandler]Error Connect %@", error.localizedDescription);} }]; task.expirationHandler = ^{ NSLog(@"WARNING: expired before finish was executed."); }; } } } //More code here but too much. @end I am not sure about the local notification because the BGAppRefreshTask is not working. I have placed a breakpoint just after the app enters background and the TASK is submitted. Then I type in e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"LEVELIT.refresh"] The app launches in the background but I get an error error: Header search couldn't locate module MQTTClient error: couldn't install checkers, unknown error Message from debugger: Terminated due to signal 9 When I tried running the app without .xcworkspace the project said it couldn't find MQTTClient. But with the workspace everything works fine. Why would launching the app in the background cause the app not to locate the MQTTClient libraries? And how do I work around this problem? Any and all help would be appreciated.
Posted
by gbenna.
Last updated
.
Post not yet marked as solved
2 Replies
459 Views
Currently working on an emergency app paired with a BLE device, and the desired use case is: When the device is triggered, it sends your location to your emergency contacts an API we've built. This flow works while the app is open, but we need things to obviously work while in background (while the phone is sleeping as well, of course, for emergency contexts) From what I've researched and understood, background fetches don't really work, because the intervals are a maximum of about 15 minutes, and iOS will make them less frequent based on app usage, and that window in an emergency situation isn't good enough. Having read around, I've bumped into a couple resources that suggest background bluetooth processing is possible, if I listen for a particular service being advertised, but I haven't been able to make things work so far. I wanted some help on this.
Posted
by JoaoNM.
Last updated
.