Working with Device Capabilities (Camera, Location, Notifications)

iOS Development — Architecture & Data

Chapter 8 · Working with Device Capabilities

Every capability this chapter covers — photos, location, notifications — touches something genuinely private to the user. Apple's own real platform enforces that seriously: most of them require an explicit, real permission prompt, and a real, specific reason string declared in advance.

Real, Required Privacy Descriptions

Before requesting access to almost any sensitive real capability, Xcode's own Info tab (or a direct edit to Info.plist) needs a real, specific usage-description key — the actual message shown to the user in the real system permission prompt.

A Real, Hard Requirement
Requesting a real, protected capability with no matching usage-description key doesn't produce a graceful error — it's a genuine, immediate app crash at the moment of the request. This is Apple's own real, deliberate enforcement, not an edge case to handle defensively.

Photos: PhotosPicker — A Real, Notable Exception

Introduced in iOS 16, SwiftUI's own real PhotosPicker is the current, idiomatic way to let a user choose a photo.

import PhotosUI struct TaskPhotoPicker: View { @State private var selectedItem: PhotosPickerItem? @State private var selectedImage: Image? var body: some View { VStack { selectedImage?.resizable().scaledToFit() PhotosPicker(selection: $selectedItem, matching: .images) { Text("Attach a Photo") } .onChange(of: selectedItem) { Task { if let data = try? await selectedItem?.loadTransferable(type: Data.self), let uiImage = UIImage(data: data) { selectedImage = Image(uiImage: uiImage) } } } } } }
A Real, Genuinely Notable Exception
PhotosPicker needs no real Info.plist privacy key at all — it runs as a separate, real, sandboxed system process; the app itself never gains broad photo-library access, only the specific real image(s) the user actually picks. This is a deliberate, real design choice by Apple, not an oversight — a genuine, useful exception to this chapter's own opening rule.

Location: CLLocationManager

import CoreLocation final class LocationProvider: NSObject, CLLocationManagerDelegate { private let manager = CLLocationManager() override init() { super.init() manager.delegate = self manager.requestWhenInUseAuthorization() } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { // real, delegate-based updates arrive here } }
CapabilityReal Required Info.plist Key
Location (When in Use)NSLocationWhenInUseUsageDescription
Photos (PhotosPicker)None required
NotificationsNone required — authorized via a real runtime request instead
A Real, Genuine Note on Async Alternatives
CLLocationManagerDelegate's callback-based pattern remains the real, foundational way location updates are delivered. Newer, async-sequence-based alternatives exist on more recent real OS versions — worth investigating directly against Apple's own current documentation before adopting one, per this course's own recurring discipline of verifying fast-moving APIs rather than assuming.

Notifications: UNUserNotificationCenter

import UserNotifications func requestNotificationPermission() async { let center = UNUserNotificationCenter.current() let granted = try? await center.requestAuthorization(options: [.alert, .sound, .badge]) print("Notifications authorized: \(granted ?? false)") } func scheduleReminder(for task: Task) { let content = UNMutableNotificationContent() content.title = "Task Reminder" content.body = task.title let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 3600, repeats: false) let request = UNNotificationRequest(identifier: task.id.uuidString, content: content, trigger: trigger) UNUserNotificationCenter.current().add(request) }
A Real, Practical Ordering Rule
requestAuthorization should be called once, real and early — typically at a natural moment in the app's own flow, not immediately on first launch before the user has any context for why it's being asked. A denied real prompt can only be reversed by the user, manually, in Settings — there's no real way for the app to ask again.

Hands-On Exercises

Exercise 1

Write a real func requestLocationAndNotify() async function that requests notification authorization via UNUserNotificationCenter, and — only if granted is true — schedules a real local notification confirming permission was granted.

📄 View solution
Exercise 2

Explain, in your own words, what would actually happen at runtime if NSLocationWhenInUseUsageDescription were missing from Info.plist and requestWhenInUseAuthorization() were called anyway.

📄 View solution
Exercise 3

Explain, in your own words, why PhotosPicker needing no Info.plist key at all is a genuine, deliberate privacy design choice, rather than an inconsistency or an oversight compared to location and camera access.

📄 View solution

Chapter 8 Quick Reference

  • Most sensitive real capabilities need a real, specific Info.plist usage-description key — missing one causes a genuine crash, not a graceful error
  • PhotosPicker (iOS 16) needs no privacy key at all — a real, deliberate exception, since it runs as a separate sandboxed system process
  • CLLocationManager's real requestWhenInUseAuthorization() + CLLocationManagerDelegate pattern requires NSLocationWhenInUseUsageDescription
  • UNUserNotificationCenter.requestAuthorization(options:) — real, current async permission request; a denied prompt can only be reversed manually in Settings
  • A real local notification is built from UNMutableNotificationContent + a trigger + UNNotificationRequest, added via UNUserNotificationCenter.current().add(request)