7.5 KiB
7.5 KiB
iOS-Specific Features
Detailed implementation guides for iOS platform features including Siri Shortcuts, background modes, permissions, lifecycle management, and persistent storage.
Siri Shortcuts Integration
Enable voice-activated tracking with App Intents (iOS 16+):
// StartSleepIntent.swift
import AppIntents
@available(iOS 16, *)
struct StartSleepIntent: AppIntent {
static var title: LocalizedStringResource = "Start Sleep"
static var description = IntentDescription("Start Sleep Tracking")
func perform() async throws -> some IntentResult {
NotificationCenter.default.post(name: .startSleep, object: nil)
return .result()
}
}
// StopSleepIntent.swift
@available(iOS 16, *)
struct StopSleepIntent: AppIntent {
static var title: LocalizedStringResource = "Stop Sleep"
static var description = IntentDescription("Stop Sleep Tracking")
func perform() async throws -> some IntentResult {
NotificationCenter.default.post(name: .stopSleep, object: nil)
return .result()
}
}
// Notification extensions
extension Notification.Name {
static let startSleep = Notification.Name("startSleep")
static let stopSleep = Notification.Name("stopSleep")
}
Handling Shortcuts in Views
struct SleepTrackingView: View {
@StateObject private var viewModel = SleepTrackingViewModel()
var body: some View {
// ... view content ...
.onReceive(NotificationCenter.default.publisher(for: .startSleep)) { _ in
if !viewModel.isTracking {
startTracking()
}
}
.onReceive(NotificationCenter.default.publisher(for: .stopSleep)) { _ in
if viewModel.isTracking {
stopTracking()
}
}
}
}
Users can then say: "Hey Siri, start sleep" or "Hey Siri, stop sleep"
Background Audio Mode
Configure background audio to maintain tracking during sleep.
Info.plist Configuration
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
Implementation Notes
- iOS automatically maintains background audio session during tracking
- App remains active in background while microphone is in use
- User sees audio indicator (red bar/pill) showing active recording
- No additional code needed beyond Info.plist configuration
- System handles audio session management automatically
Best Practices
- Inform users why the app needs background audio mode
- Display clear status indicators when tracking is active
- Handle audio interruptions gracefully (phone calls, other apps)
- Test background behavior thoroughly on physical devices
Microphone Permission Handling
Request and handle microphone permission properly:
import AVFoundation
func requestMicrophonePermission() async -> Bool {
switch AVAudioSession.sharedInstance().recordPermission {
case .granted:
return true
case .denied:
return false
case .undetermined:
return await AVAudioSession.sharedInstance().requestRecordPermission()
@unknown default:
return false
}
}
// Usage in SwiftUI
Button("Start Tracking") {
Task {
let hasPermission = await requestMicrophonePermission()
if hasPermission {
startTracking()
} else {
showPermissionAlert = true
}
}
}
Permission Alert Handling
struct PermissionAlert: ViewModifier {
@Binding var showAlert: Bool
func body(content: Content) -> some View {
content
.alert("Microphone Access Required", isPresented: $showAlert) {
Button("Open Settings") {
if let url = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(url)
}
}
Button("Cancel", role: .cancel) {}
} message: {
Text("Please enable microphone access in Settings to track sleep.")
}
}
}
Checking Permission Status
func checkMicrophonePermission() -> Bool {
let status = AVAudioSession.sharedInstance().recordPermission
return status == .granted
}
App Lifecycle Management
Handle app state transitions gracefully:
struct SleepTrackingView: View {
@Environment(\.scenePhase) private var scenePhase
var body: some View {
// ... view content ...
.onChange(of: scenePhase) { newPhase in
switch newPhase {
case .active:
print("App is active")
// Refresh UI state if needed
case .inactive:
print("App is inactive")
// Prepare for potential backgrounding
case .background:
// Tracking continues in background with audio mode
print("App is in background")
// Minimal operations only
@unknown default:
break
}
}
}
}
Advanced Lifecycle Handling
class AppLifecycleObserver: ObservableObject {
@Published var isActive = true
private var cancellables = Set<AnyCancellable>()
init() {
NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)
.sink { [weak self] _ in
self?.handleEnterBackground()
}
.store(in: &cancellables)
NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)
.sink { [weak self] _ in
self?.handleEnterForeground()
}
.store(in: &cancellables)
}
private func handleEnterBackground() {
isActive = false
// Save state, reduce operations
}
private func handleEnterForeground() {
isActive = true
// Refresh state, resume operations
}
}
Persistent Storage with AppStorage
Store configuration persistently across app launches:
struct SleepTrackingView: View {
@AppStorage("sleepapp+apikey") private var apiKey = ""
@AppStorage("sleepapp+userid") private var userId = ""
@AppStorage("sleepapp+baseurl") private var baseUrl = ""
// Values automatically persist across app launches
// Uses UserDefaults under the hood
}
Custom Storage Keys
extension String {
static let apiKeyStorage = "sleepapp+apikey"
static let userIdStorage = "sleepapp+userid"
static let baseUrlStorage = "sleepapp+baseurl"
}
struct SleepTrackingView: View {
@AppStorage(.apiKeyStorage) private var apiKey = ""
@AppStorage(.userIdStorage) private var userId = ""
@AppStorage(.baseUrlStorage) private var baseUrl = ""
}
Advanced Persistent Storage
class PersistentSettings: ObservableObject {
@AppStorage("sleepapp+apikey") var apiKey = ""
@AppStorage("sleepapp+userid") var userId = ""
@AppStorage("sleepapp+baseurl") var baseUrl = ""
@AppStorage("sleepapp+notifications") var notificationsEnabled = false
@AppStorage("sleepapp+lastSessionId") var lastSessionId = ""
func clearAll() {
apiKey = ""
userId = ""
baseUrl = ""
notificationsEnabled = false
lastSessionId = ""
}
}
// Usage
struct SettingsView: View {
@StateObject private var settings = PersistentSettings()
var body: some View {
Form {
TextField("API Key", text: $settings.apiKey)
TextField("User ID", text: $settings.userId)
Toggle("Notifications", isOn: $settings.notificationsEnabled)
}
}
}