Skip to content

Commit 26769a0

Browse files
janicduplessismeta-codesync[bot]
authored andcommitted
Use asset catalog for iOS images (#30129)
Summary: Use an asset catalog for images on iOS. At build time, `react-native-xcode.sh` has the bundler emit packager image assets into a staging asset catalog (`--asset-catalog-dest`, already in `react-native/community-cli-plugin`), compiles it with `actool` into an `RNAssets.bundle` inside the app, and the native image loader resolves images by name from that bundle's `Assets.car` with `[UIImage imageNamed:inBundle:]` instead of reading loose files. Properties worth calling out: - **One opt-in switch, no project changes.** The feature is gated on the `RCTUseAssetCatalog` key in the app's Info.plist, and that key is the single source of truth: the native loader reads it (a build-time constant, read once), and `react-native-xcode.sh` reads the same key to decide whether to bundle images into the catalog — so the build and the runtime cannot disagree on where image assets live. Because the script owns the catalog end to end (staged in derived files, compiled into the app's resources next to `main.jsbundle`), **migration is adding one Info.plist key** — no `.xcassets` to create, no build-phase or project changes. - **No fallback.** The catalog path is a single lookup with no filesystem fallback — a mis-bundled asset logs an `RCTLogError` (instead of silently rendering nothing) rather than adding an `fs` stat to the hot path. - **Parity with the CLI, OTA-safe.** The native side resolves a catalog name only for what the CLI actually emits into the catalog: png/jpg/jpeg under main-bundle `assets/…` (mirroring `isCatalogAsset`). Everything else — gif/webp packager assets, `.bundle` sub-bundles, OTA assets outside the main bundle — falls through to the existing loader. (This also adds `jpeg` to `RCTIsImageAssetsPath`, which previously listed only `png`/`jpg` — an oversight, since `.jpeg` is the same kind of bundled image; it is now treated like `png`/`jpg` regardless of the catalog.) - **No interference with Xcode's own asset pipeline.** The app's `Images.xcassets`, asset symbol generation (`UIImage(resource:)`), and `CompileAssetCatalog` are untouched: the RN catalog never enters the Xcode project, so there are no build-phase ordering constraints and nothing to disable. ## Changelog: [iOS] [Added] - Use asset catalog for ios images ## Performance **What was measured:** the time to *resolve* an asset to a `UIImage` inside `RCTImageFromLocalAssetURL` — a compiled-catalog name lookup vs. the legacy `imageNamed:` search + `imageWithContentsOfFile:` on loose files. Decode is deferred in both paths (and costs the same), so this is resolve latency, not end-to-end render time. It matters because `RCTLocalAssetImageLoader` loads local assets synchronously to avoid flicker, so this time sits on the critical path once per distinct image. Setup: RNTester, Release, new arch, iOS simulator, first load of each distinct image (UIKit caches repeats — repeat loads are ~10 µs in both builds). Raw per-load numbers (final implementation, `RNAssets.bundle`), in load order: | # | image | catalog (µs) | filesystem (µs) | |---|---|---:|---:| | 1 | `searchicon` ¹ | 1,081 | 4,497 | | 2 | `bottomnavcomponentsicondark` | 320 | 710 | | 3 | `bottomnavplaygroundsiconlight` | 147 | 422 | | 4 | `bottomnavapisiconlight` | 29 | 352 | | 5 | `bottomnavcomponentsiconlight` | 320 | 1,410 | | 6 | `uie_thumb_normal` | 67 | 667 | | 7 | `uie_thumb_selected` | 32 | 468 | | 8 | `uie_comment_normal` | 26 | 454 | | 9 | `uie_comment_highlighted` | 31 | 671 | | 10 | `verylargeimage` ² | 30 | 1,855 | | 11 | `alphahotdog` | 171 | 592 | ¹ First load in each build pays one-time costs: mapping `Assets.car` on the catalog side, ImageIO/framework warm-up + first disk touch on the filesystem side. ² Resolve only — the large image's decode cost is unchanged, so its end-to-end win is much smaller than this row suggests. Every image is faster from the catalog: median 67 µs vs 667 µs (**10×**; an earlier run of the same benchmark measured 47 µs vs 719 µs, ~15× — run-to-run variance, same order either way). The mechanism: the catalog is a single memory-mapped, indexed archive (one name lookup, no per-image syscalls), while the loose-file path does an `imageNamed:` search over several filename permutations plus a per-image file open. The URL→catalog-name derivation on the native side is a single character pass with no regex; benchmarked against a straightforward regex-based implementation of the same transform it measures 4.9 µs vs 11.0 µs per call (2.2×, Debug build, including the shared URL→bundle-path resolution both share), so the name mapping is a negligible part of the lookup. **App thinning:** verified that App Store slicing thins the `Assets.car` inside `RNAssets.bundle` exactly like the app's own catalog. Test: a fixture app with a 1x/2x/3x imageset in both its main `Images.xcassets` and an actool-compiled `RNAssets.bundle`, archived and exported with `thinning` for a 3x device — both cars were sliced to 3x-only. So unused scales are stripped per device (unlike today's loose packager files, which ship every scale to every device). ## Migration To opt an app in, add to its `Info.plist`: ```xml <key>RCTUseAssetCatalog</key> <true/> ``` That's the entire migration — no `.xcassets` to create, no build-phase or project changes. Notes: - **Do a clean build after changing the key.** Incremental builds don't remove image assets a previous build placed with the other setting (unused, but dead weight in local builds; archives are unaffected). - The key must be a literal value in the app's source Info.plist: the build script reads it with PlistBuddy (accepting the same value forms the runtime `boolValue` check accepts), so build-setting substitution or fully generated Info.plists (`GENERATE_INFOPLIST_FILE`) aren't seen by the script and stay on the legacy path. If bundling happens outside `react-native-xcode.sh` (custom CI calling `react-native bundle` directly) while the key is enabled, the native side detects the missing `RNAssets.bundle` and logs an actionable error instead of silently rendering nothing. - Incomplete but valid scale sets degrade gracefully: an imageset with, say, only a `2x` (no `3x`) is kept for a 3x device by App Store slicing and used at runtime — verified with a thinned export. Only a *non-integer*-only asset (e.g. an Android-density `1.5x` with no `1x`/`2x`/`3x` variant, which iOS asset catalogs cannot represent) is unsupported: `actool` drops it with an `Unknown scale value` warning in the build log and the runtime logs an error. That's a rare antipattern; a `community-cli-plugin` follow-up will turn it into a clear build-time error at the source (it has the scale and asset path), rather than this repo re-parsing actool output. - OTA updates keep working: assets delivered outside the main bundle never resolve to the catalog and use the regular loader. - Docs follow-up: a section on the website's Images page (`react-native-website`) and the one-line opt-in in `react-native-community/template` will be separate PRs, timed with the release that ships this. Pull Request resolved: #30129 Test Plan: RNTester and `private/helloworld` both opt in (`RCTUseAssetCatalog=YES` in their Info.plists — the only change an app needs), so CI covers the catalog path on both app shapes. The new-project template lives in `react-native-community/template` and will get the same one-line opt-in in a follow-up PR there. To test locally with RNTester: 1. In Xcode, edit the RNTester scheme → set Build Configuration to Release. 2. Build & run. Local images render, served from `RNAssets.bundle/Assets.car`; the app contains no loose packager png/jpg files. Unit coverage in `RCTURLUtilsTests` (`testAssetCatalogNameForURL`), asserting the identifiers the CLI generates: scale-suffix stripping (including non-integer `1.5x`), folder encoding, illegal-char removal, jpeg vs. non-catalog types (gif), out-of-project-root assets, query strings, non-packager paths, and nil. Mac Catalyst: verified end-to-end by building rn-tester as a Mac Catalyst app — the catalog compiles (via the Catalyst-specific `--platform macosx --ui-framework-family uikit` actool invocation) into `RNTester.app/Contents/Resources/RNAssets.bundle` with all packager images and no loose files, and the runtime uses the same `imageNamed:inBundle:` mechanism CocoaPods resource bundles use on Catalyst. (rn-tester doesn't ship a Catalyst config, so this isn't in CI, but the build is clean.) Reviewed By: cipolleschi Differential Revision: D40095766 Pulled By: javache fbshipit-source-id: 18dfcb47ae5185ef279b20dd0087de9a31e930b4
1 parent 711a9a5 commit 26769a0

9 files changed

Lines changed: 356 additions & 2 deletions

File tree

packages/react-native/React/Base/RCTUtils.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,11 @@ RCT_EXTERN NSData *__nullable RCTDecompressGzipData(NSData *__nullable data, NSU
139139
// (or nil, if the URL does not specify a path within the main bundle)
140140
RCT_EXTERN NSString *__nullable RCTBundlePathForURL(NSURL *__nullable URL);
141141

142+
// Returns the asset catalog image name for a packager asset URL, or nil if the
143+
// URL is not a main-bundle packager asset. The name matches the identifier the
144+
// CLI uses when generating the catalog (see assetPathUtils.getResourceIdentifier).
145+
RCT_EXTERN NSString *__nullable RCTAssetCatalogNameForURL(NSURL *__nullable URL);
146+
142147
// Returns the Path of Library directory
143148
RCT_EXTERN NSString *__nullable RCTLibraryPath(void);
144149

packages/react-native/React/Base/RCTUtils.mm

Lines changed: 141 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#import <objc/runtime.h>
1414
#import <zlib.h>
1515
#import <atomic>
16+
#import <vector>
1617

1718
#import <UIKit/UIKit.h>
1819

@@ -886,7 +887,8 @@ BOOL RCTIsGzippedData(NSData *__nullable data)
886887
static BOOL RCTIsImageAssetsPath(NSString *path)
887888
{
888889
NSString *extension = [path pathExtension];
889-
return [extension isEqualToString:@"png"] || [extension isEqualToString:@"jpg"];
890+
return
891+
[extension isEqualToString:@"png"] || [extension isEqualToString:@"jpg"] || [extension isEqualToString:@"jpeg"];
890892
}
891893

892894
BOOL RCTIsBundleAssetURL(NSURL *__nullable imageURL)
@@ -939,6 +941,115 @@ BOOL RCTIsLocalAssetURL(NSURL *__nullable imageURL)
939941
return bundleCache[key];
940942
}
941943

944+
static BOOL RCTUseAssetCatalog(void)
945+
{
946+
static BOOL useAssetCatalog = NO;
947+
static dispatch_once_t onceToken;
948+
dispatch_once(&onceToken, ^{
949+
useAssetCatalog = [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"RCTUseAssetCatalog"] boolValue];
950+
});
951+
return useAssetCatalog;
952+
}
953+
954+
// The bundle react-native-xcode.sh compiles packager image assets into
955+
// (RNAssets.bundle, an actool-compiled asset catalog inside the app).
956+
static NSBundle *__nullable RCTAssetCatalogBundle(void)
957+
{
958+
static NSBundle *bundle;
959+
static dispatch_once_t onceToken;
960+
dispatch_once(&onceToken, ^{
961+
NSURL *bundleURL = [[NSBundle mainBundle] URLForResource:@"RNAssets" withExtension:@"bundle"];
962+
if (bundleURL != nil) {
963+
bundle = [NSBundle bundleWithURL:bundleURL];
964+
}
965+
});
966+
return bundle;
967+
}
968+
969+
NSString *__nullable RCTAssetCatalogNameForURL(NSURL *__nullable URL)
970+
{
971+
// The "assets/" prefix the packager uses for all image assets. The CLI strips
972+
// it from the identifiers it names the imagesets with.
973+
constexpr NSUInteger assetsPrefixLength = sizeof("assets/") - 1;
974+
975+
NSString *path = RCTBundlePathForURL(URL);
976+
// Packager assets always live under "assets/". Anything else (sub-bundles,
977+
// CodePush/OTA assets outside the main bundle) is not in the catalog.
978+
if (path == nil || ![path hasPrefix:@"assets/"]) {
979+
return nil;
980+
}
981+
982+
// Other packager assets (gif, webp, ...) are copied as plain files and must
983+
// use the regular loader.
984+
if (!RCTIsImageAssetsPath(path)) {
985+
return nil;
986+
}
987+
988+
// File system paths come back decomposed (NFD); restore the precomposed form
989+
// the packager saw on disk so non-ASCII characters filter out the same way
990+
// they do in the CLI's identifier.
991+
path = path.precomposedStringWithCanonicalMapping;
992+
993+
const NSUInteger length = path.length;
994+
unichar stackBuffer[256];
995+
std::vector<unichar> heapBuffer;
996+
unichar *chars = stackBuffer;
997+
if (length > 256) {
998+
heapBuffer.resize(length);
999+
chars = heapBuffer.data();
1000+
}
1001+
[path getCharacters:chars range:NSMakeRange(0, length)];
1002+
1003+
// Strip the file extension (guaranteed present by RCTIsImageAssetsPath) and
1004+
// an optional "@<scale>x" suffix (integer or fractional, e.g. "@2x",
1005+
// "@1.5x"). The catalog stores a single imageset per image and resolves the
1006+
// scale by name at runtime, see
1007+
// https://developer.apple.com/documentation/xcode/managing-assets-with-asset-catalogs
1008+
NSUInteger end = length - 1;
1009+
while (end > assetsPrefixLength && chars[end] != '.') {
1010+
end--;
1011+
}
1012+
if (end > assetsPrefixLength && chars[end - 1] == 'x') {
1013+
// Walk back over "@<digits>(.<digits>)?" ending at the "x".
1014+
NSUInteger cursor = end - 1;
1015+
while (cursor > assetsPrefixLength && chars[cursor - 1] >= '0' && chars[cursor - 1] <= '9') {
1016+
cursor--;
1017+
}
1018+
if (cursor < end - 1) {
1019+
if (chars[cursor - 1] == '@') {
1020+
end = cursor - 1;
1021+
} else if (chars[cursor - 1] == '.') {
1022+
NSUInteger integerPart = cursor - 1;
1023+
while (integerPart > assetsPrefixLength && chars[integerPart - 1] >= '0' && chars[integerPart - 1] <= '9') {
1024+
integerPart--;
1025+
}
1026+
if (integerPart < cursor - 1 && chars[integerPart - 1] == '@') {
1027+
end = integerPart - 1;
1028+
}
1029+
}
1030+
}
1031+
}
1032+
1033+
// Build the identifier in place in a single pass: skip the "assets/" prefix,
1034+
// lowercase, encode the folder structure with "_" and drop anything that is
1035+
// not a valid identifier character, producing the same identifier as
1036+
// getResourceIdentifier in the CLI, which names the imagesets.
1037+
NSUInteger resultLength = 0;
1038+
for (NSUInteger i = assetsPrefixLength; i < end; i++) {
1039+
unichar c = chars[i];
1040+
if (c == '/') {
1041+
chars[resultLength++] = '_';
1042+
} else if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_') {
1043+
chars[resultLength++] = c;
1044+
} else if (c >= 'A' && c <= 'Z') {
1045+
chars[resultLength++] = c + ('a' - 'A');
1046+
}
1047+
}
1048+
1049+
NSString *name = [NSString stringWithCharacters:chars length:resultLength];
1050+
return name;
1051+
}
1052+
9421053
UIImage *__nullable RCTImageFromLocalBundleAssetURL(NSURL *imageURL)
9431054
{
9441055
if (![imageURL.scheme isEqualToString:@"file"]) {
@@ -955,6 +1066,35 @@ BOOL RCTIsLocalAssetURL(NSURL *__nullable imageURL)
9551066

9561067
UIImage *__nullable RCTImageFromLocalAssetURL(NSURL *imageURL)
9571068
{
1069+
if (RCTUseAssetCatalog()) {
1070+
NSString *catalogName = RCTAssetCatalogNameForURL(imageURL);
1071+
if (catalogName != nil) {
1072+
// The app opted into the asset catalog and this is a packager asset, so it
1073+
// was compiled into RNAssets.bundle at build time. Trust the catalog and
1074+
// return directly, keeping the common path a single lookup with no
1075+
// filesystem fallback. Non-catalog assets (nil name) fall through below.
1076+
NSBundle *assetCatalogBundle = RCTAssetCatalogBundle();
1077+
if (assetCatalogBundle == nil) {
1078+
// Passing a nil bundle to imageNamed:inBundle: would silently search the
1079+
// main bundle instead, potentially resolving an unrelated app image.
1080+
RCTLogError(
1081+
@"RCTUseAssetCatalog is enabled but RNAssets.bundle was not found in the app. Image assets must be "
1082+
"bundled by react-native-xcode.sh, which compiles them into the app at build time. (loading %@)",
1083+
imageURL);
1084+
return nil;
1085+
}
1086+
UIImage *image = [UIImage imageNamed:catalogName inBundle:assetCatalogBundle compatibleWithTraitCollection:nil];
1087+
if (image == nil) {
1088+
RCTLogError(
1089+
@"Image \"%@\" (%@) was not found in the asset catalog. RCTUseAssetCatalog is enabled, "
1090+
"so image assets must be compiled into the app's RNAssets.bundle at build time.",
1091+
catalogName,
1092+
imageURL);
1093+
}
1094+
return image;
1095+
}
1096+
}
1097+
9581098
NSString *imageName = RCTBundlePathForURL(imageURL);
9591099

9601100
NSBundle *bundle = nil;

packages/react-native/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@
114114
"scripts/react-native-xcode.sh",
115115
"scripts/setup-apple-spm.js",
116116
"scripts/spm",
117+
"scripts/xcode/asset-catalog.sh",
117118
"scripts/xcode/ccache-clang.sh",
118119
"scripts/xcode/ccache-clang++.sh",
119120
"scripts/xcode/ccache.conf",

packages/react-native/scripts/react-native-xcode.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,13 @@ else
171171
EXTRA_ARGS+=("--config-cmd" "'$NODE_BINARY' $NODE_ARGS '$REACT_NATIVE_DIR/cli.js' config")
172172
fi
173173

174+
# shellcheck source=/dev/null
175+
source "$REACT_NATIVE_DIR/scripts/xcode/asset-catalog.sh"
176+
ASSET_CATALOG_STAGING_DIR="$(asset_catalog_staging_dir)"
177+
if [[ -n "$ASSET_CATALOG_STAGING_DIR" ]]; then
178+
EXTRA_ARGS+=("--asset-catalog-dest" "$ASSET_CATALOG_STAGING_DIR")
179+
fi
180+
174181
# shellcheck disable=SC2086
175182
"$NODE_BINARY" $NODE_ARGS "$CLI_PATH" $BUNDLE_COMMAND \
176183
$CONFIG_ARG \
@@ -183,6 +190,8 @@ fi
183190
"${EXTRA_ARGS[@]}" \
184191
$EXTRA_PACKAGER_ARGS
185192

193+
asset_catalog_compile "$ASSET_CATALOG_STAGING_DIR"
194+
186195
if [[ $USE_HERMES == false ]]; then
187196
cp "$BUNDLE_FILE" "$DEST/"
188197
BUNDLE_FILE="$DEST/$BUNDLE_NAME.jsbundle"
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#!/bin/bash
2+
# Copyright (c) Meta Platforms, Inc. and affiliates.
3+
#
4+
# This source code is licensed under the MIT license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
# Bundles packager image assets into a compiled asset catalog (RNAssets.bundle)
8+
# inside the app, where the native image loader resolves them by name. Sourced
9+
# by react-native-xcode.sh.
10+
#
11+
# The feature is opt-in via the RCTUseAssetCatalog key in the app's Info.plist,
12+
# which is the same key the native image loader reads, so bundling and runtime
13+
# cannot disagree on where image assets live. The catalog is fully owned by
14+
# these functions (staged in derived files, compiled with actool into the app's
15+
# resources next to the js bundle), so the app's Xcode project needs no
16+
# changes. Apps that have not migrated are unaffected.
17+
#
18+
# After changing the RCTUseAssetCatalog key, do a clean build: incremental
19+
# builds do not remove image assets a previous build placed in the app with the
20+
# other setting (they are unused but add dead weight).
21+
22+
# Prints the directory the bundler should emit image assets into (via
23+
# --asset-catalog-dest), or nothing if the app has not opted into the asset
24+
# catalog with the RCTUseAssetCatalog Info.plist key.
25+
asset_catalog_staging_dir() {
26+
[[ "$BUNDLE_PLATFORM" == "ios" && -n "$PRODUCT_SETTINGS_PATH" ]] || return 0
27+
28+
local use_asset_catalog
29+
use_asset_catalog="$(/usr/libexec/PlistBuddy -c 'Print :RCTUseAssetCatalog' "$PRODUCT_SETTINGS_PATH" 2>/dev/null || true)"
30+
# Accept the value forms NSBundle's boolValue treats as true, so this check
31+
# cannot disagree with the native runtime check.
32+
case "$(echo "$use_asset_catalog" | tr '[:upper:]' '[:lower:]')" in
33+
true | yes | 1) ;;
34+
*) return 0 ;;
35+
esac
36+
37+
local staging_dir="${DERIVED_FILE_DIR:-$(mktemp -d)}/rn-assets"
38+
rm -rf "$staging_dir"
39+
mkdir -p "$staging_dir/RNAssets.xcassets"
40+
printf '%s\n' "$staging_dir"
41+
}
42+
43+
# Compiles the staging catalog ($1, as returned by asset_catalog_staging_dir)
44+
# into RNAssets.bundle inside the app. The compiled Assets.car resolves the
45+
# right scale by name at runtime, see
46+
# https://developer.apple.com/documentation/xcode/managing-assets-with-asset-catalogs
47+
asset_catalog_compile() {
48+
local staging_dir="$1"
49+
local rn_assets_bundle="$DEST/RNAssets.bundle"
50+
# Always remove first so a stale bundle from a previous build does not ship
51+
# when the app opts out or has no image assets.
52+
rm -rf "$rn_assets_bundle"
53+
[[ -n "$staging_dir" ]] || return 0
54+
if [[ -z "$(find "$staging_dir/RNAssets.xcassets" -maxdepth 1 -name '*.imageset' -print -quit)" ]]; then
55+
return 0
56+
fi
57+
58+
mkdir -p "$rn_assets_bundle"
59+
local actool_args=("--platform" "${PLATFORM_NAME:-iphoneos}")
60+
# These are always iOS-family image assets (this runs only for BUNDLE_PLATFORM
61+
# "ios", which includes Mac Catalyst), so the deployment target must be an iOS
62+
# version. On Catalyst the platform is macosx but MACOSX_DEPLOYMENT_TARGET is a
63+
# macOS version (e.g. 10.15); passing that as the target makes actool silently
64+
# emit loose files instead of a compiled Assets.car, so it must not be used.
65+
actool_args+=("--minimum-deployment-target" "${IPHONEOS_DEPLOYMENT_TARGET:-15.1}")
66+
case "${TARGETED_DEVICE_FAMILY:-1}" in *1*) actool_args+=("--target-device" "iphone") ;; esac
67+
case "${TARGETED_DEVICE_FAMILY:-1}" in *2*) actool_args+=("--target-device" "ipad") ;; esac
68+
if [[ "${IS_MACCATALYST:-NO}" == "YES" ]]; then
69+
actool_args+=("--ui-framework-family" "uikit")
70+
fi
71+
72+
# Surface actool diagnostics in the build log: without these flags actool
73+
# suppresses them entirely, and it exits 0 even when it drops an imageset.
74+
local actool_output
75+
actool_output="$(xcrun actool "$staging_dir/RNAssets.xcassets" \
76+
--compile "$rn_assets_bundle" \
77+
--output-format human-readable-text \
78+
--errors --warnings --notices \
79+
"${actool_args[@]}" 2>&1)" || true
80+
echo "$actool_output"
81+
if [[ ! -f "$rn_assets_bundle/Assets.car" ]]; then
82+
echo "error: failed to compile image assets into RNAssets.bundle. See actool output above." >&2
83+
exit 2
84+
fi
85+
86+
cat > "$rn_assets_bundle/Info.plist" <<'RN_ASSETS_PLIST'
87+
<?xml version="1.0" encoding="UTF-8"?>
88+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
89+
<plist version="1.0">
90+
<dict>
91+
<key>CFBundleIdentifier</key>
92+
<string>org.reactjs.RNAssets</string>
93+
<key>CFBundleInfoDictionaryVersion</key>
94+
<string>6.0</string>
95+
<key>CFBundleName</key>
96+
<string>RNAssets</string>
97+
<key>CFBundlePackageType</key>
98+
<string>BNDL</string>
99+
</dict>
100+
</plist>
101+
RN_ASSETS_PLIST
102+
}

packages/rn-tester/RNTester/Info.plist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
<string>You need to add NSPhotoLibraryUsageDescription key in Info.plist to enable photo library usage, otherwise it is going to *fail silently*!</string>
4949
<key>RCTNewArchEnabled</key>
5050
<true/>
51+
<key>RCTUseAssetCatalog</key>
52+
<true/>
5153
<key>UILaunchStoryboardName</key>
5254
<string>LaunchScreen</string>
5355
<key>UIRequiredDeviceCapabilities</key>

0 commit comments

Comments
 (0)