[iOS/Mac] Fix MediaPicker.CapturePhotoAsync() fails with UnauthorisedAccessException on iOS#34695
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34695Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34695" |
🚦 Gate — Test Verification |
🤖 AI Summary📊 Expand Full Review —
|
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #34695 | Remove EnsureGrantedAsync<Permissions.PhotosAddOnly>() from capture path in PhotoAsync() |
❌ Gate FAILED (no tests) | MediaPicker.ios.cs (5 line deletion) |
Original PR; 1 file, minimal change |
🔧 Fix — Analysis & Comparison
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | try-fix (opus-4.6) | Wrap check in try- swallows PermissionException PASS (283/283 unit tests) | MediaPicker.ios.cs (+10/-1) |
Swallowing exceptions is a code smell; test limitation: unit tests target netstandard | |
| 2 | try-fix (sonnet-4.6) | Move into video-capture-only path | PASS (283/283 unit tests) | MediaPicker.ios.cs (+4/-5) |
Still video capture via UIImagePickerController also doesn't save to gallery |
| 3 | try-fix (gpt-5.3-codex) | Status-gated: CheckStatusAsync first, only enforce if Granted/Limited PASS (283/283 unit tests) | MediaPicker.ios.cs (+2/-1) |
Still requires permission if status happens to be Granted; doesn't fix root cause | |
| 4 | try-fix (gpt-5.4) | Centralize into helper | PASS (283/283 unit tests) | MediaPicker.ios.cs (+29/-28) |
Larger refactor; still removes PhotosAddOnly for photo capture; both PhotoAsync/PhotosAsync benefit |
| 5 | try-fix cross-poll (opus) | Info.plist guard: before check | PASS (283/283 unit tests) | MediaPicker.ios.cs (+1/-1) |
Preserves opt-in behavior but preserves a semantically wrong check |
| 6 | try-fix cross-poll (sonnet) | Fix at return when key absent | layer PASS (283/283 unit tests) | MediaPicker.ios.cs (+2), Permissions.ios.tvos.macos.cs (+12/-3) |
Most principled; benefits all callers; but too broad for targeted bug fix |
| PR | PR #34695 | Remove EnsureGrantedAsync<PhotosAddOnly> entirely (5-line Gate FAILED (no tests added) |
MediaPicker.ios.cs (5 deletion) |
Original PR; identical approach to already-merged PR #34287 on inflight/current | deletion) |
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| became Attempt 5 | |||
| became Attempt 6 | |||
| gpt-5.3-codex | 2 | Yes | Post-capture optional save (N/ CapturePhotoAsync doesn't save to gallery) |
| gpt-5.4 | 2 | No | NO NEW IDEAS |
| claude-opus-4.6 | 3 | No | NO NEW confirmed simple removal is the correct fix |
| claude-sonnet-4.6 | 3 | Yes | SaveToPhotoAlbum feature addition (out of scope; similar SaveToGallery already in development) |
| gpt-5.3-codex | 3 | Yes | Narrow try-catch (duplicates Attempt 5 logic without improvement) |
| gpt-5.4 | 3 | Yes | Change camera permission handling (unrelated to PhotosAddOnly bug) |
Exhausted: no substantively new approaches remainYes
Selected Fix: PR's fix (simple CapturePhotoAsync never writes to the photo library (returns temp file via GetFileResult); PhotosAddOnly is semantically wrong for this code path. Any alternative that preserves the check adds complexity around an incorrect permission. Confirmed by: (a) claude-opus Round 3 analysis, (b) identical fix already merged to inflight/current via PR #34287.removal)
📋 Report — Final Recommendation
⚠️ Final Recommendation: REQUEST CHANGES
Phase Status
| Phase | Status | Notes |
|---|---|---|
| Pre-Flight | ✅ COMPLETE | 1 implementation file; no test files; competing PR #34287 identified |
| Gate | ❌ FAILED | ios — no tests added; camera capture not automatable |
| Try-Fix | ✅ COMPLETE | 6 attempts, all ✅ PASS (unit tests); PR's removal selected as best fix |
| Report | ✅ COMPLETE |
Summary
PR #34695 correctly removes an incorrect EnsureGrantedAsync<Permissions.PhotosAddOnly>() call from MediaPicker.CapturePhotoAsync() on iOS. The fix is a clean 5-line deletion targeting MediaPicker.ios.cs. The same fix was independently authored and already merged to the inflight/current branch as PR #34287. The gate failed because no automated tests were added — the PR author notes this is inherent to the scenario (camera capture cannot be automated in tests). Despite the gate failure, the fix quality is correct.
Root Cause
In PhotoAsync() (the implementation backing CapturePhotoAsync), the UIImagePickerController path (else branch, iOS < 14 or camera capture) contained:
if (!pickExisting)
{
await Permissions.EnsureGrantedAsync<Permissions.PhotosAddOnly>();
}PhotosAddOnly requires NSPhotoLibraryAddUsageDescription in the app's Info.plist. This key is only needed when an app explicitly saves captured media to the photo library — which CapturePhotoAsync never does (it writes to a temp file and returns a FileResult). Apps that omit NSPhotoLibraryAddUsageDescription (reasonably, since they only want to capture) receive UnauthorizedAccessException from iOS when the permission check runs, even though they have camera permission granted.
Fix Quality
PR's fix is correct — the 5-line deletion is the minimal, targeted change needed. CapturePhotoAsync does not call PHPhotoLibrary.performChanges() or write to the device library anywhere in the code path; the permission check has no semantic justification. Multi-model try-fix exploration confirmed this conclusion:
- 6 alternative approaches were tried; all technically pass unit tests but introduce unnecessary complexity around a permission that serves no purpose in this code path
- claude-opus Round 3: "The permission was never needed — no code path in
CapturePhotoAsynccallsPHPhotoLibrary.performChanges()or writes to the library. Any alternative that keeps the check in any form is strictly worse." - Duplicate fix evidence: PR Removed PhotosAddOnly permission request within MediaPicker.ios #34287 (merged 2026-03-22 to
inflight/current) makes the identical code change for the same reason (issue iOS MediaPicker CapturePhotoAsync without "PhotosAddOnly" permission #34246).
Concerns Requiring Changes
-
No tests added — The gate failed. While the PR author notes camera capture is not automatable, there are options:
- A unit test could be added to
Essentials.UnitTestsverifying the permission sequence forCapturePhotoAsync(e.g., via mocking or confirmingPhotosAddOnlyis never in the required permissions list) - At minimum, a code comment explaining why
PhotosAddOnlyis not required here would help prevent future regression
- A unit test could be added to
-
Duplicate PR — PR Removed PhotosAddOnly permission request within MediaPicker.ios #34287 addresses the same root cause and was already merged to
inflight/current. The team should clarify whether PR [iOS/Mac] Fix MediaPicker.CapturePhotoAsync() fails with UnauthorisedAccessException on iOS #34695 should targetmain(since Removed PhotosAddOnly permission request within MediaPicker.ios #34287 is oninflight/current), or whether one of these PRs should be closed in favor of the other. -
CaptureVideoAsyncreview — Attempt 2 surfaced a related question:CaptureVideoAsyncuses the samePhotoAsync()path (photo=false, pickExisting=false). ThePhotosAddOnlycheck was also guarded by!pickExistingin that path. ConfirmCaptureVideoAsyncvia UIImagePickerController also doesn't needPhotosAddOnly(video capture also returns a temp file).
|
This is duplicate PR of #34287, hence closing this PR. |
Note
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!
Issue Details
In the latest .NET MAUI, calling CapturePhotoAsync() throws an UnauthorizedAccessException.
Root Cause
In the CapturePhotoAsync method, EnsureGrantedAsync is called for the PhotosAddOnly permission.
PhotosAddOnly is used to write images to the photo library, which is not required for CapturePhotoAsync.
Description of Change
Removed the PhotosAddOnly permission check from the CapturePhotoAsync method.
Regarding test case
Capturing an image is not possible in a test.
Issues Fixed
Fixes #34661
Tested the behavior in the following platforms.
Before.mov
After.mov