photokit

Implement, review, or improve photo picking, camera capture, and media handling in iOS apps using PhotoKit and AVFoundation. Use when working with PhotosPicker, PHPickerViewController, camera capture sessions (AVCaptureSession), photo library access, image loading and display, video recording, or me

By dpearson2699 · 2,759 installs

npx skills add dpearson2699/swift-ios-skills --skill photokit

Source repository · Upstream listing

PhotoKit Modern patterns for photo picking, camera capture, image loading, and media permissions targeting iOS 26+ with Swift 6.3. Patterns are backward compatible to iOS 16 unless noted. See [references/photokit patterns.md](references/photokit patterns.md) for complete picker recipes and [references/camera capture.md](references/camera capture.md) for AVCaptureSession patterns. Contents [PhotosPicker (SwiftUI, iOS 16+)]( photospicker swiftui ios 16) [Privacy and Permissions]( privacy and permissions) [Camera Capture Basics]( camera capture basics) [Image Loading and Display]( image loading and display) [Common Mistakes]( common mistakes) [Review Checklist]( review checklist) [References]( references) PhotosPicker (SwiftUI, iOS 16+) PhotosPicker is the native SwiftUI replacement for UIImagePickerController . It runs out of process, requires no photo library permission for browsing, and supports single or multi selection with media type filtering. Single Selection Multi Selection Media Type Filtering Filter with PHPickerFilter composites to restrict selectable media: Loading Selected Items with Transferable PhotosPickerItem loads content asynchronously via loadTransferable(type:) . Define a Transferable type for automatic decoding: Always load in a Task to avoid blocking the main thread. Handle nil returns and thrown errors the user may select a format that cannot be decoded. Privacy and Permissions Photo Library Access Levels iOS provides two access levels for the photo library. The system automatically presents the limited library picker when an app requests .readWrite access users choose which photos to share. Access Level Description Info.plist Key Add only Write photos to the library without reading NSPhotoLibraryAddUsageDescription Read write Full or limited read access plus write NSPhotoLibraryUsageDescription PhotosPicker requires no permission to browse it runs out of process and only grants access to selected items. Request explicit permission only when you need to read the full library (e.g., a custom gallery) or save photos. Checking and Requesting Photo Library Permission Camera Permission Add NSCameraUsageDescription to Info.plist. Check and request access before configuring a capture session: Handling Denied Permissions When the user denies access, guide them to Settings. Never repeatedly prompt or hide functionality silently. Required Info.plist Keys Key When Required NSPhotoLibraryUsageDescription Reading photos from the library NSPhotoLibraryAddUsageDescription Saving photos/videos to the library NSCameraUsageDescription Accessing the camera NSMicrophoneUsageDescription Recording audio (video with sound) Omitting a required key causes a runtime crash when the permission dialog would appear. Camera Capture Basics Own each capture session in a dedicated controller and serialize configuration, startRunning() , and stopRunning() on the same non main executor. Never mix main actor configuration with detached start/stop tasks: beginConfiguration() / commitConfiguration() and session state changes must not race. The representable view only displays the preview. Minimal Camera Manager Load [Camera Capture](references/camera capture.md) for the serialized controller pattern, photo/video delegates, focus, torch, orientation, and scanning. The critical lifecycle is: 1. Request access outside the session configuration transaction. 2. On the capture executor, call beginConfiguration() and immediately install defer { commitConfiguration() } so every early exit balances the transaction. 3. Add inputs and outputs only after canAddInput / canAddOutput checks. 4. Start or stop on that same executor, then publish UI state on the main actor only after the synchronous call returns. 5. On failure, stop, restore a fresh session fixture, fix the configuration, and rerun authorization, background/foreground, interruption, and capture checks. Camera Preview in SwiftUI Wrap AVCaptureVideoPreviewLayer in a UIViewRepresentable . Override layerClass for automatic resizing: Using the Camera in a View Always call stop() in onDisappear . A running capture session holds the camera exclusively and drains battery. Image Loading and Display AsyncImage for Remote Images AsyncImage does not cache images across view redraws. For production apps with many images, use a dedicated image loading library or URLCache based caching. Downsampling Large Images Load full resolution photos from the library into a display sized CGImage to avoid memory spikes. A 48MP photo can consume over 200 MB uncompressed. Use this whenever displaying user selected photos in lists, grids, or thumbnails. Pass the raw Data from PhotosPickerItem directly to the downsampler before creating a UIImage . Image Rendering Modes Use .original for photos and artwork. Use .template for icons that should adopt the current tint color. Common Mistakes DON'T: Use UIImagePickerController for photo picking. DO: Use PhotosPicker (SwiftUI) or PHPickerViewController (UIKit). Why: UIImagePickerController is legacy API with limited functionality. PhotosPicker runs out of process, supports multi selection, and requires no library permission for browsing. DON'T: Request full photo library access when you only need the user to pick photos. DO: Use PhotosPicker which requires no permission, or request .readWrite and let the system handle limited access. Why: Full access is unnecessary for most pick and use workflows. The system's limited library picker respects user privacy and still grants access to selected items. DON'T: Load full resolution images into memory for thumbnails. DO: Use CGImageSource with kCGImageSourceThumbnailMaxPixelSize to downsample. A 48MP image is over 200 MB uncompressed. DON'T: Block the main thread loading PhotosPickerItem data. DO: Use async loadTransferable(type:) in a Task . DON'T: Forget to stop AVCaptureSession when the view disappears. DO: Call session.stopRunning() in onDisappear or dismantleUIView . DON'T: Assume camera access is granted without checking. DO: Check AVCaptureDevice.authorizationStatus(for: .video) and handle .denied / .restricted . DON'T: Call session.startRunning() on the main thread. DO: Run it on the same dedicated serial executor that owns configuration and stop operations. Why: startRunning() is a synchronous blocking call that can take hundreds of milliseconds while the hardware initializes. DON'T: Create AVCaptureSession inside a UIViewRepresentable . DO: Own the session in a separate @Observable model. Review Checklist [ ] PhotosPicker used instead of deprecated UIImagePickerController [ ] Privacy descriptions in Info.plist for camera/photo library [ ] Loading states handled for async image/video loading [ ] Large images downsampled with CGImageSource before display [ ] Camera session started on background thread; stopped in onDisappear [ ] Permission denial handled with Settings deep link [ ] AVCaptureSession owned by model, not created inside UIViewRepresentable [ ] Media asset types and picker results are Sendable across concurrency boundaries References [references/photokit patterns.md](references/photokit patterns.md) — Picker patterns, media loading, HEIC handling [references/camera capture.md](references/camera capture.md) — AVCaptureSession, photo/video capture, QR scanning [references/image loading caching.md](references/image loading caching.md) — AsyncImage, caching, downsampling [references/av playback.md](references/av playback.md) — AVPlayer, streaming, audio