Skip to content

Commit 9a7fc89

Browse files
Use asset catalog for iOS images
Load packager image assets from a compiled asset catalog instead of loose files in the app bundle. At build time react-native-xcode.sh has the CLI emit imagesets into a staging 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 with [UIImage imageNamed:inBundle:]. The feature is opt-in via the RCTUseAssetCatalog Info.plist key, which 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 build and runtime cannot disagree on where image assets live. The script owns the catalog end to end, so migrating an app is adding the one Info.plist key: no .xcassets to create and no Xcode project changes. The app's own asset pipeline (Images.xcassets, asset symbol generation, CompileAssetCatalog) is untouched, and apps that have not migrated are unaffected. The catalog path is a single lookup with no filesystem fallback; a mis-bundled asset logs an RCTLogError instead of failing silently. The native side only resolves catalog names for what the CLI actually emits into the catalog (png/jpg/jpeg under main-bundle assets/, mirroring isCatalogAsset), so gif/webp packager assets, sub-bundles and OTA assets outside the main bundle fall through to the existing loader. jpeg is also added to RCTIsImageAssetsPath so jpeg assets are routed to the bundle-asset loaders. rn-tester and private/helloworld opt in via their Info.plists. helloworld's bundle phase now also substitutes REACT_NATIVE_PATH in CONFIG_JSON, which any bundling build requires. Cold image loads in RNTester are ~15x faster (median 47us vs 719us).
1 parent e04ff69 commit 9a7fc89

7 files changed

Lines changed: 296 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: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -886,7 +886,8 @@ BOOL RCTIsGzippedData(NSData *__nullable data)
886886
static BOOL RCTIsImageAssetsPath(NSString *path)
887887
{
888888
NSString *extension = [path pathExtension];
889-
return [extension isEqualToString:@"png"] || [extension isEqualToString:@"jpg"];
889+
return [extension isEqualToString:@"png"] || [extension isEqualToString:@"jpg"] ||
890+
[extension isEqualToString:@"jpeg"];
890891
}
891892

892893
BOOL RCTIsBundleAssetURL(NSURL *__nullable imageURL)
@@ -939,6 +940,114 @@ BOOL RCTIsLocalAssetURL(NSURL *__nullable imageURL)
939940
return bundleCache[key];
940941
}
941942

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

9561065
UIImage *__nullable RCTImageFromLocalAssetURL(NSURL *imageURL)
9571066
{
1067+
if (RCTUseAssetCatalog()) {
1068+
NSString *catalogName = RCTAssetCatalogNameForURL(imageURL);
1069+
if (catalogName != nil) {
1070+
// The app opted into the asset catalog and this is a packager asset, so it
1071+
// was compiled into RNAssets.bundle at build time. Trust the catalog and
1072+
// return directly, keeping the common path a single lookup with no
1073+
// filesystem fallback. Non-catalog assets (nil name) fall through below.
1074+
UIImage *image = [UIImage imageNamed:catalogName
1075+
inBundle:RCTAssetCatalogBundle()
1076+
compatibleWithTraitCollection:nil];
1077+
if (image == nil) {
1078+
RCTLogError(
1079+
@"Image \"%@\" (%@) was not found in the asset catalog. RCTUseAssetCatalog is enabled, "
1080+
"so image assets must be compiled into the app's RNAssets.bundle at build time.",
1081+
catalogName,
1082+
imageURL);
1083+
}
1084+
return image;
1085+
}
1086+
}
1087+
9581088
NSString *imageName = RCTBundlePathForURL(imageURL);
9591089

9601090
NSBundle *bundle = nil;

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

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,25 @@ else
151151
EXTRA_ARGS+=("--config-cmd" "'$NODE_BINARY' $NODE_ARGS '$REACT_NATIVE_DIR/cli.js' config")
152152
fi
153153

154+
# iOS asset catalog: when the app opts in with the RCTUseAssetCatalog key in its
155+
# Info.plist, emit image assets into an asset catalog and compile it with actool
156+
# into RNAssets.bundle inside the app, where the native image loader resolves
157+
# them by name. The key is the same one the native image loader reads, so
158+
# bundling and runtime cannot disagree on where image assets live, and the
159+
# catalog is fully owned by this script, so the app's Xcode project needs no
160+
# changes. Apps that have not migrated are unaffected. This also covers Mac
161+
# Catalyst (BUNDLE_PLATFORM is forced to "ios" above).
162+
USE_ASSET_CATALOG=""
163+
if [[ "$BUNDLE_PLATFORM" == "ios" && -n "$PRODUCT_SETTINGS_PATH" ]]; then
164+
USE_ASSET_CATALOG="$(/usr/libexec/PlistBuddy -c 'Print :RCTUseAssetCatalog' "$PRODUCT_SETTINGS_PATH" 2>/dev/null || true)"
165+
if [[ "$USE_ASSET_CATALOG" == "true" ]]; then
166+
ASSET_CATALOG_STAGING_DIR="${DERIVED_FILE_DIR:-$(mktemp -d)}/rn-assets"
167+
rm -rf "$ASSET_CATALOG_STAGING_DIR"
168+
mkdir -p "$ASSET_CATALOG_STAGING_DIR/RNAssets.xcassets"
169+
EXTRA_ARGS+=("--asset-catalog-dest" "$ASSET_CATALOG_STAGING_DIR")
170+
fi
171+
fi
172+
154173
# shellcheck disable=SC2086
155174
"$NODE_BINARY" $NODE_ARGS "$CLI_PATH" $BUNDLE_COMMAND \
156175
$CONFIG_ARG \
@@ -163,6 +182,49 @@ fi
163182
"${EXTRA_ARGS[@]}" \
164183
$EXTRA_PACKAGER_ARGS
165184

185+
# Compile the image assets emitted above into RNAssets.bundle inside the app.
186+
# The compiled Assets.car resolves the right scale by name at runtime, see
187+
# https://developer.apple.com/documentation/xcode/managing-assets-with-asset-catalogs
188+
if [[ "$USE_ASSET_CATALOG" == "true" ]]; then
189+
RN_ASSETS_BUNDLE="$DEST/RNAssets.bundle"
190+
rm -rf "$RN_ASSETS_BUNDLE"
191+
if [[ -n "$(find "$ASSET_CATALOG_STAGING_DIR/RNAssets.xcassets" -maxdepth 1 -name '*.imageset' -print -quit)" ]]; then
192+
mkdir -p "$RN_ASSETS_BUNDLE"
193+
ACTOOL_ARGS=("--platform" "${PLATFORM_NAME:-iphoneos}")
194+
ACTOOL_ARGS+=("--minimum-deployment-target" "${IPHONEOS_DEPLOYMENT_TARGET:-${MACOSX_DEPLOYMENT_TARGET:-15.1}}")
195+
case "${TARGETED_DEVICE_FAMILY:-1}" in *1*) ACTOOL_ARGS+=("--target-device" "iphone") ;; esac
196+
case "${TARGETED_DEVICE_FAMILY:-}" in *2*) ACTOOL_ARGS+=("--target-device" "ipad") ;; esac
197+
if [[ "${IS_MACCATALYST:-NO}" == "YES" ]]; then
198+
ACTOOL_ARGS+=("--ui-framework-family" "uikit")
199+
fi
200+
ACTOOL_OUTPUT="$(xcrun actool "$ASSET_CATALOG_STAGING_DIR/RNAssets.xcassets" \
201+
--compile "$RN_ASSETS_BUNDLE" \
202+
--output-format human-readable-text \
203+
"${ACTOOL_ARGS[@]}" 2>&1)" || true
204+
if [[ ! -f "$RN_ASSETS_BUNDLE/Assets.car" ]]; then
205+
echo "$ACTOOL_OUTPUT" >&2
206+
echo "error: failed to compile image assets into RNAssets.bundle. See actool output above." >&2
207+
exit 2
208+
fi
209+
cat > "$RN_ASSETS_BUNDLE/Info.plist" <<'RN_ASSETS_PLIST'
210+
<?xml version="1.0" encoding="UTF-8"?>
211+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
212+
<plist version="1.0">
213+
<dict>
214+
<key>CFBundleIdentifier</key>
215+
<string>org.reactjs.RNAssets</string>
216+
<key>CFBundleInfoDictionaryVersion</key>
217+
<string>6.0</string>
218+
<key>CFBundleName</key>
219+
<string>RNAssets</string>
220+
<key>CFBundlePackageType</key>
221+
<string>BNDL</string>
222+
</dict>
223+
</plist>
224+
RN_ASSETS_PLIST
225+
fi
226+
fi
227+
166228
if [[ $USE_HERMES == false ]]; then
167229
cp "$BUNDLE_FILE" "$DEST/"
168230
BUNDLE_FILE="$DEST/$BUNDLE_NAME.jsbundle"

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>

packages/rn-tester/RNTesterUnitTests/RCTURLUtilsTests.m

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,97 @@ - (void)testIsLocalAssetsURLParam
100100
XCTAssertFalse(RCTIsLocalAssetURL(otherAssetsURL));
101101
}
102102

103+
- (void)testAssetCatalogNameForURL
104+
{
105+
NSString *resourcePath = [[NSBundle mainBundle] resourcePath];
106+
107+
// Nested folder + "@2x" scale suffix: folders are encoded into the name, the
108+
// scale suffix and extension are stripped, and the "assets_" prefix removed.
109+
NSURL *scaledURL =
110+
[NSURL fileURLWithPath:[resourcePath stringByAppendingPathComponent:@"assets/AwesomeModule/icon@2x.png"]];
111+
XCTAssertEqualObjects(RCTAssetCatalogNameForURL(scaledURL), @"awesomemodule_icon");
112+
113+
// Same asset without a scale suffix resolves to the same name.
114+
NSURL *unscaledURL =
115+
[NSURL fileURLWithPath:[resourcePath stringByAppendingPathComponent:@"assets/AwesomeModule/icon.png"]];
116+
XCTAssertEqualObjects(RCTAssetCatalogNameForURL(unscaledURL), @"awesomemodule_icon");
117+
118+
// Illegal characters (e.g. "-") are stripped, matching the CLI identifier.
119+
NSURL *illegalCharsURL =
120+
[NSURL fileURLWithPath:[resourcePath stringByAppendingPathComponent:@"assets/my-module/my-icon@3x.png"]];
121+
XCTAssertEqualObjects(RCTAssetCatalogNameForURL(illegalCharsURL), @"mymodule_myicon");
122+
123+
// Non-integer scale suffixes are also stripped.
124+
NSURL *fractionalScaleURL =
125+
[NSURL fileURLWithPath:[resourcePath stringByAppendingPathComponent:@"assets/AwesomeModule/icon@1.5x.png"]];
126+
XCTAssertEqualObjects(RCTAssetCatalogNameForURL(fractionalScaleURL), @"awesomemodule_icon");
127+
128+
// Fractional scale with a leading zero (the packager emits e.g. "@0.5x").
129+
NSURL *zeroFractionalScaleURL =
130+
[NSURL fileURLWithPath:[resourcePath stringByAppendingPathComponent:@"assets/AwesomeModule/icon@0.5x.png"]];
131+
XCTAssertEqualObjects(RCTAssetCatalogNameForURL(zeroFractionalScaleURL), @"awesomemodule_icon");
132+
133+
// An uppercase "X" is not a scale suffix (the packager's scale format is
134+
// case-sensitive), so it stays in the name like any other character.
135+
NSURL *uppercaseScaleURL =
136+
[NSURL fileURLWithPath:[resourcePath stringByAppendingPathComponent:@"assets/AwesomeModule/icon@2X.png"]];
137+
XCTAssertEqualObjects(RCTAssetCatalogNameForURL(uppercaseScaleURL), @"awesomemodule_icon2x");
138+
139+
// Only the extension is stripped from a name containing dots; the inner dot
140+
// is an illegal identifier character and is removed.
141+
NSURL *multiDotURL =
142+
[NSURL fileURLWithPath:[resourcePath stringByAppendingPathComponent:@"assets/AwesomeModule/photo.small.png"]];
143+
XCTAssertEqualObjects(RCTAssetCatalogNameForURL(multiDotURL), @"awesomemodule_photosmall");
144+
145+
// Non-ASCII characters are not valid identifier characters and are removed.
146+
// File system paths are decomposed (NFD), so this also verifies the name is
147+
// normalized back to the precomposed form the CLI derived the identifier
148+
// from ("ü" must not leave a stray "u" behind).
149+
NSURL *unicodeURL =
150+
[NSURL fileURLWithPath:[resourcePath stringByAppendingPathComponent:@"assets/AwesomeModule/ünïcode.png"]];
151+
XCTAssertEqualObjects(RCTAssetCatalogNameForURL(unicodeURL), @"awesomemodule_ncode");
152+
153+
// Uppercase extensions are not catalog assets: the CLI's isCatalogAsset
154+
// check is case-sensitive, so these are copied as plain files.
155+
NSURL *uppercaseExtensionURL =
156+
[NSURL fileURLWithPath:[resourcePath stringByAppendingPathComponent:@"assets/AwesomeModule/Icon.PNG"]];
157+
XCTAssertNil(RCTAssetCatalogNameForURL(uppercaseExtensionURL));
158+
159+
// A packager asset without an image extension is not a catalog asset.
160+
NSURL *noExtensionURL =
161+
[NSURL fileURLWithPath:[resourcePath stringByAppendingPathComponent:@"assets/AwesomeModule/data"]];
162+
XCTAssertNil(RCTAssetCatalogNameForURL(noExtensionURL));
163+
164+
// ".jpeg" is a catalog image type (matching the CLI's isCatalogAsset).
165+
NSURL *jpegURL =
166+
[NSURL fileURLWithPath:[resourcePath stringByAppendingPathComponent:@"assets/AwesomeModule/photo@2x.jpeg"]];
167+
XCTAssertEqualObjects(RCTAssetCatalogNameForURL(jpegURL), @"awesomemodule_photo");
168+
169+
// Non-catalog image types (the CLI only emits png/jpg/jpeg into the catalog)
170+
// are not catalog assets and use the regular loader.
171+
NSURL *gifURL =
172+
[NSURL fileURLWithPath:[resourcePath stringByAppendingPathComponent:@"assets/AwesomeModule/anim.gif"]];
173+
XCTAssertNil(RCTAssetCatalogNameForURL(gifURL));
174+
175+
// Assets outside the project root are encoded with "_" by the packager
176+
// (e.g. "assets/../../shared" -> "assets/__shared") and resolve to the same
177+
// identifier the CLI generates.
178+
NSURL *outOfRootURL =
179+
[NSURL fileURLWithPath:[resourcePath stringByAppendingPathComponent:@"assets/__shared/icon@2x.png"]];
180+
XCTAssertEqualObjects(RCTAssetCatalogNameForURL(outOfRootURL), @"__shared_icon");
181+
182+
// Query strings are ignored, same as the legacy loader.
183+
NSURL *queryURL = [NSURL
184+
URLWithString:[NSString stringWithFormat:@"file://%@/assets/AwesomeModule/icon@2x.png?platform=ios&hash=abc",
185+
resourcePath]];
186+
XCTAssertEqualObjects(RCTAssetCatalogNameForURL(queryURL), @"awesomemodule_icon");
187+
188+
// A non-packager path (not under "assets/") is not a catalog asset.
189+
NSURL *notPackagerURL = [NSURL fileURLWithPath:[resourcePath stringByAppendingPathComponent:@"icon.png"]];
190+
XCTAssertNil(RCTAssetCatalogNameForURL(notPackagerURL));
191+
192+
// A nil URL is handled gracefully.
193+
XCTAssertNil(RCTAssetCatalogNameForURL(nil));
194+
}
195+
103196
@end

private/helloworld/ios/HelloWorld.xcodeproj/project.pbxproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@
265265
);
266266
runOnlyForDeploymentPostprocessing = 0;
267267
shellPath = /bin/sh;
268-
shellScript = "set -e\n\nexport CONFIG_JSON=$(sed -e \"s|HELLOWORLD_PATH|$(realpath \"${SRCROOT}/../\")|g\" \"${SRCROOT}/../.react-native.config\")\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n";
268+
shellScript = "set -e\n\nexport CONFIG_JSON=$(sed -e \"s|HELLOWORLD_PATH|$(realpath \"${SRCROOT}/../\")|g\" -e \"s|REACT_NATIVE_PATH|$(realpath \"${REACT_NATIVE_PATH}\")|g\" \"${SRCROOT}/../.react-native.config\")\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n";
269269
};
270270
A44ED3CC3037C88F69E3AF15 /* [CP] Embed Pods Frameworks */ = {
271271
isa = PBXShellScriptBuildPhase;

private/helloworld/ios/HelloWorld/Info.plist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
</dict>
3535
<key>NSLocationWhenInUseUsageDescription</key>
3636
<string></string>
37+
<key>RCTUseAssetCatalog</key>
38+
<true/>
3739
<key>UILaunchStoryboardName</key>
3840
<string>LaunchScreen</string>
3941
<key>UIRequiredDeviceCapabilities</key>

0 commit comments

Comments
 (0)