RORK LABJP
BUILD — Rork Max runs real Macs in the cloud loaded with Xcode and the iOS SDK, writing SwiftUI, compiling, reading the errors and building again. That loop, not the code generation, is what lifts the outputNATIVE — What comes out is pure Swift and SwiftUI, not React Native. Reaching AR, Metal graphics and widgets that React Native cannot touch is the real gap between this and other buildersPLATFORMS — Coverage spans iPhone, iPad, Apple Watch, Apple TV and Vision Pro, plus iMessage. Worth a look if you want to start from a watch app or an extension rather than a phone screenCOMPANION — The Rork Companion app lets you check a generated build on a real iPhone without a paid Apple Developer account, lowering the bar for trying a first project end to endPRICING — Free to start, paid plans from $25 a month, and Rork Max on the $200 Max plan. Worth working out up front how many projects it takes to earn that backDEADLINE — From August 31, 2026, Google Play requires target API level 36 or higher for new apps and updates alike. Ten days out, and the targetSdkVersion of what you generate is yours to verifyBUILD — Rork Max runs real Macs in the cloud loaded with Xcode and the iOS SDK, writing SwiftUI, compiling, reading the errors and building again. That loop, not the code generation, is what lifts the outputNATIVE — What comes out is pure Swift and SwiftUI, not React Native. Reaching AR, Metal graphics and widgets that React Native cannot touch is the real gap between this and other buildersPLATFORMS — Coverage spans iPhone, iPad, Apple Watch, Apple TV and Vision Pro, plus iMessage. Worth a look if you want to start from a watch app or an extension rather than a phone screenCOMPANION — The Rork Companion app lets you check a generated build on a real iPhone without a paid Apple Developer account, lowering the bar for trying a first project end to endPRICING — Free to start, paid plans from $25 a month, and Rork Max on the $200 Max plan. Worth working out up front how many projects it takes to earn that backDEADLINE — From August 31, 2026, Google Play requires target API level 36 or higher for new apps and updates alike. Ten days out, and the targetSdkVersion of what you generate is yours to verify
Articles/Dev Tools
Dev Tools/2026-06-29Advanced

Handling iOS Limited Photo Library Access in a Rork (Expo) App

Handle iOS limited photo library access (selected photos only) correctly in a Rork (Expo) app. Covers the three states of full / limited / denied, designing a screen that works from the selected subset, and a path to add more photos, all with working code.

Rork540Expo176Photo LibraryPermissions4Privacy9iOS110

Premium Article

I got a request on a wallpaper app — "I'd like to use my own photos as backgrounds" — and added photo library access. It worked fine on my test device, yet one user reported, "I selected photos but nothing shows up in the app." The cause: at the iOS permission dialog, they had chosen "Select Photos" rather than "Allow Access to All Photos." My app was built assuming full access, reading the whole library, so it could not correctly handle the few photos the user had picked.

iOS limited access (selected photos only) is exactly the option privacy-minded users tend to pick. Using a Rork-generated Expo app, this walkthrough lays out a design that builds limited access in as the assumption, not the exception.

Not "was it granted" but "how much was granted"

Treat the photo permission as a granted boolean and you drop limited access. The iOS photo permission effectively has three states.

StateUser's choiceWhat the app can doCorrect path
Full accessAll PhotosRead the whole libraryNormal grid
Limited accessSelect PhotosRead only the chosen photosShow the selection + an "add more" path
DeniedDon't AllowRead nothingGuide to the Settings app

The pitfall: even limited access succeeds as a permission. Look only at granted === true and you cannot tell full from limited. Try to read the whole library under limited access and only the few chosen photos come back — which, depending on the implementation, looks "empty." Mistake that for denial and you show the user an off-target "please allow in Settings," confusing them.

Receive the three states correctly

Expo's expo-media-library returns information that distinguishes full from limited. Read accessPrivileges (iOS) to decide all / limited / none.

// photos/permission.ts
import * as MediaLibrary from "expo-media-library";
 
export type PhotoAccess = "all" | "limited" | "denied";
 
export async function requestPhotoAccess(): Promise<PhotoAccess> {
  // Pass false for writeOnly to request read permission
  const res = await MediaLibrary.requestPermissionsAsync(false);
  return normalize(res);
}
 
export async function getPhotoAccess(): Promise<PhotoAccess> {
  const res = await MediaLibrary.getPermissionsAsync(false);
  return normalize(res);
}
 
function normalize(res: MediaLibrary.PermissionResponse): PhotoAccess {
  if (res.status !== "granted") return "denied";
  // iOS: accessPrivileges is "limited" under limited access
  // On Android or full-access iOS it is "all" (or undefined)
  const priv = (res as any).accessPrivileges as string | undefined;
  if (priv === "limited") return "limited";
  return "all";
}

The false argument to requestPermissionsAsync(false) means "not write-only" — that is, request read access too. Get this wrong and you cannot read, producing a hard-to-diagnose bug.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
Switch to a screen design that works from the selected subset, treating limited access as the assumption rather than full access
Receive permission not as a granted boolean but as three states (all / limited / denied), with the right path for each
Build a path to re-pick more photos under limited access, and a design that never mistakes an empty selection for a denied permission
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

Dev Tools2026-05-21
Rork iOS App Rejected with ITMS-90683 on TestFlight — How to Fix Missing Purpose Strings via app.json
If your Rork-built iOS app passes upload but gets an email titled ITMS-90683: Missing Purpose String in Info.plist, this guide walks through the real cause and the permanent fix via app.json, based on 12 years of shipping personal iOS apps with the same problem appearing across new SDK updates.
Dev Tools2026-07-27
When to Raise Your Minimum iOS Version — Count Leftover Branches, Not User Percentages
Judging a minimum OS bump by usage share produces the same answer every year, so the decision never happens. Here is the annotation convention, the sweep script that counts how many branches each candidate floor would retire, and what to watch for 30 days after.
Dev Tools2026-06-26
Import the User's Own Image From the Files App in a Rork App, Without the URL Going Stale
Pull images from the Files app or iCloud Drive and the URL you picked goes invalid moments later. Here is how expo-document-picker, security-scoped URLs, and a reliable copy into your sandbox actually work, with running code.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →