Hi there! Welcome to AB Dev Hub. Let’s talk about iOS Background Modes—the features that let your app do more while it’s not active. We’ll keep it simple and clear so you can start using them right away.
What Happens When an App Goes to the Background?
When a user minimizes your app or locks their device, iOS moves your app to the background.
Without background modes, the app pauses and stops running code. But with iOS 4, Apple introduced ways to keep apps working in the background. There are 11 modes you can use.
Audio, AirPlay, and Picture in Picture
This mode lets your app play music, share video with AirPlay, or use Picture in Picture. No extra code needed if you use the built-in controllers.
Just for an example of how to use PiP:
import AVKit
let player = AVPlayer(url: videoURL)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
if AVPictureInPictureController.isPictureInPictureSupported() {
let pipController = AVPictureInPictureController(playerLayer: playerViewController.playerLayer!)
// Start or stop PiP as needed
}
Location Updates
Want your app to track location even when it’s closed? Use this mode. But first, ask for the user’s permission.
You can request location access with this code:
private func checkAuthorization() {
switch locationManager.authorizationStatus {
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
locationManager.requestAlwaysAuthorization()
case .authorizedAlways:
locationManager.startUpdatingLocation()
case .authorizedWhenInUse:
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
default:
break
}
}
And handle updates like this:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let currentLocation = locations.first {
print(currentLocation)
updateRegion(location: currentLocation)
}
}
Background Fetch and Background Processing
Background Fetch (Before iOS 13)
- This is the older system from iOS 7 and up.
- It wakes your app occasionally to fetch data.
- You enable it by adding
fetch
to UIBackgroundModes in your Info.plist and implementingperformFetchWithCompletionHandler
in AppDelegate. - The system controls when your app gets time, usually up to 30 seconds.
App Refresh Tasks (iOS 13+)
- They’re part of the BackgroundTasks framework.
- Use
BGAppRefreshTask
for short tasks, similar to Background Fetch. - You usually have about 30 seconds to update data.
- Good for quick checks, like pulling in new messages or notifications.
Processing Tasks (iOS 13+)
- Also from the BackgroundTasks framework.
-
BGProcessingTask
allows more time when the device is idle or charging. - It’s handy for heavier tasks, like database cleanup or large downloads.
- The system can still stop the task if conditions change.
How They Differ
Task Type | Framework | Time Allowed | When It Runs | Ideal Use Case |
---|---|---|---|---|
Background Fetch | UIKit | ~30 seconds | System decides | Quick data refreshes |
BGAppRefreshTask | BackgroundTasks | ~30 seconds | System decides | Quick updates (iOS 13+) |
BGProcessingTask | BackgroundTasks | Potentially longer | Idle/charging | Heavy processing, large files |
Registering and Scheduling
-
In Xcode
- Select your app Target.
- Go to Signing & Capabilities.
- Add Background Modes.
- Check Background Fetch or Background Processing as needed.
-
In Info.plist
- Add
BGTaskSchedulerPermittedIdentifiers
. - List each task identifier (like
"com.example.myApp.refresh"
).
- Add
-
In Your Code
- Import
BackgroundTasks
. -
Register tasks in AppDelegate or SceneDelegate:
BGTaskScheduler.shared.register( forTaskWithIdentifier: "com.example.myApp.refresh", using: nil ) { task in self.handleAppRefresh(task: task as! BGAppRefreshTask) }
- Import
-
Scheduling
- Create a
BGAppRefreshTaskRequest
orBGProcessingTaskRequest
. - Set
earliestBeginDate
if you want a delay. - Submit it with
BGTaskScheduler.shared.submit(request)
.
- Create a
Handling Tasks
BGAppRefreshTask
func handleAppRefresh(task: BGAppRefreshTask) {
scheduleAppRefresh() // reschedule
let operation = MyFetchOperation()
task.expirationHandler = {
operation.cancel()
}
operation.completionBlock = {
task.setTaskCompleted(success: !operation.isCancelled)
}
OperationQueue().addOperation(operation)
}
- You have about 30 seconds.
BGProcessingTask
func handleProcessingTask(task: BGProcessingTask) {
scheduleProcessingTask() // reschedule
let operation = MyLongRunningOperation()
task.expirationHandler = {
operation.cancel()
}
operation.completionBlock = {
task.setTaskCompleted(success: !operation.isCancelled)
}
OperationQueue().addOperation(operation)
}
- You may have more time, but the task can still end early if conditions change.
Testing
- Xcode > Debug > Simulate Background Fetch (for the old style).
- Xcode > Debug > Simulate Background Tasks (for the new BGTaskScheduler).
-
You can also run these commands in the debugger:
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.example.myApp.refresh"]
```bash
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.example.myApp.processing"]
```
But note, _simulateLaunchForTaskWithIdentifier:
is a private API. It may not always work. Using Xcode’s Debug menu is safer.
Summary
- Background Fetch is the older approach for quick data updates.
- BGAppRefreshTask and BGProcessingTask use the new BackgroundTasks framework.
- Always register your tasks in Info.plist and in code.
- Test with Xcode’s debug options or the console if needed.
Voice over IP (VoIP)
This mode lets apps handle internet calls in the background. When a VoIP push arrives, your app wakes up to connect the call.
- This mode is for apps that make or receive calls over the internet.
- When your app has VoIP mode, it can wake up to handle incoming calls even if it’s in the background.
- On iOS, you usually receive a VoIP push notification from your server.
- That notification tells your app to ring the user’s device or start the call.
- Apple wants VoIP apps to show calls through CallKit, which makes them look like regular phone calls.
Steps to Enable VoIP Mode
-
Enable Background Modes in Signing & Capabilities:
- Add Background Modes.
- Check Voice over IP.
-
Use Push Notifications:
- Have your server send a VoIP push (type “voip”) when an incoming call starts.
-
Use CallKit (Recommended by Apple):
- When you get the VoIP push, show the system call UI with CallKit.
- This gives a consistent user experience.
How it works
- VoIP apps need to respond to calls instantly, even if the app is in the background.
- A normal push might not wake the app fast enough, but a VoIP push does.
Tips
- Apple wants you to use VoIP pushes only for calls. Don’t use them to keep your app running in the background for non-call purposes.
- Make sure you end your call or remove the app from the “active call” state when it’s done, so the system knows it can sleep again.
Summary
- VoIP mode lets apps answer internet calls in the background.
- You set it in Background Modes and use VoIP push notifications.
- CallKit helps display a familiar call screen to the user.
- Stick to call-related functions, or Apple may reject your app.
External Accessory Communication
If your app connects to certified devices (like Bluetooth speakers or USB gadgets), this mode keeps communication going in the background.
Bluetooth LE Accessories
With this mode, your app can:
- Receive data from Bluetooth devices.
- Act as a Bluetooth device and send data.
Here’s how to scan for devices:
let SERVICE_UUID = CBUUID(string: "0000ff0-0000-0000-0000-00405f9b99fb")
centralManager.scanForPeripherals(
withServices: [SERVICE_UUID],
options: [CBCentralManagerScanOptionAllowDuplicatesKey: true]
)
Remote Notifications
Silent push notifications wake your app to fetch new data. You have 30 seconds to process them. Just remember:
- They don’t always arrive. (Apple does not guarantee it at all)
- They’re limited in Low Power Mode.
Handle them like this:
func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
do {
let data = try await fetchSomeData()
completionHandler(data == nil ? .noData : .newData)
} catch {
completionHandler(.failed)
}
}
Push to Talk
In iOS 16, Apple introduced Push to Talk. It lets users send real-time voice messages, like a walkie-talkie. When a push arrives, your app wakes up and plays the audio.
How It Works
- Your server sends a special push notification.
- When that push arrives, your app wakes up to play or record voice.
- It feels instant, like pressing a button on a real walkie-talkie.
When to use It
- It’s good for quick voice chats where typing is slow.
- It’s more personal than a text message.
Tips
- Make sure you handle these pushes carefully, so the app can wake up fast.
- Keep your audio handling simple—no long recordings.
Summary
- Push to Talk adds real-time voice messaging.
- It’s like a walkie-talkie for iOS.
- When a push arrives, your app activates to play or record.
Nearby Interactions
This mode uses Ultra Wideband (UWB) technology to communicate with nearby devices. It’s great for games and location-based apps. You’ll need pre-paired Bluetooth accessories to use it in the background.
- It’s useful for location-based games, device tracking, and real-time peer-to-peer apps.
- In the background, you need pre-paired Bluetooth accessories to keep the session active.
How It Works
- Import the NearbyInteraction framework.
- Create an NISession.
- Set a delegate to respond to updates.
- Start the session with a valid NINearbyPeerConfiguration or other configs.
Basic Code Example
import NearbyInteraction
import CoreBluetooth
class NearbyManager: NSObject, NISessionDelegate {
private var niSession: NISession?
override init() {
super.init()
niSession = NISession()
niSession?.delegate = self
}
func startNearbySession() {
// Example config for a peer-to-peer session
guard let tokenData = fetchSharedToken() else { return }
let config = NINearbyPeerConfiguration(peerToken: tokenData)
niSession?.run(config)
}
func session(_ session: NISession, didUpdate nearbyObjects: [NINearbyObject]) {
// Handle real-time distance/angle updates
guard let firstObject = nearbyObjects.first else { return }
print("Distance: \(firstObject.distance ?? 0), Direction: \(firstObject.direction)")
}
func session(_ session: NISession, didInvalidateWith error: Error) {
// Handle errors or session invalidation
print("Session invalidated: \(error.localizedDescription)")
}
private func fetchSharedToken() -> NIDiscoveryToken? {
// Typically retrieved from another device or server
return nil
}
}
Background Use
- By default, Nearby Interaction stops when the app goes to the background.
- If you have a Bluetooth accessory that’s paired, you can keep tracking in the background.
- You still have to enable any necessary background modes in Xcode (like Uses Bluetooth LE Accessories).
Tips
- Test with two UWB-capable devices (like recent iPhones).
- Make sure Bluetooth is on.
- Show clear prompts so users know when you’re scanning for nearby devices.
Summary
- Nearby Interactions uses UWB for close-range tracking.
- You need a paired accessory for background use.
- Create an NISession, set the delegate, and start with a configuration.
Hey there, developers! 👨💻
If you found today’s article helpful, consider giving a little back to help this project thrive. Here’s how you can show your support:
🌟 Follow me on these platforms:
Each follow makes a huge difference—it connects us with more learners like you and fuels our mission to create high-quality, beginner-friendly content.
☕ Buy Me a Coffee
Want to go the extra mile? You can support me through Buy me a coffee. Your generosity directly contributes to creating new tutorials, lessons, and resources to help aspiring developers like you master Swift. Every bit of support is deeply appreciated!
That’s it! These modes make your app more useful, even when it’s not on the screen. Got questions? Let’s chat in the comments. Thanks for reading!
Top comments (0)