axiom-photo-library

0
0
Source

PHPicker, PhotosPicker, photo selection, limited library access, presentLimitedLibraryPicker, save to camera roll, PHPhotoLibrary, PHAssetCreationRequest, Transferable, PhotosPickerItem, photo permissions

Install

mkdir -p .claude/skills/axiom-photo-library && curl -L -o skill.zip "https://mcp.directory/api/skills/download/5769" && unzip -o skill.zip -d .claude/skills/axiom-photo-library && rm skill.zip

Installs to .claude/skills/axiom-photo-library

About this skill

Photo Library Access with PhotoKit

Guides you through photo picking, limited library handling, and saving photos to the camera roll using privacy-forward patterns.

When to Use This Skill

Use when you need to:

  • ☑ Let users select photos from their library
  • ☑ Handle limited photo library access
  • ☑ Save photos/videos to the camera roll
  • ☑ Choose between PHPicker and PhotosPicker
  • ☑ Load images from PhotosPickerItem
  • ☑ Observe photo library changes
  • ☑ Request appropriate permission level

Example Prompts

"How do I let users pick photos in SwiftUI?" "User says they can't see their photos" "How do I save a photo to the camera roll?" "What's the difference between PHPicker and PhotosPicker?" "How do I handle limited photo access?" "User granted limited access but can't see photos" "How do I load an image from PhotosPickerItem?"

Red Flags

Signs you're making this harder than it needs to be:

  • ❌ Using UIImagePickerController (deprecated for photo selection)
  • ❌ Requesting full library access when picker suffices (privacy violation)
  • ❌ Ignoring .limited authorization status (users can't expand selection)
  • ❌ Not handling Transferable loading failures (crashes on large photos)
  • ❌ Synchronously loading images from picker results (blocks UI)
  • ❌ Using PhotoKit APIs when you only need to pick photos (over-engineering)
  • ❌ Assuming .authorized after user grants access (could be .limited)

Mandatory First Steps

Before implementing photo library features:

1. Choose Your Approach

What do you need?

┌─ User picks photos (no library browsing)?
│  ├─ SwiftUI app → PhotosPicker (iOS 16+)
│  └─ UIKit app → PHPickerViewController (iOS 14+)
│  └─ NO library permission needed! Picker handles it.
│
├─ Display user's full photo library (gallery UI)?
│  └─ Requires PHPhotoLibrary authorization
│     └─ Request .readWrite for browsing
│     └─ Handle .limited status with presentLimitedLibraryPicker
│
├─ Save photos to camera roll?
│  └─ Requires PHPhotoLibrary authorization
│     └─ Request .addOnly (minimal) or .readWrite
│
└─ Just capture with camera?
   └─ Don't use PhotoKit - see camera-capture skill

2. Understand Permission Levels

LevelWhat It AllowsRequest Method
No permissionUser picks via system pickerPHPicker/PhotosPicker (automatic)
.addOnlySave to camera roll onlyrequestAuthorization(for: .addOnly)
.limitedUser-selected subset onlyUser chooses in system UI
.authorizedFull library accessrequestAuthorization(for: .readWrite)

Key insight: PHPicker and PhotosPicker require NO permission. The system handles privacy.

3. Info.plist Keys

<!-- Required for any PhotoKit access -->
<key>NSPhotoLibraryUsageDescription</key>
<string>Access your photos to share them</string>

<!-- Required if saving photos -->
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Save photos to your library</string>

Core Patterns

Pattern 1: SwiftUI PhotosPicker (iOS 16+)

Use case: Let users select photos in a SwiftUI app.

import SwiftUI
import PhotosUI

struct ContentView: View {
    @State private var selectedItem: PhotosPickerItem?
    @State private var selectedImage: Image?

    var body: some View {
        VStack {
            PhotosPicker(
                selection: $selectedItem,
                matching: .images  // Filter to images only
            ) {
                Label("Select Photo", systemImage: "photo")
            }

            if let image = selectedImage {
                image
                    .resizable()
                    .scaledToFit()
            }
        }
        .onChange(of: selectedItem) { _, newItem in
            Task {
                await loadImage(from: newItem)
            }
        }
    }

    private func loadImage(from item: PhotosPickerItem?) async {
        guard let item else {
            selectedImage = nil
            return
        }

        // Load as Data first (more reliable than Image)
        if let data = try? await item.loadTransferable(type: Data.self),
           let uiImage = UIImage(data: data) {
            selectedImage = Image(uiImage: uiImage)
        }
    }
}

Multi-selection:

@State private var selectedItems: [PhotosPickerItem] = []

PhotosPicker(
    selection: $selectedItems,
    maxSelectionCount: 5,
    matching: .images
) {
    Text("Select Photos")
}

Advanced Filters (iOS 15+/16+)

// Screenshots only
matching: .screenshots

// Screen recordings only
matching: .screenRecordings

// Slo-mo videos
matching: .sloMoVideos

// Cinematic videos (iOS 16+)
matching: .cinematicVideos

// Depth effect photos
matching: .depthEffectPhotos

// Bursts
matching: .bursts

// Compound filters with .any, .all, .not
// Videos AND Live Photos
matching: .any(of: [.videos, .livePhotos])

// All images EXCEPT screenshots
matching: .all(of: [.images, .not(.screenshots)])

// All images EXCEPT screenshots AND panoramas
matching: .all(of: [.images, .not(.any(of: [.screenshots, .panoramas]))])

Cost: 15 min implementation, no permissions required

Pattern 1b: Embedded PhotosPicker (iOS 17+)

Use case: Embed picker inline in your UI instead of presenting as sheet.

import SwiftUI
import PhotosUI

struct EmbeddedPickerView: View {
    @State private var selectedItems: [PhotosPickerItem] = []

    var body: some View {
        VStack {
            // Your content above picker
            SelectedPhotosGrid(items: selectedItems)

            // Embedded picker fills available space
            PhotosPicker(
                selection: $selectedItems,
                maxSelectionCount: 10,
                selectionBehavior: .continuous,  // Live updates as user taps
                matching: .images
            ) {
                // Label is ignored for inline style
                Text("Select")
            }
            .photosPickerStyle(.inline)  // Embed instead of present
            .photosPickerDisabledCapabilities([.selectionActions])  // Hide Add/Cancel buttons
            .photosPickerAccessoryVisibility(.hidden, edges: .all)  // Hide nav/toolbar
            .frame(height: 300)  // Control picker height
            .ignoresSafeArea(.container, edges: .bottom)  // Extend to bottom edge
        }
    }
}

Picker Styles:

StyleDescription
.presentationDefault modal sheet
.inlineEmbedded in your view hierarchy
.compactSingle row, minimal vertical space

Customization modifiers:

// Hide navigation/toolbar accessories
.photosPickerAccessoryVisibility(.hidden, edges: .all)
.photosPickerAccessoryVisibility(.hidden, edges: .top)  // Just navigation bar
.photosPickerAccessoryVisibility(.hidden, edges: .bottom)  // Just toolbar

// Disable capabilities (hides UI for them)
.photosPickerDisabledCapabilities([.search])  // Hide search
.photosPickerDisabledCapabilities([.collectionNavigation])  // Hide albums
.photosPickerDisabledCapabilities([.stagingArea])  // Hide selection review
.photosPickerDisabledCapabilities([.selectionActions])  // Hide Add/Cancel

// Continuous selection for live updates
selectionBehavior: .continuous

Privacy note: First time an embedded picker appears, iOS shows an onboarding UI explaining your app can only access selected photos. A privacy badge indicates the picker is out-of-process.

Pattern 2: UIKit PHPickerViewController (iOS 14+)

Use case: Photo selection in UIKit apps.

import PhotosUI

class PhotoPickerViewController: UIViewController, PHPickerViewControllerDelegate {

    func showPicker() {
        var config = PHPickerConfiguration()
        config.selectionLimit = 1  // 0 = unlimited
        config.filter = .images    // or .videos, .any(of: [.images, .videos])

        let picker = PHPickerViewController(configuration: config)
        picker.delegate = self
        present(picker, animated: true)
    }

    func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
        picker.dismiss(animated: true)

        guard let result = results.first else { return }

        // Load image asynchronously
        result.itemProvider.loadObject(ofClass: UIImage.self) { [weak self] object, error in
            guard let image = object as? UIImage else { return }

            DispatchQueue.main.async {
                self?.displayImage(image)
            }
        }
    }
}

Filter options:

// Images only
config.filter = .images

// Videos only
config.filter = .videos

// Live Photos only
config.filter = .livePhotos

// Images and videos
config.filter = .any(of: [.images, .videos])

// Exclude screenshots (iOS 15+)
config.filter = .all(of: [.images, .not(.screenshots)])

// iOS 16+ filters
config.filter = .cinematicVideos
config.filter = .depthEffectPhotos
config.filter = .bursts

UIKit Embedded Picker (iOS 17+)

// Configure for embedded use
var config = PHPickerConfiguration()
config.selection = .continuous  // Live updates instead of waiting for Add button
config.mode = .compact  // Single row layout (optional)
config.selectionLimit = 10

// Hide accessories
config.edgesWithoutContentMargins = .all  // No margins around picker

// Disable capabilities
config.disabledCapabilities = [.search, .selectionActions]

let picker = PHPickerViewController(configuration: config)
picker.delegate = self

// Add as child view controller (required for embedded)
addChild(picker)
containerView.addSubview(picker.view)
picker.view.frame = containerView.bounds
picker.didMove(toParent: self)

Updating picker while displayed (iOS 17+):

// Deselect assets by their identifiers
picker.deselectAssets(withIdentifiers: ["assetID1", "assetID2"])

// Reorder assets in selection
picker.moveAsset(withIdentifier: "assetID", afterAssetWithIdentifier: "otherID")
``

---

*Content truncated.*

axiom-ios-build

CharlesWiltgen

Use when ANY iOS build fails, test crashes, Xcode misbehaves, or environment issue occurs before debugging code. Covers build failures, compilation errors, dependency conflicts, simulator problems, environment-first diagnostics.

91

axiom-getting-started

CharlesWiltgen

Use when first installing Axiom, unsure which skill to use, want an overview of available skills, or need help finding the right skill for your situation — interactive onboarding that recommends skills based on your project and current focus

00

axiom-ui-testing

CharlesWiltgen

Use when writing UI tests, recording interactions, tests have race conditions, timing dependencies, inconsistent pass/fail behavior, or XCTest UI tests are flaky - covers Recording UI Automation (WWDC 2025), condition-based waiting, network conditioning, multi-factor testing, crash debugging, and accessibility-first testing patterns

00

axiom-core-spotlight-ref

CharlesWiltgen

Use when indexing app content for Spotlight search, using NSUserActivity for prediction/handoff, or choosing between CSSearchableItem and IndexedEntity - covers Core Spotlight framework and NSUserActivity integration for iOS 9+

00

axiom-vision-diag

CharlesWiltgen

subject not detected, hand pose missing landmarks, low confidence observations, Vision performance, coordinate conversion, VisionKit errors, observation nil, text not recognized, barcode not detected, DataScannerViewController not working, document scan issues

00

axiom-now-playing-carplay

CharlesWiltgen

CarPlay Now Playing integration patterns. Use when implementing CarPlay audio controls, CPNowPlayingTemplate customization, or debugging CarPlay-specific issues.

00

You might also like

flutter-development

aj-geddes

Build beautiful cross-platform mobile apps with Flutter and Dart. Covers widgets, state management with Provider/BLoC, navigation, API integration, and material design.

643969

drawio-diagrams-enhanced

jgtolentino

Create professional draw.io (diagrams.net) diagrams in XML format (.drawio files) with integrated PMP/PMBOK methodologies, extensive visual asset libraries, and industry-standard professional templates. Use this skill when users ask to create flowcharts, swimlane diagrams, cross-functional flowcharts, org charts, network diagrams, UML diagrams, BPMN, project management diagrams (WBS, Gantt, PERT, RACI), risk matrices, stakeholder maps, or any other visual diagram in draw.io format. This skill includes access to custom shape libraries for icons, clipart, and professional symbols.

591705

ui-ux-pro-max

nextlevelbuilder

"UI/UX design intelligence. 50 styles, 21 palettes, 50 font pairings, 20 charts, 8 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app, .html, .tsx, .vue, .svelte. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient."

318398

godot

bfollington

This skill should be used when working on Godot Engine projects. It provides specialized knowledge of Godot's file formats (.gd, .tscn, .tres), architecture patterns (component-based, signal-driven, resource-based), common pitfalls, validation tools, code templates, and CLI workflows. The `godot` command is available for running the game, validating scripts, importing resources, and exporting builds. Use this skill for tasks involving Godot game development, debugging scene/resource files, implementing game systems, or creating new Godot components.

339397

nano-banana-pro

garg-aayush

Generate and edit images using Google's Nano Banana Pro (Gemini 3 Pro Image) API. Use when the user asks to generate, create, edit, modify, change, alter, or update images. Also use when user references an existing image file and asks to modify it in any way (e.g., "modify this image", "change the background", "replace X with Y"). Supports both text-to-image generation and image-to-image editing with configurable resolution (1K default, 2K, or 4K for high resolution). DO NOT read the image file first - use this skill directly with the --input-image parameter.

451339

fastapi-templates

wshobson

Create production-ready FastAPI projects with async patterns, dependency injection, and comprehensive error handling. Use when building new FastAPI applications or setting up backend API projects.

304231

Stay ahead of the MCP ecosystem

Get weekly updates on new skills and servers.