From adf495df85031d38692b47965ea15592868e5958 Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Mon, 6 Jul 2026 11:02:37 -0400 Subject: [PATCH] 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). --- packages/react-native/React/Base/RCTUtils.h | 5 + packages/react-native/React/Base/RCTUtils.mm | 141 +++++++++++++++++- packages/react-native/package.json | 1 + .../scripts/react-native-xcode.sh | 9 ++ .../scripts/xcode/asset-catalog.sh | 102 +++++++++++++ packages/rn-tester/RNTester/Info.plist | 2 + .../RNTesterUnitTests/RCTURLUtilsTests.m | 93 ++++++++++++ .../ios/HelloWorld.xcodeproj/project.pbxproj | 2 +- private/helloworld/ios/HelloWorld/Info.plist | 2 + 9 files changed, 355 insertions(+), 2 deletions(-) create mode 100644 packages/react-native/scripts/xcode/asset-catalog.sh diff --git a/packages/react-native/React/Base/RCTUtils.h b/packages/react-native/React/Base/RCTUtils.h index 95b49b79a417..be9574c9167d 100644 --- a/packages/react-native/React/Base/RCTUtils.h +++ b/packages/react-native/React/Base/RCTUtils.h @@ -139,6 +139,11 @@ RCT_EXTERN NSData *__nullable RCTDecompressGzipData(NSData *__nullable data, NSU // (or nil, if the URL does not specify a path within the main bundle) RCT_EXTERN NSString *__nullable RCTBundlePathForURL(NSURL *__nullable URL); +// Returns the asset catalog image name for a packager asset URL, or nil if the +// URL is not a main-bundle packager asset. The name matches the identifier the +// CLI uses when generating the catalog (see assetPathUtils.getResourceIdentifier). +RCT_EXTERN NSString *__nullable RCTAssetCatalogNameForURL(NSURL *__nullable URL); + // Returns the Path of Library directory RCT_EXTERN NSString *__nullable RCTLibraryPath(void); diff --git a/packages/react-native/React/Base/RCTUtils.mm b/packages/react-native/React/Base/RCTUtils.mm index 60e2cb19cbec..9e8cc2748096 100644 --- a/packages/react-native/React/Base/RCTUtils.mm +++ b/packages/react-native/React/Base/RCTUtils.mm @@ -886,7 +886,8 @@ BOOL RCTIsGzippedData(NSData *__nullable data) static BOOL RCTIsImageAssetsPath(NSString *path) { NSString *extension = [path pathExtension]; - return [extension isEqualToString:@"png"] || [extension isEqualToString:@"jpg"]; + return [extension isEqualToString:@"png"] || [extension isEqualToString:@"jpg"] || + [extension isEqualToString:@"jpeg"]; } BOOL RCTIsBundleAssetURL(NSURL *__nullable imageURL) @@ -939,6 +940,113 @@ BOOL RCTIsLocalAssetURL(NSURL *__nullable imageURL) return bundleCache[key]; } +static BOOL RCTUseAssetCatalog(void) +{ + static BOOL useAssetCatalog = NO; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + useAssetCatalog = [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"RCTUseAssetCatalog"] boolValue]; + }); + return useAssetCatalog; +} + +// The bundle react-native-xcode.sh compiles packager image assets into +// (RNAssets.bundle, an actool-compiled asset catalog inside the app). +static NSBundle *__nullable RCTAssetCatalogBundle(void) +{ + static NSBundle *bundle; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSURL *bundleURL = [[NSBundle mainBundle] URLForResource:@"RNAssets" withExtension:@"bundle"]; + if (bundleURL != nil) { + bundle = [NSBundle bundleWithURL:bundleURL]; + } + }); + return bundle; +} + +NSString *__nullable RCTAssetCatalogNameForURL(NSURL *__nullable URL) +{ + // The "assets/" prefix the packager uses for all image assets. The CLI strips + // it from the identifiers it names the imagesets with. + const NSUInteger assetsPrefixLength = 7; + + NSString *path = RCTBundlePathForURL(URL); + // Packager assets always live under "assets/". Anything else (sub-bundles, + // CodePush/OTA assets outside the main bundle) is not in the catalog. + if (path == nil || ![path hasPrefix:@"assets/"]) { + return nil; + } + + // Other packager assets (gif, webp, ...) are copied as plain files and must + // use the regular loader. + if (!RCTIsImageAssetsPath(path)) { + return nil; + } + + // File system paths come back decomposed (NFD); restore the precomposed form + // the packager saw on disk so non-ASCII characters filter out the same way + // they do in the CLI's identifier. + path = path.precomposedStringWithCanonicalMapping; + + const NSUInteger length = path.length; + unichar stackBuffer[256]; + unichar *chars = length <= 256 ? stackBuffer : (unichar *)malloc(length * sizeof(unichar)); + [path getCharacters:chars range:NSMakeRange(0, length)]; + + // Strip the file extension (guaranteed present by RCTIsImageAssetsPath) and + // an optional "@x" suffix (integer or fractional, e.g. "@2x", + // "@1.5x"). The catalog stores a single imageset per image and resolves the + // scale by name at runtime, see + // https://developer.apple.com/documentation/xcode/managing-assets-with-asset-catalogs + NSUInteger end = length - 1; + while (end > assetsPrefixLength && chars[end] != '.') { + end--; + } + if (end > assetsPrefixLength && chars[end - 1] == 'x') { + // Walk back over "@(.)?" ending at the "x". + NSUInteger cursor = end - 1; + while (cursor > assetsPrefixLength && chars[cursor - 1] >= '0' && chars[cursor - 1] <= '9') { + cursor--; + } + if (cursor < end - 1) { + if (chars[cursor - 1] == '@') { + end = cursor - 1; + } else if (chars[cursor - 1] == '.') { + NSUInteger integerPart = cursor - 1; + while (integerPart > assetsPrefixLength && chars[integerPart - 1] >= '0' && chars[integerPart - 1] <= '9') { + integerPart--; + } + if (integerPart < cursor - 1 && chars[integerPart - 1] == '@') { + end = integerPart - 1; + } + } + } + } + + // Build the identifier in place in a single pass: skip the "assets/" prefix, + // lowercase, encode the folder structure with "_" and drop anything that is + // not a valid identifier character, producing the same identifier as + // getResourceIdentifier in the CLI, which names the imagesets. + NSUInteger resultLength = 0; + for (NSUInteger i = assetsPrefixLength; i < end; i++) { + unichar c = chars[i]; + if (c == '/') { + chars[resultLength++] = '_'; + } else if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_') { + chars[resultLength++] = c; + } else if (c >= 'A' && c <= 'Z') { + chars[resultLength++] = c + ('a' - 'A'); + } + } + + NSString *name = [NSString stringWithCharacters:chars length:resultLength]; + if (chars != stackBuffer) { + free(chars); + } + return name; +} + UIImage *__nullable RCTImageFromLocalBundleAssetURL(NSURL *imageURL) { if (![imageURL.scheme isEqualToString:@"file"]) { @@ -955,6 +1063,37 @@ BOOL RCTIsLocalAssetURL(NSURL *__nullable imageURL) UIImage *__nullable RCTImageFromLocalAssetURL(NSURL *imageURL) { + if (RCTUseAssetCatalog()) { + NSString *catalogName = RCTAssetCatalogNameForURL(imageURL); + if (catalogName != nil) { + // The app opted into the asset catalog and this is a packager asset, so it + // was compiled into RNAssets.bundle at build time. Trust the catalog and + // return directly, keeping the common path a single lookup with no + // filesystem fallback. Non-catalog assets (nil name) fall through below. + NSBundle *assetCatalogBundle = RCTAssetCatalogBundle(); + if (assetCatalogBundle == nil) { + // Passing a nil bundle to imageNamed:inBundle: would silently search the + // main bundle instead, potentially resolving an unrelated app image. + RCTLogError( + @"RCTUseAssetCatalog is enabled but RNAssets.bundle was not found in the app. Image assets must be " + "bundled by react-native-xcode.sh, which compiles them into the app at build time. (loading %@)", + imageURL); + return nil; + } + UIImage *image = [UIImage imageNamed:catalogName + inBundle:assetCatalogBundle + compatibleWithTraitCollection:nil]; + if (image == nil) { + RCTLogError( + @"Image \"%@\" (%@) was not found in the asset catalog. RCTUseAssetCatalog is enabled, " + "so image assets must be compiled into the app's RNAssets.bundle at build time.", + catalogName, + imageURL); + } + return image; + } + } + NSString *imageName = RCTBundlePathForURL(imageURL); NSBundle *bundle = nil; diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 06a58f116f60..27c66f664a45 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -107,6 +107,7 @@ "scripts/react_native_pods_utils/script_phases.sh", "scripts/react_native_pods.rb", "scripts/react-native-xcode.sh", + "scripts/xcode/asset-catalog.sh", "scripts/xcode/ccache-clang.sh", "scripts/xcode/ccache-clang++.sh", "scripts/xcode/ccache.conf", diff --git a/packages/react-native/scripts/react-native-xcode.sh b/packages/react-native/scripts/react-native-xcode.sh index 1dcdf55e1902..0a8b6c8de8a2 100755 --- a/packages/react-native/scripts/react-native-xcode.sh +++ b/packages/react-native/scripts/react-native-xcode.sh @@ -151,6 +151,13 @@ else EXTRA_ARGS+=("--config-cmd" "'$NODE_BINARY' $NODE_ARGS '$REACT_NATIVE_DIR/cli.js' config") fi +# shellcheck source=/dev/null +source "$REACT_NATIVE_DIR/scripts/xcode/asset-catalog.sh" +ASSET_CATALOG_STAGING_DIR="$(asset_catalog_staging_dir)" +if [[ -n "$ASSET_CATALOG_STAGING_DIR" ]]; then + EXTRA_ARGS+=("--asset-catalog-dest" "$ASSET_CATALOG_STAGING_DIR") +fi + # shellcheck disable=SC2086 "$NODE_BINARY" $NODE_ARGS "$CLI_PATH" $BUNDLE_COMMAND \ $CONFIG_ARG \ @@ -163,6 +170,8 @@ fi "${EXTRA_ARGS[@]}" \ $EXTRA_PACKAGER_ARGS +asset_catalog_compile "$ASSET_CATALOG_STAGING_DIR" + if [[ $USE_HERMES == false ]]; then cp "$BUNDLE_FILE" "$DEST/" BUNDLE_FILE="$DEST/$BUNDLE_NAME.jsbundle" diff --git a/packages/react-native/scripts/xcode/asset-catalog.sh b/packages/react-native/scripts/xcode/asset-catalog.sh new file mode 100644 index 000000000000..8ad58af1be92 --- /dev/null +++ b/packages/react-native/scripts/xcode/asset-catalog.sh @@ -0,0 +1,102 @@ +#!/bin/bash +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +# Bundles packager image assets into a compiled asset catalog (RNAssets.bundle) +# inside the app, where the native image loader resolves them by name. Sourced +# by react-native-xcode.sh. +# +# The feature is opt-in via the RCTUseAssetCatalog key in the app's Info.plist, +# which is the same key the native image loader reads, so bundling and runtime +# cannot disagree on where image assets live. The catalog is fully owned by +# these functions (staged in derived files, compiled with actool into the app's +# resources next to the js bundle), so the app's Xcode project needs no +# changes. Apps that have not migrated are unaffected. +# +# After changing the RCTUseAssetCatalog key, do a clean build: incremental +# builds do not remove image assets a previous build placed in the app with the +# other setting (they are unused but add dead weight). + +# Prints the directory the bundler should emit image assets into (via +# --asset-catalog-dest), or nothing if the app has not opted into the asset +# catalog with the RCTUseAssetCatalog Info.plist key. +asset_catalog_staging_dir() { + [[ "$BUNDLE_PLATFORM" == "ios" && -n "$PRODUCT_SETTINGS_PATH" ]] || return 0 + + local use_asset_catalog + use_asset_catalog="$(/usr/libexec/PlistBuddy -c 'Print :RCTUseAssetCatalog' "$PRODUCT_SETTINGS_PATH" 2>/dev/null || true)" + # Accept the value forms NSBundle's boolValue treats as true, so this check + # cannot disagree with the native runtime check. + case "$(echo "$use_asset_catalog" | tr '[:upper:]' '[:lower:]')" in + true | yes | 1) ;; + *) return 0 ;; + esac + + local staging_dir="${DERIVED_FILE_DIR:-$(mktemp -d)}/rn-assets" + rm -rf "$staging_dir" + mkdir -p "$staging_dir/RNAssets.xcassets" + printf '%s\n' "$staging_dir" +} + +# Compiles the staging catalog ($1, as returned by asset_catalog_staging_dir) +# into RNAssets.bundle inside the app. The compiled Assets.car resolves the +# right scale by name at runtime, see +# https://developer.apple.com/documentation/xcode/managing-assets-with-asset-catalogs +asset_catalog_compile() { + local staging_dir="$1" + local rn_assets_bundle="$DEST/RNAssets.bundle" + # Always remove first so a stale bundle from a previous build does not ship + # when the app opts out or has no image assets. + rm -rf "$rn_assets_bundle" + [[ -n "$staging_dir" ]] || return 0 + if [[ -z "$(find "$staging_dir/RNAssets.xcassets" -maxdepth 1 -name '*.imageset' -print -quit)" ]]; then + return 0 + fi + + mkdir -p "$rn_assets_bundle" + local actool_args=("--platform" "${PLATFORM_NAME:-iphoneos}") + # These are always iOS-family image assets (this runs only for BUNDLE_PLATFORM + # "ios", which includes Mac Catalyst), so the deployment target must be an iOS + # version. On Catalyst the platform is macosx but MACOSX_DEPLOYMENT_TARGET is a + # macOS version (e.g. 10.15); passing that as the target makes actool silently + # emit loose files instead of a compiled Assets.car, so it must not be used. + actool_args+=("--minimum-deployment-target" "${IPHONEOS_DEPLOYMENT_TARGET:-15.1}") + case "${TARGETED_DEVICE_FAMILY:-1}" in *1*) actool_args+=("--target-device" "iphone") ;; esac + case "${TARGETED_DEVICE_FAMILY:-1}" in *2*) actool_args+=("--target-device" "ipad") ;; esac + if [[ "${IS_MACCATALYST:-NO}" == "YES" ]]; then + actool_args+=("--ui-framework-family" "uikit") + fi + + # Surface actool diagnostics in the build log: without these flags actool + # suppresses them entirely, and it exits 0 even when it drops an imageset. + local actool_output + actool_output="$(xcrun actool "$staging_dir/RNAssets.xcassets" \ + --compile "$rn_assets_bundle" \ + --output-format human-readable-text \ + --errors --warnings --notices \ + "${actool_args[@]}" 2>&1)" || true + echo "$actool_output" + if [[ ! -f "$rn_assets_bundle/Assets.car" ]]; then + echo "error: failed to compile image assets into RNAssets.bundle. See actool output above." >&2 + exit 2 + fi + + cat > "$rn_assets_bundle/Info.plist" <<'RN_ASSETS_PLIST' + + + + + CFBundleIdentifier + org.reactjs.RNAssets + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + RNAssets + CFBundlePackageType + BNDL + + +RN_ASSETS_PLIST +} diff --git a/packages/rn-tester/RNTester/Info.plist b/packages/rn-tester/RNTester/Info.plist index 373b8322fdc7..b785e4857907 100644 --- a/packages/rn-tester/RNTester/Info.plist +++ b/packages/rn-tester/RNTester/Info.plist @@ -48,6 +48,8 @@ You need to add NSPhotoLibraryUsageDescription key in Info.plist to enable photo library usage, otherwise it is going to *fail silently*! RCTNewArchEnabled + RCTUseAssetCatalog + UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities diff --git a/packages/rn-tester/RNTesterUnitTests/RCTURLUtilsTests.m b/packages/rn-tester/RNTesterUnitTests/RCTURLUtilsTests.m index 318ed4890381..7128377b5de1 100644 --- a/packages/rn-tester/RNTesterUnitTests/RCTURLUtilsTests.m +++ b/packages/rn-tester/RNTesterUnitTests/RCTURLUtilsTests.m @@ -100,4 +100,97 @@ - (void)testIsLocalAssetsURLParam XCTAssertFalse(RCTIsLocalAssetURL(otherAssetsURL)); } +- (void)testAssetCatalogNameForURL +{ + NSString *resourcePath = [[NSBundle mainBundle] resourcePath]; + + // Nested folder + "@2x" scale suffix: folders are encoded into the name, the + // scale suffix and extension are stripped, and the "assets_" prefix removed. + NSURL *scaledURL = + [NSURL fileURLWithPath:[resourcePath stringByAppendingPathComponent:@"assets/AwesomeModule/icon@2x.png"]]; + XCTAssertEqualObjects(RCTAssetCatalogNameForURL(scaledURL), @"awesomemodule_icon"); + + // Same asset without a scale suffix resolves to the same name. + NSURL *unscaledURL = + [NSURL fileURLWithPath:[resourcePath stringByAppendingPathComponent:@"assets/AwesomeModule/icon.png"]]; + XCTAssertEqualObjects(RCTAssetCatalogNameForURL(unscaledURL), @"awesomemodule_icon"); + + // Illegal characters (e.g. "-") are stripped, matching the CLI identifier. + NSURL *illegalCharsURL = + [NSURL fileURLWithPath:[resourcePath stringByAppendingPathComponent:@"assets/my-module/my-icon@3x.png"]]; + XCTAssertEqualObjects(RCTAssetCatalogNameForURL(illegalCharsURL), @"mymodule_myicon"); + + // Non-integer scale suffixes are also stripped. + NSURL *fractionalScaleURL = + [NSURL fileURLWithPath:[resourcePath stringByAppendingPathComponent:@"assets/AwesomeModule/icon@1.5x.png"]]; + XCTAssertEqualObjects(RCTAssetCatalogNameForURL(fractionalScaleURL), @"awesomemodule_icon"); + + // Fractional scale with a leading zero (the packager emits e.g. "@0.5x"). + NSURL *zeroFractionalScaleURL = + [NSURL fileURLWithPath:[resourcePath stringByAppendingPathComponent:@"assets/AwesomeModule/icon@0.5x.png"]]; + XCTAssertEqualObjects(RCTAssetCatalogNameForURL(zeroFractionalScaleURL), @"awesomemodule_icon"); + + // An uppercase "X" is not a scale suffix (the packager's scale format is + // case-sensitive), so it stays in the name like any other character. + NSURL *uppercaseScaleURL = + [NSURL fileURLWithPath:[resourcePath stringByAppendingPathComponent:@"assets/AwesomeModule/icon@2X.png"]]; + XCTAssertEqualObjects(RCTAssetCatalogNameForURL(uppercaseScaleURL), @"awesomemodule_icon2x"); + + // Only the extension is stripped from a name containing dots; the inner dot + // is an illegal identifier character and is removed. + NSURL *multiDotURL = + [NSURL fileURLWithPath:[resourcePath stringByAppendingPathComponent:@"assets/AwesomeModule/photo.small.png"]]; + XCTAssertEqualObjects(RCTAssetCatalogNameForURL(multiDotURL), @"awesomemodule_photosmall"); + + // Non-ASCII characters are not valid identifier characters and are removed. + // File system paths are decomposed (NFD), so this also verifies the name is + // normalized back to the precomposed form the CLI derived the identifier + // from ("ü" must not leave a stray "u" behind). + NSURL *unicodeURL = + [NSURL fileURLWithPath:[resourcePath stringByAppendingPathComponent:@"assets/AwesomeModule/ünïcode.png"]]; + XCTAssertEqualObjects(RCTAssetCatalogNameForURL(unicodeURL), @"awesomemodule_ncode"); + + // Uppercase extensions are not catalog assets: the CLI's isCatalogAsset + // check is case-sensitive, so these are copied as plain files. + NSURL *uppercaseExtensionURL = + [NSURL fileURLWithPath:[resourcePath stringByAppendingPathComponent:@"assets/AwesomeModule/Icon.PNG"]]; + XCTAssertNil(RCTAssetCatalogNameForURL(uppercaseExtensionURL)); + + // A packager asset without an image extension is not a catalog asset. + NSURL *noExtensionURL = + [NSURL fileURLWithPath:[resourcePath stringByAppendingPathComponent:@"assets/AwesomeModule/data"]]; + XCTAssertNil(RCTAssetCatalogNameForURL(noExtensionURL)); + + // ".jpeg" is a catalog image type (matching the CLI's isCatalogAsset). + NSURL *jpegURL = + [NSURL fileURLWithPath:[resourcePath stringByAppendingPathComponent:@"assets/AwesomeModule/photo@2x.jpeg"]]; + XCTAssertEqualObjects(RCTAssetCatalogNameForURL(jpegURL), @"awesomemodule_photo"); + + // Non-catalog image types (the CLI only emits png/jpg/jpeg into the catalog) + // are not catalog assets and use the regular loader. + NSURL *gifURL = + [NSURL fileURLWithPath:[resourcePath stringByAppendingPathComponent:@"assets/AwesomeModule/anim.gif"]]; + XCTAssertNil(RCTAssetCatalogNameForURL(gifURL)); + + // Assets outside the project root are encoded with "_" by the packager + // (e.g. "assets/../../shared" -> "assets/__shared") and resolve to the same + // identifier the CLI generates. + NSURL *outOfRootURL = + [NSURL fileURLWithPath:[resourcePath stringByAppendingPathComponent:@"assets/__shared/icon@2x.png"]]; + XCTAssertEqualObjects(RCTAssetCatalogNameForURL(outOfRootURL), @"__shared_icon"); + + // Query strings are ignored, same as the legacy loader. + NSURL *queryURL = [NSURL + URLWithString:[NSString stringWithFormat:@"file://%@/assets/AwesomeModule/icon@2x.png?platform=ios&hash=abc", + resourcePath]]; + XCTAssertEqualObjects(RCTAssetCatalogNameForURL(queryURL), @"awesomemodule_icon"); + + // A non-packager path (not under "assets/") is not a catalog asset. + NSURL *notPackagerURL = [NSURL fileURLWithPath:[resourcePath stringByAppendingPathComponent:@"icon.png"]]; + XCTAssertNil(RCTAssetCatalogNameForURL(notPackagerURL)); + + // A nil URL is handled gracefully. + XCTAssertNil(RCTAssetCatalogNameForURL(nil)); +} + @end diff --git a/private/helloworld/ios/HelloWorld.xcodeproj/project.pbxproj b/private/helloworld/ios/HelloWorld.xcodeproj/project.pbxproj index dca49419fb7c..b36116e685e8 100644 --- a/private/helloworld/ios/HelloWorld.xcodeproj/project.pbxproj +++ b/private/helloworld/ios/HelloWorld.xcodeproj/project.pbxproj @@ -265,7 +265,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - 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"; + 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"; }; A44ED3CC3037C88F69E3AF15 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; diff --git a/private/helloworld/ios/HelloWorld/Info.plist b/private/helloworld/ios/HelloWorld/Info.plist index 07b050898d9d..a766cc2814e5 100644 --- a/private/helloworld/ios/HelloWorld/Info.plist +++ b/private/helloworld/ios/HelloWorld/Info.plist @@ -34,6 +34,8 @@ NSLocationWhenInUseUsageDescription + RCTUseAssetCatalog + UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities