Skip to content

Commit dce8343

Browse files
Use asset catalog for iOS images
Load packager image assets from an iOS asset catalog (RNAssets.xcassets) instead of loose files in the app bundle. The CLI bundler emits imagesets into the catalog at build time (via --asset-catalog-dest, already in @react-native/community-cli-plugin); Xcode compiles them into Assets.car and the native image loader resolves them by name with [UIImage imageNamed:]. The catalog path is gated on the RCTUseAssetCatalog Info.plist key, a build-time constant read once. Apps that have not migrated are unaffected and the catalog path is a single lookup with no filesystem fallback. Only main-bundle packager assets resolve to a catalog name, so OTA/sub-bundle assets fall through to the existing loader. react-native-xcode.sh passes --asset-catalog-dest when an RNAssets.xcassets exists next to the app's Info.plist. In rn-tester the "Build JS Bundle" phase is ordered before Resources and declares the catalog as an output so the new Xcode build system compiles it after it is populated (asset symbol generation is disabled on the target to avoid a dependency cycle with that step). Cold image loads in RNTester are ~15x faster (median 47us vs 719us) and Apple app thinning can strip unused scales.
1 parent e04ff69 commit dce8343

8 files changed

Lines changed: 152 additions & 27 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ DerivedData
2222
project.xcworkspace
2323
**/.xcode.env.local
2424

25+
# Generated iOS asset catalog imagesets (produced at build time from JS assets)
26+
**/RNAssets.xcassets/*.imageset
27+
2528
# Gradle
2629
/build/
2730
/packages/rn-tester/build

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: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -939,6 +939,69 @@ BOOL RCTIsLocalAssetURL(NSURL *__nullable imageURL)
939939
return bundleCache[key];
940940
}
941941

942+
// Whether the app was built to embed packager image assets in the iOS asset
943+
// catalog (RNAssets.xcassets). This is a build-time property, set by the app via
944+
// the "RCTUseAssetCatalog" key in Info.plist, alongside the CLI's
945+
// --asset-catalog-dest bundling flag. It is read once and cached.
946+
static BOOL RCTUseAssetCatalog(void)
947+
{
948+
static BOOL useAssetCatalog = NO;
949+
static dispatch_once_t onceToken;
950+
dispatch_once(&onceToken, ^{
951+
useAssetCatalog = [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"RCTUseAssetCatalog"] boolValue];
952+
});
953+
return useAssetCatalog;
954+
}
955+
956+
NSString *__nullable RCTAssetCatalogNameForURL(NSURL *__nullable URL)
957+
{
958+
NSString *path = RCTBundlePathForURL(URL);
959+
// Packager assets always live under "assets/". Anything else (sub-bundles,
960+
// CodePush/OTA assets outside the main bundle) is not in the catalog.
961+
if (path == nil || ![path hasPrefix:@"assets/"]) {
962+
return nil;
963+
}
964+
965+
// Strip the optional "@Nx" scale suffix and the file extension in a single
966+
// pass. Asset catalog images resolve the correct scale by name at runtime.
967+
// See https://developer.apple.com/documentation/xcode/asset-catalogs
968+
static NSRegularExpression *scaleAndExtensionRegex;
969+
static dispatch_once_t scaleOnceToken;
970+
dispatch_once(&scaleOnceToken, ^{
971+
scaleAndExtensionRegex = [NSRegularExpression regularExpressionWithPattern:@"(@\\d+x)?\\.\\w+$" options:0 error:nil];
972+
});
973+
path = [scaleAndExtensionRegex stringByReplacingMatchesInString:path
974+
options:0
975+
range:NSMakeRange(0, path.length)
976+
withTemplate:@""];
977+
978+
// Lowercase and encode the folder structure into the name.
979+
path = [path.lowercaseString stringByReplacingOccurrencesOfString:@"/" withString:@"_"];
980+
981+
// Remove characters that are not allowed in an asset catalog identifier.
982+
static NSCharacterSet *illegalCharacters;
983+
static dispatch_once_t illegalOnceToken;
984+
dispatch_once(&illegalOnceToken, ^{
985+
illegalCharacters =
986+
[[NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyz0123456789_"] invertedSet];
987+
});
988+
path = [[path componentsSeparatedByCharactersInSet:illegalCharacters] componentsJoinedByString:@""];
989+
990+
// Remove the "assets_" (or "assetsunstable_path_") prefix.
991+
static NSRegularExpression *prefixRegex;
992+
static dispatch_once_t prefixOnceToken;
993+
dispatch_once(&prefixOnceToken, ^{
994+
prefixRegex = [NSRegularExpression regularExpressionWithPattern:@"^(assetsunstable_path|assets)_" options:0
995+
error:nil];
996+
});
997+
path = [prefixRegex stringByReplacingMatchesInString:path
998+
options:0
999+
range:NSMakeRange(0, path.length)
1000+
withTemplate:@""];
1001+
1002+
return path;
1003+
}
1004+
9421005
UIImage *__nullable RCTImageFromLocalBundleAssetURL(NSURL *imageURL)
9431006
{
9441007
if (![imageURL.scheme isEqualToString:@"file"]) {
@@ -955,6 +1018,17 @@ BOOL RCTIsLocalAssetURL(NSURL *__nullable imageURL)
9551018

9561019
UIImage *__nullable RCTImageFromLocalAssetURL(NSURL *imageURL)
9571020
{
1021+
if (RCTUseAssetCatalog()) {
1022+
NSString *catalogName = RCTAssetCatalogNameForURL(imageURL);
1023+
if (catalogName != nil) {
1024+
// The app opted into the asset catalog and this is a packager asset, so it
1025+
// was compiled into RNAssets.xcassets at build time. Trust the catalog and
1026+
// return directly, keeping the common path a single lookup with no
1027+
// filesystem fallback. Non-catalog assets (nil name) fall through below.
1028+
return [UIImage imageNamed:catalogName];
1029+
}
1030+
}
1031+
9581032
NSString *imageName = RCTBundlePathForURL(imageURL);
9591033

9601034
NSBundle *bundle = nil;

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

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

154+
# iOS asset catalog: if the app has an RNAssets.xcassets next to its Info.plist,
155+
# emit image assets into it so Xcode compiles them into Assets.car and Apple app
156+
# thinning can strip unused scales. Presence of the catalog is the opt-in signal,
157+
# so apps that have not migrated are unaffected. This also covers Mac Catalyst
158+
# (BUNDLE_PLATFORM is forced to "ios" above). Pair with RCTUseAssetCatalog=YES in
159+
# the app's Info.plist so the native side loads from the catalog.
160+
if [[ "$BUNDLE_PLATFORM" == "ios" && -n "$PRODUCT_SETTINGS_PATH" ]]; then
161+
ASSET_CATALOG_DIR="$(dirname "$PRODUCT_SETTINGS_PATH")"
162+
if [[ -d "$ASSET_CATALOG_DIR/RNAssets.xcassets" ]]; then
163+
EXTRA_ARGS+=("--asset-catalog-dest" "$ASSET_CATALOG_DIR")
164+
fi
165+
fi
166+
154167
# shellcheck disable=SC2086
155168
"$NODE_BINARY" $NODE_ARGS "$CLI_PATH" $BUNDLE_COMMAND \
156169
$CONFIG_ARG \

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>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"info" : {
3+
"author" : "xcode",
4+
"version" : 1
5+
}
6+
}

packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
0CF641B628ECB21C00DCDD11 /* RNAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0CF641B528ECB21C00DCDD11 /* RNAssets.xcassets */; };
1011
0EA618032BE537D3001875EF /* RNTesterBundle.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 0EA618022BE537D3001875EF /* RNTesterBundle.bundle */; };
1112
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
1213
2DDEF0101F84BF7B00DBDF73 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2DDEF00F1F84BF7B00DBDF73 /* Images.xcassets */; };
@@ -74,6 +75,7 @@
7475
/* End PBXContainerItemProxy section */
7576

7677
/* Begin PBXFileReference section */
78+
0CF641B528ECB21C00DCDD11 /* RNAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = RNAssets.xcassets; path = RNTester/RNAssets.xcassets; sourceTree = "<group>"; };
7779
0EA618022BE537D3001875EF /* RNTesterBundle.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; name = RNTesterBundle.bundle; path = RNTester/RNTesterBundle.bundle; sourceTree = "<group>"; };
7880
13B07F961A680F5B00A75B9A /* RNTester.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RNTester.app; sourceTree = BUILT_PRODUCTS_DIR; };
7981
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = RNTester/AppDelegate.h; sourceTree = "<group>"; };
@@ -211,6 +213,7 @@
211213
13B07FB71A68108700A75B9A /* main.m */,
212214
832F45BA2A8A6E1F0097B4E6 /* SwiftTest.swift */,
213215
2DDEF00F1F84BF7B00DBDF73 /* Images.xcassets */,
216+
0CF641B528ECB21C00DCDD11 /* RNAssets.xcassets */,
214217
8145AE05241172D900A3F8DA /* LaunchScreen.storyboard */,
215218
680759612239798500290469 /* Fabric */,
216219
272E6B3A1BEA846C001FCF37 /* NativeExampleViews */,
@@ -366,8 +369,8 @@
366369
F28F13DD10D40D98C0BB7BE8 /* [CP] Check Pods Manifest.lock */,
367370
13B07F871A680F5B00A75B9A /* Sources */,
368371
13B07F8C1A680F5B00A75B9A /* Frameworks */,
369-
13B07F8E1A680F5B00A75B9A /* Resources */,
370372
68CD48B71D2BCB2C007E06A9 /* Build JS Bundle */,
373+
13B07F8E1A680F5B00A75B9A /* Resources */,
371374
79E8BE2B119D4C5CCD2F04B3 /* [RN] Copy Hermes Framework */,
372375
17FE348EDF12252D972FFC2F /* [CP] Embed Pods Frameworks */,
373376
DFEE284B22AD5E88BBF1026A /* [CP] Copy Pods Resources */,
@@ -470,6 +473,7 @@
470473
buildActionMask = 2147483647;
471474
files = (
472475
2DDEF0101F84BF7B00DBDF73 /* Images.xcassets in Resources */,
476+
0CF641B628ECB21C00DCDD11 /* RNAssets.xcassets in Resources */,
473477
8145AE06241172D900A3F8DA /* LaunchScreen.storyboard in Resources */,
474478
0EA618032BE537D3001875EF /* RNTesterBundle.bundle in Resources */,
475479
F0D621C32BBB9E38005960AC /* PrivacyInfo.xcprivacy in Resources */,
@@ -503,14 +507,10 @@
503507
inputFileListPaths = (
504508
"${PODS_ROOT}/Target Support Files/Pods-RNTester/Pods-RNTester-frameworks-${CONFIGURATION}-input-files.xcfilelist",
505509
);
506-
inputPaths = (
507-
);
508510
name = "[CP] Embed Pods Frameworks";
509511
outputFileListPaths = (
510512
"${PODS_ROOT}/Target Support Files/Pods-RNTester/Pods-RNTester-frameworks-${CONFIGURATION}-output-files.xcfilelist",
511513
);
512-
outputPaths = (
513-
);
514514
runOnlyForDeploymentPostprocessing = 0;
515515
shellPath = /bin/sh;
516516
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RNTester/Pods-RNTester-frameworks.sh\"\n";
@@ -546,14 +546,10 @@
546546
inputFileListPaths = (
547547
"${PODS_ROOT}/Target Support Files/Pods-RNTesterIntegrationTests/Pods-RNTesterIntegrationTests-resources-${CONFIGURATION}-input-files.xcfilelist",
548548
);
549-
inputPaths = (
550-
);
551549
name = "[CP] Copy Pods Resources";
552550
outputFileListPaths = (
553551
"${PODS_ROOT}/Target Support Files/Pods-RNTesterIntegrationTests/Pods-RNTesterIntegrationTests-resources-${CONFIGURATION}-output-files.xcfilelist",
554552
);
555-
outputPaths = (
556-
);
557553
runOnlyForDeploymentPostprocessing = 0;
558554
shellPath = /bin/sh;
559555
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RNTesterIntegrationTests/Pods-RNTesterIntegrationTests-resources.sh\"\n";
@@ -567,21 +563,18 @@
567563
inputFileListPaths = (
568564
"${PODS_ROOT}/Target Support Files/Pods-RNTesterUnitTests/Pods-RNTesterUnitTests-resources-${CONFIGURATION}-input-files.xcfilelist",
569565
);
570-
inputPaths = (
571-
);
572566
name = "[CP] Copy Pods Resources";
573567
outputFileListPaths = (
574568
"${PODS_ROOT}/Target Support Files/Pods-RNTesterUnitTests/Pods-RNTesterUnitTests-resources-${CONFIGURATION}-output-files.xcfilelist",
575569
);
576-
outputPaths = (
577-
);
578570
runOnlyForDeploymentPostprocessing = 0;
579571
shellPath = /bin/sh;
580572
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RNTesterUnitTests/Pods-RNTesterUnitTests-resources.sh\"\n";
581573
showEnvVarsInLog = 0;
582574
};
583575
68CD48B71D2BCB2C007E06A9 /* Build JS Bundle */ = {
584576
isa = PBXShellScriptBuildPhase;
577+
alwaysOutOfDate = 1;
585578
buildActionMask = 2147483647;
586579
files = (
587580
);
@@ -591,6 +584,7 @@
591584
);
592585
name = "Build JS Bundle";
593586
outputPaths = (
587+
"$(SRCROOT)/RNTester/RNAssets.xcassets",
594588
);
595589
runOnlyForDeploymentPostprocessing = 0;
596590
shellPath = /bin/sh;
@@ -622,14 +616,10 @@
622616
inputFileListPaths = (
623617
"${PODS_ROOT}/Target Support Files/Pods-RNTesterIntegrationTests/Pods-RNTesterIntegrationTests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
624618
);
625-
inputPaths = (
626-
);
627619
name = "[CP] Embed Pods Frameworks";
628620
outputFileListPaths = (
629621
"${PODS_ROOT}/Target Support Files/Pods-RNTesterIntegrationTests/Pods-RNTesterIntegrationTests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
630622
);
631-
outputPaths = (
632-
);
633623
runOnlyForDeploymentPostprocessing = 0;
634624
shellPath = /bin/sh;
635625
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RNTesterIntegrationTests/Pods-RNTesterIntegrationTests-frameworks.sh\"\n";
@@ -665,14 +655,10 @@
665655
inputFileListPaths = (
666656
"${PODS_ROOT}/Target Support Files/Pods-RNTester/Pods-RNTester-resources-${CONFIGURATION}-input-files.xcfilelist",
667657
);
668-
inputPaths = (
669-
);
670658
name = "[CP] Copy Pods Resources";
671659
outputFileListPaths = (
672660
"${PODS_ROOT}/Target Support Files/Pods-RNTester/Pods-RNTester-resources-${CONFIGURATION}-output-files.xcfilelist",
673661
);
674-
outputPaths = (
675-
);
676662
runOnlyForDeploymentPostprocessing = 0;
677663
shellPath = /bin/sh;
678664
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RNTester/Pods-RNTester-resources.sh\"\n";
@@ -708,14 +694,10 @@
708694
inputFileListPaths = (
709695
"${PODS_ROOT}/Target Support Files/Pods-RNTesterUnitTests/Pods-RNTesterUnitTests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
710696
);
711-
inputPaths = (
712-
);
713697
name = "[CP] Embed Pods Frameworks";
714698
outputFileListPaths = (
715699
"${PODS_ROOT}/Target Support Files/Pods-RNTesterUnitTests/Pods-RNTesterUnitTests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
716700
);
717-
outputPaths = (
718-
);
719701
runOnlyForDeploymentPostprocessing = 0;
720702
shellPath = /bin/sh;
721703
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RNTesterUnitTests/Pods-RNTesterUnitTests-frameworks.sh\"\n";
@@ -801,6 +783,7 @@
801783
baseConfigurationReference = CA59C9994B1822826D8983F0 /* Pods-RNTester.debug.xcconfig */;
802784
buildSettings = {
803785
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
786+
ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS = NO;
804787
CLANG_ENABLE_MODULES = YES;
805788
DEVELOPMENT_TEAM = "";
806789
HEADER_SEARCH_PATHS = (
@@ -839,6 +822,7 @@
839822
baseConfigurationReference = 51BC9297B6C3163C14532020 /* Pods-RNTester.release.xcconfig */;
840823
buildSettings = {
841824
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
825+
ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS = NO;
842826
CLANG_ENABLE_MODULES = YES;
843827
DEVELOPMENT_TEAM = "";
844828
EXCLUDED_ARCHS = "";
@@ -947,19 +931,24 @@
947931
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
948932
MTL_ENABLE_DEBUG_INFO = YES;
949933
ONLY_ACTIVE_ARCH = YES;
950-
OTHER_CFLAGS = "$(inherited)";
934+
OTHER_CFLAGS = (
935+
"$(inherited)",
936+
"-DRCT_REMOVE_LEGACY_ARCH=1",
937+
);
951938
OTHER_CPLUSPLUSFLAGS = (
952939
"$(OTHER_CFLAGS)",
953940
"-DFOLLY_NO_CONFIG",
954941
"-DFOLLY_MOBILE=1",
955942
"-DFOLLY_USE_LIBCPP=1",
956943
"-DFOLLY_CFG_NO_COROUTINES=1",
957944
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
945+
"-DRCT_REMOVE_LEGACY_ARCH=1",
958946
);
959947
OTHER_LDFLAGS = (
960948
"-ObjC",
961949
"-lc++",
962950
);
951+
PODFILE_DIR = "$(SRCROOT)";
963952
REACT_NATIVE_PATH = "${PODS_ROOT}/../../react-native";
964953
SDKROOT = iphoneos;
965954
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
@@ -1040,19 +1029,24 @@
10401029
);
10411030
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
10421031
MTL_ENABLE_DEBUG_INFO = NO;
1043-
OTHER_CFLAGS = "$(inherited)";
1032+
OTHER_CFLAGS = (
1033+
"$(inherited)",
1034+
"-DRCT_REMOVE_LEGACY_ARCH=1",
1035+
);
10441036
OTHER_CPLUSPLUSFLAGS = (
10451037
"$(OTHER_CFLAGS)",
10461038
"-DFOLLY_NO_CONFIG",
10471039
"-DFOLLY_MOBILE=1",
10481040
"-DFOLLY_USE_LIBCPP=1",
10491041
"-DFOLLY_CFG_NO_COROUTINES=1",
10501042
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
1043+
"-DRCT_REMOVE_LEGACY_ARCH=1",
10511044
);
10521045
OTHER_LDFLAGS = (
10531046
"-ObjC",
10541047
"-lc++",
10551048
);
1049+
PODFILE_DIR = "$(SRCROOT)";
10561050
REACT_NATIVE_PATH = "${PODS_ROOT}/../../react-native";
10571051
SDKROOT = iphoneos;
10581052
USE_HERMES = true;

packages/rn-tester/RNTesterUnitTests/RCTURLUtilsTests.m

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,32 @@ - (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+
// A non-packager path (not under "assets/") is not a catalog asset.
124+
NSURL *notPackagerURL = [NSURL fileURLWithPath:[resourcePath stringByAppendingPathComponent:@"icon.png"]];
125+
XCTAssertNil(RCTAssetCatalogNameForURL(notPackagerURL));
126+
127+
// A nil URL is handled gracefully.
128+
XCTAssertNil(RCTAssetCatalogNameForURL(nil));
129+
}
130+
103131
@end

0 commit comments

Comments
 (0)