-
Notifications
You must be signed in to change notification settings - Fork 43
iOS: file:// URI paths not percent-decoded in RNFAppleFilamentProxy, causing silent load failure #335
Description
Bug Description
RNFAppleFilamentProxy.mm strips the file:// prefix from URIs using substringFromIndex:7 but does not percent-decode the remaining path. This causes silent asset loading failures on iOS production/release builds when the file path contains spaces (encoded as %20).
Root Cause
In RNFAppleFilamentProxy.mm, the file:// handling code:
if ([filePath hasPrefix:@"file://"]) {
filePath = [filePath substringFromIndex:7]; // strips "file://" but leaves %20 intact
NSData* bufferData = [NSData dataWithContentsOfFile:filePath ...];
}Image.resolveAssetSource() returns URL-encoded file:// URIs in iOS production builds. The iOS app data path includes Application Support (with a space), which becomes Application%20Support in the URI:
file:///var/mobile/Containers/Data/Application/.../Library/Application%20Support/ExponentExperienceData/...
After stripping file://, the path still contains %20, so NSData dataWithContentsOfFile: receives a path with literal %20 — which doesn't exist on the filesystem. The load fails silently (no error propagated to JS).
Impact
- All assets loaded via
require()+useBuffer/useModelfail silently on iOS production when the resolved path passes throughApplication Support(which is the standard Expo/React Native data directory) - Works fine in
__DEV__because Metro serves assets via HTTP URLs (nofile://path involved) - Affects
.glb,.filamat, and any other asset types loaded throughFilamentProxy.loadAsset()
Reproduction
- Use Expo managed workflow with react-native-filament
- Load a
.filamator.glbfile viarequire():const material = require('./assets/material.filamat'); const buffer = useBuffer({ source: material });
- Works in development (
npx expo start) - Build a release/production IPA (
eas build) - Asset fails to load —
useBuffernever resolves,useModelstays in loading state
Suggested Fix
Replace manual string slicing with proper NSURL path extraction, which automatically handles percent-decoding:
// Before (bug)
if ([filePath hasPrefix:@"file://"]) {
filePath = [filePath substringFromIndex:7];
}
// After (fix) - Option A: NSURL (idiomatic)
if ([filePath hasPrefix:@"file://"]) {
NSURL* fileURL = [NSURL URLWithString:filePath];
filePath = [fileURL path]; // automatically percent-decodes
}
// After (fix) - Option B: explicit decoding
if ([filePath hasPrefix:@"file://"]) {
filePath = [[filePath substringFromIndex:7] stringByRemovingPercentEncoding];
}Current JS Workaround
We are currently working around this in JS by manually decoding the URI before passing it:
const source = useMemo(() => {
if (__DEV__) return MaterialAsset;
const resolved = Image.resolveAssetSource(MaterialAsset);
if (resolved?.uri) {
return { uri: decodeURIComponent(resolved.uri) };
}
return MaterialAsset;
}, []);Related
- PR feat: support loading assets from
file://#205 introducedfile://support (where this bug originates) - PR fix: android release mode assets #263 fixed an analogous Android release-mode asset loading failure
- Issue No Error Handling - UseModel() #281 — lack of error handling in
useModel()makes this bug harder to diagnose
Environment
- react-native-filament: 1.9.0
- Expo SDK: 52
- iOS: 18.x
- React Native: 0.76.x