v0.3.0 — PhotoPicker SwiftUI wrapper
The PhotosUI bridge. Cross-platform SwiftUI wrapper around `PHPickerViewController` that returns directly into kadr clip types — bypasses the manual `PHAsset` round-trip for the common "user picks N items, build a video" flow.
What's new
import SwiftUI
import Kadr
import KadrPhotos
struct ContentView: View {
@State private var picked: [PhotoPickerResult] = []
@State private var showPicker = false
@State private var clips: [any Clip] = []
var body: some View {
Button(\"Pick from Photos\") { showPicker = true }
.sheet(isPresented: \$showPicker) {
PhotoPicker(
selection: \$picked,
configuration: .init(selectionLimit: 5, filter: .any)
)
}
.task(id: picked.map(\\.assetIdentifier)) {
clips = (try? await PhotosClipResolver.clips(from: picked)) ?? []
}
}
}- `PhotoPicker(selection:configuration:)` — UIKit on iOS / visionOS, AppKit on macOS via `UIViewControllerRepresentable` / `NSViewControllerRepresentable`.
- `PhotoPickerResult` — `Sendable` + `Equatable` + `Identifiable`. Wraps `assetIdentifier`; `@MainActor resolveAsset()` returns the `PHAsset`.
- `PhotoPicker.Configuration` — selectionLimit, filter, preferredAssetRepresentationMode. Sensible defaults.
- `PhotoPicker.Filter` — `.images` / `.videos` / `.livePhotos` / `.any`.
- `PhotoPicker.AssetRepresentationMode` — `.automatic` / `.current` / `.compatible`.
- `PhotosClipResolver.clip(from:)` + `clips(from:)` — convenience that dispatches on `PHAsset.mediaType` and returns `any Clip`. `CMTime` and `TimeInterval` overloads.
Compatibility
- Requires kadr ≥ 0.9.2 (unchanged).
- iOS 16+ / macOS 13+ / visionOS 1+. tvOS still excluded.
- Pure additive — every v0.2 call site compiles unchanged.
- The picker doesn't require library authorization to display — but `resolveAsset()` and `clip(from:)` do (same v0.1 contract).
Tests
Suite: 21 → 36 (+15).
What's next
- v0.4.0 — PHAsset metadata exposure (creation date / location / EXIF) + direct asset → `ImageOverlay` / `StickerOverlay` helpers
Full notes in CHANGELOG.md.