From a4e0f09d2110f3412d5997464a5ab896109ca9c0 Mon Sep 17 00:00:00 2001
From: Janic Duplessis <janicduplessis@gmail.com>
Date: Wed, 7 Oct 2020 17:13:13 -0400
Subject: [PATCH 1/2] Use asset catalog for ios images

---
 .gitignore                                     |   3 +++
 .../react-native-codegen-0.0.5.tgz             | Bin 0 -> 125030 bytes
 .../Libraries/Image/AssetSourceResolver.js     |  17 ++++++++++++++++-
 .../Image/__tests__/resolveAssetSource-test.js |   4 ++--
 packages/react-native/React/Base/RCTUtils.m    |   5 ++++-
 .../react-native/scripts/react-native-xcode.sh |   5 +++++
 packages/react-native/template/_gitignore      |   3 +++
 .../ios/HelloWorld.xcodeproj/project.pbxproj   |   8 ++++++--
 .../RNTester/RNAssets.xcassets/Contents.json   |   6 ++++++
 .../RNTesterPods.xcodeproj/project.pbxproj     |   6 +++++-
 .../HelloWorld/RNAssets.xcassets/Contents.json |   6 ++++++
 11 files changed, 56 insertions(+), 7 deletions(-)
 create mode 100644 packages/react-native-codegen/react-native-codegen-0.0.5.tgz
 create mode 100644 packages/rn-tester/RNTester/RNAssets.xcassets/Contents.json
 create mode 100644 template/ios/HelloWorld/RNAssets.xcassets/Contents.json

diff --git a/.gitignore b/.gitignore
index 491d740ad1a..f8e44d4d2e8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -119,6 +119,9 @@ package-lock.json
 !/packages/rn-tester/Pods/__offline_mirrors_hermes__
 !/packages/rn-tester/Pods/__offline_mirrors_jsc__
 
+# Generated asset catalog
+/packages/rn-tester/RNTester/RNAssets.xcassets/*.imageset
+
 # @react-native/codegen
 /packages/react-native/React/FBReactNativeSpec/FBReactNativeSpec
 /packages/react-native-codegen/lib
diff --git a/packages/react-native-codegen/react-native-codegen-0.0.5.tgz b/packages/react-native-codegen/react-native-codegen-0.0.5.tgz
new file mode 100644
index 0000000000000000000000000000000000000000..ad4634539a778a71f554fd8009d701154b15c25e
GIT binary patch
diff --git a/packages/react-native/Libraries/Image/AssetSourceResolver.js b/packages/react-native/Libraries/Image/AssetSourceResolver.js
index d7ae700437b..c4867e72358 100644
--- a/packages/react-native/Libraries/Image/AssetSourceResolver.js
+++ b/packages/react-native/Libraries/Image/AssetSourceResolver.js
@@ -71,6 +71,15 @@ class AssetSourceResolver {
     return !!(this.jsbundleUrl && this.jsbundleUrl.startsWith('file://'));
   }
 
+  isCatalogAsset(): boolean {
+    return (
+      this.asset.__packager_asset &&
+      (this.asset.type === 'png' ||
+        this.asset.type === 'jpg' ||
+        this.asset.type === 'jpeg')
+    );
+  }
+
   defaultAsset(): ResolvedAssetSource {
     if (this.isLoadedFromServer()) {
       return this.assetServerURL();
@@ -81,7 +90,9 @@ class AssetSourceResolver {
         ? this.drawableFolderInBundle()
         : this.resourceIdentifierWithoutScale();
     } else {
-      return this.scaledAssetURLNearBundle();
+      return this.isCatalogAsset()
+        ? this.assetFromCatalog()
+        : this.scaledAssetURLNearBundle();
     }
   }
 
@@ -123,6 +134,10 @@ class AssetSourceResolver {
     );
   }
 
+  assetFromCatalog(): ResolvedAssetSource {
+    return this.fromSource(getAndroidResourceIdentifier(this.asset));
+  }
+
   /**
    * The default location of assets bundled with the app, located by
    * resource identifier
diff --git a/packages/react-native/Libraries/Image/__tests__/resolveAssetSource-test.js b/packages/react-native/Libraries/Image/__tests__/resolveAssetSource-test.js
index d05f0b7aba3..8b8a9dd79e8 100644
--- a/packages/react-native/Libraries/Image/__tests__/resolveAssetSource-test.js
+++ b/packages/react-native/Libraries/Image/__tests__/resolveAssetSource-test.js
@@ -136,7 +136,7 @@ describe('resolveAssetSource', () => {
           __packager_asset: true,
           width: 100,
           height: 200,
-          uri: 'file:///Path/To/Sample.app/assets/module/a/logo.png',
+          uri: 'module_a_logo',
           scale: 1,
         },
       );
@@ -159,7 +159,7 @@ describe('resolveAssetSource', () => {
           __packager_asset: true,
           width: 100,
           height: 200,
-          uri: 'file:///Path/To/Sample.app/assets/__module/a/logo.png',
+          uri: '__module_a_logo',
           scale: 1,
         },
       );
diff --git a/packages/react-native/React/Base/RCTUtils.m b/packages/react-native/React/Base/RCTUtils.m
index 4e862e9c6c3..51a20dac918 100644
--- a/packages/react-native/React/Base/RCTUtils.m
+++ b/packages/react-native/React/Base/RCTUtils.m
@@ -730,8 +730,11 @@ BOOL RCTIsGzippedData(NSData *__nullable data)
 
 static BOOL RCTIsImageAssetsPath(NSString *path)
 {
+  if (path == nil) {
+    return NO;
+  }
   NSString *extension = [path pathExtension];
-  return [extension isEqualToString:@"png"] || [extension isEqualToString:@"jpg"];
+  return extension.length == 0 || [extension isEqualToString:@"png"] || [extension isEqualToString:@"jpg"];
 }
 
 BOOL RCTIsBundleAssetURL(NSURL *__nullable imageURL)
diff --git a/packages/react-native/scripts/react-native-xcode.sh b/packages/react-native/scripts/react-native-xcode.sh
index 9f80ef20246..d0225d9a7ee 100755
--- a/packages/react-native/scripts/react-native-xcode.sh
+++ b/packages/react-native/scripts/react-native-xcode.sh
@@ -152,6 +152,10 @@ if [[ $USE_HERMES != false && $DEV == false ]]; then
   EXTRA_ARGS="$EXTRA_ARGS --minify false"
 fi
 
+# PRODUCT_SETTINGS_PATH is where the target Info.plist file is. The asset
+# catalog will be in the same folder.
+ASSET_CATALOG_DEST=${ASSET_CATALOG_DEST:-"$(dirname "$PRODUCT_SETTINGS_PATH")"}
+
 "$NODE_BINARY" $NODE_ARGS "$CLI_PATH" $BUNDLE_COMMAND \
   $CONFIG_ARG \
   --entry-file "$ENTRY_FILE" \
@@ -160,6 +164,7 @@ fi
   --reset-cache \
   --bundle-output "$BUNDLE_FILE" \
   --assets-dest "$DEST" \
+  --asset-catalog-dest "$ASSET_CATALOG_DEST" \
   $EXTRA_ARGS \
   $EXTRA_PACKAGER_ARGS
 
diff --git a/packages/react-native/template/_gitignore b/packages/react-native/template/_gitignore
index 0cab2ac6fc4..e2bfd3b99d8 100644
--- a/packages/react-native/template/_gitignore
+++ b/packages/react-native/template/_gitignore
@@ -64,3 +64,6 @@ yarn-error.log
 
 # testing
 /coverage
+
+# Generated asset catalog
+**/RNAssets.xcassets/*.imageset
diff --git a/packages/react-native/template/ios/HelloWorld.xcodeproj/project.pbxproj b/packages/react-native/template/ios/HelloWorld.xcodeproj/project.pbxproj
index afc51486e31..ad82047cc77 100644
--- a/packages/react-native/template/ios/HelloWorld.xcodeproj/project.pbxproj
+++ b/packages/react-native/template/ios/HelloWorld.xcodeproj/project.pbxproj
@@ -10,7 +10,8 @@
 		00E356F31AD99517003FC87E /* HelloWorldTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* HelloWorldTests.m */; };
 		0C80B921A6F3F58F76C31292 /* libPods-HelloWorld.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-HelloWorld.a */; };
 		13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; };
-		13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
+		0C49AA4A252E5346004CE48B /* RNAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0C49AA49252E5346004CE48B /* RNAssets.xcassets */; };
+    13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
 		13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
 		7699B88040F8A987B510C191 /* libPods-HelloWorld-HelloWorldTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 19F6CBCC0A4E27FBF8BF4A61 /* libPods-HelloWorld-HelloWorldTests.a */; };
 		81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
@@ -30,6 +31,7 @@
 		00E356EE1AD99517003FC87E /* HelloWorldTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HelloWorldTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
 		00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		00E356F21AD99517003FC87E /* HelloWorldTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HelloWorldTests.m; sourceTree = "<group>"; };
+		0C49AA49252E5346004CE48B /* RNAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = RNAssets.xcassets; path = HelloWorld/RNAssets.xcassets; sourceTree = "<group>"; };
 		13B07F961A680F5B00A75B9A /* HelloWorld.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HelloWorld.app; sourceTree = BUILT_PRODUCTS_DIR; };
 		13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = HelloWorld/AppDelegate.h; sourceTree = "<group>"; };
 		13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = HelloWorld/AppDelegate.mm; sourceTree = "<group>"; };
@@ -89,6 +91,7 @@
 				13B07FAF1A68108700A75B9A /* AppDelegate.h */,
 				13B07FB01A68108700A75B9A /* AppDelegate.mm */,
 				13B07FB51A68108700A75B9A /* Images.xcassets */,
+				0C49AA49252E5346004CE48B /* RNAssets.xcassets */,
 				13B07FB61A68108700A75B9A /* Info.plist */,
 				81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */,
 				13B07FB71A68108700A75B9A /* main.m */,
@@ -180,8 +183,8 @@
 				FD10A7F022414F080027D42C /* Start Packager */,
 				13B07F871A680F5B00A75B9A /* Sources */,
 				13B07F8C1A680F5B00A75B9A /* Frameworks */,
-				13B07F8E1A680F5B00A75B9A /* Resources */,
 				00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
+        13B07F8E1A680F5B00A75B9A /* Resources */,
 				00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */,
 				E235C05ADACE081382539298 /* [CP] Copy Pods Resources */,
 			);
@@ -243,6 +246,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,
+				0C49AA4A252E5346004CE48B /* RNAssets.xcassets in Resources */,
 				13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
diff --git a/packages/rn-tester/RNTester/RNAssets.xcassets/Contents.json b/packages/rn-tester/RNTester/RNAssets.xcassets/Contents.json
new file mode 100644
index 00000000000..2d92bd53fdb
--- /dev/null
+++ b/packages/rn-tester/RNTester/RNAssets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}
diff --git a/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj b/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj
index ff44d2b4f18..cf84795db09 100644
--- a/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj
+++ b/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj
@@ -8,6 +8,7 @@
 
 /* Begin PBXBuildFile section */
 		04157F50C11E9F16DDD69B17 /* libPods-RNTester.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2F98312BF816A7F2688C036D /* libPods-RNTester.a */; };
+    0CF641B628ECB21C00DCDD11 /* RNAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0CF641B528ECB21C00DCDD11 /* RNAssets.xcassets */; };
 		13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
 		272E6B3F1BEA849E001FCF37 /* UpdatePropertiesExampleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 272E6B3C1BEA849E001FCF37 /* UpdatePropertiesExampleView.m */; };
 		27F441EC1BEBE5030039B79C /* FlexibleSizeExampleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 27F441E81BEBE5030039B79C /* FlexibleSizeExampleView.m */; };
@@ -80,6 +81,7 @@
 
 /* Begin PBXFileReference section */
 		0CC3BE1A25DDB68A0033CAEB /* RNTester.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = RNTester.entitlements; path = RNTester/RNTester.entitlements; sourceTree = "<group>"; };
+		0CF641B528ECB21C00DCDD11 /* RNAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = RNAssets.xcassets; path = RNTester/RNAssets.xcassets; sourceTree = "<group>"; };
 		13B07F961A680F5B00A75B9A /* RNTester.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RNTester.app; sourceTree = BUILT_PRODUCTS_DIR; };
 		13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = RNTester/AppDelegate.h; sourceTree = "<group>"; };
 		13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = RNTester/Info.plist; sourceTree = "<group>"; };
@@ -234,6 +236,7 @@
 				5CB07C99226467E60039471C /* RNTesterTurboModuleProvider.mm */,
 				13B07FB71A68108700A75B9A /* main.m */,
 				2DDEF00F1F84BF7B00DBDF73 /* Images.xcassets */,
+				0CF641B528ECB21C00DCDD11 /* RNAssets.xcassets */,
 				8145AE05241172D900A3F8DA /* LaunchScreen.storyboard */,
 				680759612239798500290469 /* Fabric */,
 				272E6B3A1BEA846C001FCF37 /* NativeExampleViews */,
@@ -412,8 +415,8 @@
 				3B2555DA1E9C50DBD7DEDDC7 /* [CP] Check Pods Manifest.lock */,
 				13B07F871A680F5B00A75B9A /* Sources */,
 				13B07F8C1A680F5B00A75B9A /* Frameworks */,
-				13B07F8E1A680F5B00A75B9A /* Resources */,
 				68CD48B71D2BCB2C007E06A9 /* Build JS Bundle */,
+				13B07F8E1A680F5B00A75B9A /* Resources */,
 				5CF0FD27207FC6EC00C13D65 /* Start Metro */,
 				79E8BE2B119D4C5CCD2F04B3 /* [RN] Copy Hermes Framework */,
 				4E5A5A192F46F13B14A915AF /* [CP] Embed Pods Frameworks */,
@@ -513,6 +516,7 @@
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				0CF641B628ECB21C00DCDD11 /* RNAssets.xcassets in Resources */,
 				2DDEF0101F84BF7B00DBDF73 /* Images.xcassets in Resources */,
 				8145AE06241172D900A3F8DA /* LaunchScreen.storyboard in Resources */,
 				3D2AFAF51D646CF80089D1A3 /* legacy_image@2x.png in Resources */,
diff --git a/template/ios/HelloWorld/RNAssets.xcassets/Contents.json b/template/ios/HelloWorld/RNAssets.xcassets/Contents.json
new file mode 100644
index 00000000000..2d92bd53fdb
--- /dev/null
+++ b/template/ios/HelloWorld/RNAssets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}

From 0ccbecb4f3f1879d484d9626a2dc0646c3fd78aa Mon Sep 17 00:00:00 2001
From: Janic Duplessis <janicduplessis@gmail.com>
Date: Fri, 21 Oct 2022 20:55:54 -0400
Subject: [PATCH 2/2] Implement catalog conversion natively and add fallback

---
 .../react-native-codegen-0.0.5.tgz            | Bin 125030 -> 0 bytes
 .../Libraries/Image/AssetSourceResolver.js    |  17 +----
 .../__tests__/resolveAssetSource-test.js      |   4 +-
 packages/react-native/React/Base/RCTUtils.h   |   3 +
 packages/react-native/React/Base/RCTUtils.m   |  72 +++++++++++++++++-
 .../RNTester/RNAssets.xcassets/Contents.json  |   4 +-
 .../RNTesterUnitTests/RCTURLUtilsTests.m      |  21 +++++
 .../RNAssets.xcassets/Contents.json           |   4 +-
 8 files changed, 99 insertions(+), 26 deletions(-)
 delete mode 100644 packages/react-native-codegen/react-native-codegen-0.0.5.tgz

diff --git a/packages/react-native-codegen/react-native-codegen-0.0.5.tgz b/packages/react-native-codegen/react-native-codegen-0.0.5.tgz
deleted file mode 100644
index ad4634539a778a71f554fd8009d701154b15c25e..0000000000000000000000000000000000000000
GIT binary patch
diff --git a/packages/react-native/Libraries/Image/AssetSourceResolver.js b/packages/react-native/Libraries/Image/AssetSourceResolver.js
index c4867e72358..d7ae700437b 100644
--- a/packages/react-native/Libraries/Image/AssetSourceResolver.js
+++ b/packages/react-native/Libraries/Image/AssetSourceResolver.js
@@ -71,15 +71,6 @@ class AssetSourceResolver {
     return !!(this.jsbundleUrl && this.jsbundleUrl.startsWith('file://'));
   }
 
-  isCatalogAsset(): boolean {
-    return (
-      this.asset.__packager_asset &&
-      (this.asset.type === 'png' ||
-        this.asset.type === 'jpg' ||
-        this.asset.type === 'jpeg')
-    );
-  }
-
   defaultAsset(): ResolvedAssetSource {
     if (this.isLoadedFromServer()) {
       return this.assetServerURL();
@@ -90,9 +81,7 @@ class AssetSourceResolver {
         ? this.drawableFolderInBundle()
         : this.resourceIdentifierWithoutScale();
     } else {
-      return this.isCatalogAsset()
-        ? this.assetFromCatalog()
-        : this.scaledAssetURLNearBundle();
+      return this.scaledAssetURLNearBundle();
     }
   }
 
@@ -134,10 +123,6 @@ class AssetSourceResolver {
     );
   }
 
-  assetFromCatalog(): ResolvedAssetSource {
-    return this.fromSource(getAndroidResourceIdentifier(this.asset));
-  }
-
   /**
    * The default location of assets bundled with the app, located by
    * resource identifier
diff --git a/packages/react-native/Libraries/Image/__tests__/resolveAssetSource-test.js b/packages/react-native/Libraries/Image/__tests__/resolveAssetSource-test.js
index 8b8a9dd79e8..d05f0b7aba3 100644
--- a/packages/react-native/Libraries/Image/__tests__/resolveAssetSource-test.js
+++ b/packages/react-native/Libraries/Image/__tests__/resolveAssetSource-test.js
@@ -136,7 +136,7 @@ describe('resolveAssetSource', () => {
           __packager_asset: true,
           width: 100,
           height: 200,
-          uri: 'module_a_logo',
+          uri: 'file:///Path/To/Sample.app/assets/module/a/logo.png',
           scale: 1,
         },
       );
@@ -159,7 +159,7 @@ describe('resolveAssetSource', () => {
           __packager_asset: true,
           width: 100,
           height: 200,
-          uri: '__module_a_logo',
+          uri: 'file:///Path/To/Sample.app/assets/__module/a/logo.png',
           scale: 1,
         },
       );
diff --git a/packages/react-native/React/Base/RCTUtils.h b/packages/react-native/React/Base/RCTUtils.h
index a9502dd215a..96cd3b2af6a 100644
--- a/packages/react-native/React/Base/RCTUtils.h
+++ b/packages/react-native/React/Base/RCTUtils.h
@@ -135,6 +135,9 @@ RCT_EXTERN NSString *__nullable RCTLibraryPath(void);
 // (or nil, if the URL does not specify a path within the Library directory)
 RCT_EXTERN NSString *__nullable RCTLibraryPathForURL(NSURL *__nullable URL);
 
+// Return the name of the asset in the catalog for a packager URL.
+RCT_EXTERN NSString *__nullable RCTAssetCatalogNameForURL(NSURL *__nullable URL);
+
 // Determines if a given image URL refers to a image in bundle
 RCT_EXTERN BOOL RCTIsBundleAssetURL(NSURL *__nullable imageURL);
 
diff --git a/packages/react-native/React/Base/RCTUtils.m b/packages/react-native/React/Base/RCTUtils.m
index 51a20dac918..e71fc282cff 100644
--- a/packages/react-native/React/Base/RCTUtils.m
+++ b/packages/react-native/React/Base/RCTUtils.m
@@ -728,13 +728,66 @@ BOOL RCTIsGzippedData(NSData *__nullable data)
   return RCTRelativePathForURL(RCTHomePath(), URL);
 }
 
-static BOOL RCTIsImageAssetsPath(NSString *path)
+static NSRegularExpression *RCTAssetURLScaleRegex()
+{
+  static dispatch_once_t onceToken;
+  static NSRegularExpression *regex;
+  dispatch_once(&onceToken, ^{
+    regex = [NSRegularExpression regularExpressionWithPattern:@"@\\dx$" options:0 error:nil];
+  });
+  return regex;
+}
+
+static NSRegularExpression *RCTAssetURLCharactersRegex()
+{
+  static dispatch_once_t onceToken;
+  static NSRegularExpression *regex;
+  dispatch_once(&onceToken, ^{
+    regex = [NSRegularExpression regularExpressionWithPattern:@"[^a-z0-9_]" options:0 error:nil];
+  });
+  return regex;
+}
+
+NSString *__nullable RCTAssetCatalogNameForURL(NSURL *__nullable URL)
 {
-  if (path == nil) {
-    return NO;
+  NSString *path = RCTBundlePathForURL(URL);
+  // Packager assets always start with assets/
+  if (path == nil || ![path hasPrefix:@"assets/"]) {
+    return nil;
   }
+
+  // Remove extension
+  path = [path stringByDeletingPathExtension];
+
+  // Remove scale suffix
+  path = [RCTAssetURLScaleRegex() stringByReplacingMatchesInString:path
+                                                           options:0
+                                                             range:NSMakeRange(0, [path length])
+                                                      withTemplate:@""];
+
+  path = [path lowercaseString];
+
+  // Encode folder structure in file name
+  path = [path stringByReplacingOccurrencesOfString:@"/" withString:@"_"];
+
+  // Remove illegal chars
+  path = [RCTAssetURLCharactersRegex() stringByReplacingMatchesInString:path
+                                                                options:0
+                                                                  range:NSMakeRange(0, [path length])
+                                                           withTemplate:@""];
+
+  // Remove "assets_" prefix
+  if ([path hasPrefix:@"assets_"]) {
+    path = [path substringFromIndex:@"assets_".length];
+  }
+
+  return path;
+}
+
+static BOOL RCTIsImageAssetsPath(NSString *path)
+{
   NSString *extension = [path pathExtension];
-  return extension.length == 0 || [extension isEqualToString:@"png"] || [extension isEqualToString:@"jpg"];
+  return [extension isEqualToString:@"png"] || [extension isEqualToString:@"jpg"];
 }
 
 BOOL RCTIsBundleAssetURL(NSURL *__nullable imageURL)
@@ -803,6 +856,17 @@ BOOL RCTIsLocalAssetURL(NSURL *__nullable imageURL)
 
 UIImage *__nullable RCTImageFromLocalAssetURL(NSURL *imageURL)
 {
+  NSString *catalogName = RCTAssetCatalogNameForURL(imageURL);
+  if (catalogName) {
+    UIImage *image = [UIImage imageNamed:catalogName];
+    if (image) {
+      return image;
+    } else {
+      RCTLogWarn(
+          @"Image %@ not found in the asset catalog. Make sure your app template is updated correctly.", catalogName);
+    }
+  }
+
   NSString *imageName = RCTBundlePathForURL(imageURL);
 
   NSBundle *bundle = nil;
diff --git a/packages/rn-tester/RNTester/RNAssets.xcassets/Contents.json b/packages/rn-tester/RNTester/RNAssets.xcassets/Contents.json
index 2d92bd53fdb..73c00596a7f 100644
--- a/packages/rn-tester/RNTester/RNAssets.xcassets/Contents.json
+++ b/packages/rn-tester/RNTester/RNAssets.xcassets/Contents.json
@@ -1,6 +1,6 @@
 {
   "info" : {
-    "version" : 1,
-    "author" : "xcode"
+    "author" : "xcode",
+    "version" : 1
   }
 }
diff --git a/packages/rn-tester/RNTesterUnitTests/RCTURLUtilsTests.m b/packages/rn-tester/RNTesterUnitTests/RCTURLUtilsTests.m
index 318ed489038..b5dcb59ba1d 100644
--- a/packages/rn-tester/RNTesterUnitTests/RCTURLUtilsTests.m
+++ b/packages/rn-tester/RNTesterUnitTests/RCTURLUtilsTests.m
@@ -100,4 +100,25 @@ - (void)testIsLocalAssetsURLParam
   XCTAssertFalse(RCTIsLocalAssetURL(otherAssetsURL));
 }
 
+- (void)testAssetCatalogNameForURL
+{
+  NSString *validAssetPath =
+      [NSString stringWithFormat:@"file://%@/assets/AwesomeModule/icon@2x.png", [[NSBundle mainBundle] resourcePath]];
+  NSString *result = RCTAssetCatalogNameForURL([NSURL URLWithString:validAssetPath]);
+  XCTAssertEqualObjects(result, @"awesomemodule_icon");
+
+  NSString *validAssetNoScalePath =
+      [NSString stringWithFormat:@"file://%@/assets/AwesomeModule/icon.png", [[NSBundle mainBundle] resourcePath]];
+  result = RCTAssetCatalogNameForURL([NSURL URLWithString:validAssetNoScalePath]);
+  XCTAssertEqualObjects(result, @"awesomemodule_icon");
+
+  NSString *notPackagerAssetPath =
+      [NSString stringWithFormat:@"file://%@/icon.png", [[NSBundle mainBundle] resourcePath]];
+  result = RCTAssetCatalogNameForURL([NSURL URLWithString:notPackagerAssetPath]);
+  XCTAssertNil(result);
+
+  result = RCTAssetCatalogNameForURL(nil);
+  XCTAssertNil(result);
+}
+
 @end
diff --git a/template/ios/HelloWorld/RNAssets.xcassets/Contents.json b/template/ios/HelloWorld/RNAssets.xcassets/Contents.json
index 2d92bd53fdb..73c00596a7f 100644
--- a/template/ios/HelloWorld/RNAssets.xcassets/Contents.json
+++ b/template/ios/HelloWorld/RNAssets.xcassets/Contents.json
@@ -1,6 +1,6 @@
 {
   "info" : {
-    "version" : 1,
-    "author" : "xcode"
+    "author" : "xcode",
+    "version" : 1
   }
 }
