Files
skills/ios-application-dev/references/system-integration.md
shihao 6487becf60 Initial commit: add all skills files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 16:52:49 +08:00

9.5 KiB

System Integration

iOS system integration guide covering permissions, location, sharing, app lifecycle, and haptic feedback.

Permission Requests

Request permissions contextually, not at launch:

import AVFoundation

@objc func openCamera() {
    AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in
        DispatchQueue.main.async {
            if granted {
                self?.showCameraInterface()
            } else {
                self?.showPermissionDeniedAlert()
            }
        }
    }
}

Photo Library

import Photos

func requestPhotoAccess() {
    PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in
        DispatchQueue.main.async {
            switch status {
            case .authorized, .limited:
                self.showPhotoPicker()
            case .denied, .restricted:
                self.showSettingsAlert()
            default:
                break
            }
        }
    }
}

Microphone

func requestMicrophoneAccess() {
    AVAudioSession.sharedInstance().requestRecordPermission { granted in
        DispatchQueue.main.async {
            if granted {
                self.startRecording()
            }
        }
    }
}

Notifications

import UserNotifications

func requestNotificationPermission() {
    UNUserNotificationCenter.current().requestAuthorization(
        options: [.alert, .badge, .sound]
    ) { granted, error in
        DispatchQueue.main.async {
            if granted {
                self.registerForRemoteNotifications()
            }
        }
    }
}

Location Button

For one-time location access without persistent permission:

import CoreLocationUI

class StoreFinderVC: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let locationBtn = CLLocationButton()
        locationBtn.icon = .arrowFilled
        locationBtn.label = .currentLocation
        locationBtn.cornerRadius = 20
        locationBtn.addTarget(self, action: #selector(findNearby), for: .touchUpInside)
        
        view.addSubview(locationBtn)
        locationBtn.snp.makeConstraints { make in
            make.centerX.equalToSuperview()
            make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-24)
        }
    }
}

Core Location

import CoreLocation

class LocationManager: NSObject, CLLocationManagerDelegate {
    private let manager = CLLocationManager()
    
    func requestLocation() {
        manager.delegate = self
        manager.desiredAccuracy = kCLLocationAccuracyBest
        manager.requestWhenInUseAuthorization()
    }
    
    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        switch manager.authorizationStatus {
        case .authorizedWhenInUse, .authorizedAlways:
            manager.requestLocation()
        case .denied:
            showLocationDeniedAlert()
        default:
            break
        }
    }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.last else { return }
        handleLocation(location)
    }
}

Share Sheet

@objc func shareContent() {
    let items: [Any] = [contentURL, contentImage].compactMap { $0 }
    let activityVC = UIActivityViewController(activityItems: items, applicationActivities: nil)
    
    if let popover = activityVC.popoverPresentationController {
        popover.sourceView = shareButton
        popover.sourceRect = shareButton.bounds
    }
    
    present(activityVC, animated: true)
}

Custom Share Items

class ShareItem: NSObject, UIActivityItemSource {
    let title: String
    let url: URL
    
    init(title: String, url: URL) {
        self.title = title
        self.url = url
    }
    
    func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
        return url
    }
    
    func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
        return url
    }
    
    func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String {
        return title
    }
}

Excluding Activities

let activityVC = UIActivityViewController(activityItems: items, applicationActivities: nil)
activityVC.excludedActivityTypes = [
    .addToReadingList,
    .assignToContact,
    .print
]

App Lifecycle

class PlayerViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        NotificationCenter.default.addObserver(
            self, selector: #selector(onBackground),
            name: UIApplication.didEnterBackgroundNotification, object: nil
        )
        NotificationCenter.default.addObserver(
            self, selector: #selector(onForeground),
            name: UIApplication.willEnterForegroundNotification, object: nil
        )
        NotificationCenter.default.addObserver(
            self, selector: #selector(onTerminate),
            name: UIApplication.willTerminateNotification, object: nil
        )
    }
    
    @objc private func onBackground() { 
        saveState()
        pausePlayback()
    }
    
    @objc private func onForeground() { 
        restoreState()
        resumePlayback()
    }
    
    @objc private func onTerminate() {
        saveState()
    }
}

Scene Lifecycle (iOS 13+)

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    func sceneDidBecomeActive(_ scene: UIScene) {
        // Resume tasks
    }
    
    func sceneWillResignActive(_ scene: UIScene) {
        // Pause tasks
    }
    
    func sceneDidEnterBackground(_ scene: UIScene) {
        // Save state
    }
    
    func sceneWillEnterForeground(_ scene: UIScene) {
        // Prepare UI
    }
}

State Preservation

class ViewController: UIViewController {
    override func encodeRestorableState(with coder: NSCoder) {
        super.encodeRestorableState(with: coder)
        coder.encode(currentItemID, forKey: "currentItemID")
    }
    
    override func decodeRestorableState(with coder: NSCoder) {
        super.decodeRestorableState(with: coder)
        if let itemID = coder.decodeObject(forKey: "currentItemID") as? String {
            loadItem(itemID)
        }
    }
}

Haptic Feedback

func onTaskComplete() {
    UINotificationFeedbackGenerator().notificationOccurred(.success)
}

func onError() {
    UINotificationFeedbackGenerator().notificationOccurred(.error)
}

func onWarning() {
    UINotificationFeedbackGenerator().notificationOccurred(.warning)
}

func onSelection() {
    UISelectionFeedbackGenerator().selectionChanged()
}

func onImpact() {
    UIImpactFeedbackGenerator(style: .medium).impactOccurred()
}

Impact Styles

Style Usage
.light Subtle feedback, small UI changes
.medium Standard feedback, button presses
.heavy Strong feedback, significant actions
.soft Gentle feedback, background changes
.rigid Sharp feedback, collisions

Prepared Feedback

For time-critical haptics, prepare the generator in advance:

class DraggableView: UIView {
    private let impactGenerator = UIImpactFeedbackGenerator(style: .medium)
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        impactGenerator.prepare()
    }
    
    func didSnapToPosition() {
        impactGenerator.impactOccurred()
    }
}

Deep Linking

URL Schemes

// In AppDelegate or SceneDelegate
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
    guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
        return false
    }
    
    switch components.host {
    case "item":
        if let itemID = components.queryItems?.first(where: { $0.name == "id" })?.value {
            navigateToItem(itemID)
            return true
        }
    default:
        break
    }
    
    return false
}
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
    guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
          let url = userActivity.webpageURL else {
        return false
    }
    
    return handleUniversalLink(url)
}

Background Tasks

import BackgroundTasks

func registerBackgroundTasks() {
    BGTaskScheduler.shared.register(
        forTaskWithIdentifier: "com.app.refresh",
        using: nil
    ) { task in
        self.handleAppRefresh(task: task as! BGAppRefreshTask)
    }
}

func scheduleAppRefresh() {
    let request = BGAppRefreshTaskRequest(identifier: "com.app.refresh")
    request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
    
    do {
        try BGTaskScheduler.shared.submit(request)
    } catch {
        print("Could not schedule app refresh: \(error)")
    }
}

func handleAppRefresh(task: BGAppRefreshTask) {
    scheduleAppRefresh()
    
    let operation = RefreshOperation()
    
    task.expirationHandler = {
        operation.cancel()
    }
    
    operation.completionBlock = {
        task.setTaskCompleted(success: !operation.isCancelled)
    }
    
    OperationQueue.main.addOperation(operation)
}

UIKit, Core Location, and Apple are trademarks of Apple Inc.