Skip to content

Commit

Permalink
fix(iOS): Images don't render on iOS 14 with RN < 0.63.2
Browse files Browse the repository at this point in the history
This change monkey patches React Native on versions prior to 0.63.2 to
fix a bug with images not rendering when on iOS 14.

See facebook/react-native#29420.
  • Loading branch information
tido64 committed Dec 16, 2020
1 parent 7c39ea5 commit 93eaf0c
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 41 deletions.
16 changes: 8 additions & 8 deletions ios/ReactTestApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
objects = {

/* Begin PBXBuildFile section */
1914199A234B2DD800D856AE /* RCTDevSupport+UIScene.m in Sources */ = {isa = PBXBuildFile; fileRef = 19141999234B2DD800D856AE /* RCTDevSupport+UIScene.m */; };
19ECD0D6232ED425003D8557 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19ECD0D5232ED425003D8557 /* AppDelegate.swift */; };
19ECD0DA232ED425003D8557 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19ECD0D9232ED425003D8557 /* ContentView.swift */; };
192DD201240FCAF5004E9CEB /* Manifest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 192DD200240FCAF5004E9CEB /* Manifest.swift */; };
196C22622490CB7600449D3C /* React+Compatibility.m in Sources */ = {isa = PBXBuildFile; fileRef = 196C22602490CB7600449D3C /* React+Compatibility.m */; };
196C7215232F1788006556ED /* ReactInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 196C7214232F1788006556ED /* ReactInstance.swift */; };
196C724123319A85006556ED /* QRCodeReaderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 196C724023319A85006556ED /* QRCodeReaderDelegate.swift */; };
1988284524105BEC005057FF /* UIViewController+ReactTestApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 1988284424105BEC005057FF /* UIViewController+ReactTestApp.m */; };
19ECD0D6232ED425003D8557 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19ECD0D5232ED425003D8557 /* AppDelegate.swift */; };
196C7215232F1788006556ED /* ReactInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 196C7214232F1788006556ED /* ReactInstance.swift */; };
19ECD0D8232ED425003D8557 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19ECD0D7232ED425003D8557 /* SceneDelegate.swift */; };
19ECD0DA232ED425003D8557 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19ECD0D9232ED425003D8557 /* ContentView.swift */; };
1914199A234B2DD800D856AE /* RCTDevSupport+UIScene.m in Sources */ = {isa = PBXBuildFile; fileRef = 19141999234B2DD800D856AE /* RCTDevSupport+UIScene.m */; };
196C22622490CB7600449D3C /* React+Compatibility.m in Sources */ = {isa = PBXBuildFile; fileRef = 196C22602490CB7600449D3C /* React+Compatibility.m */; };
1988284524105BEC005057FF /* UIViewController+ReactTestApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 1988284424105BEC005057FF /* UIViewController+ReactTestApp.m */; };
19ECD0DC232ED427003D8557 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 19ECD0DB232ED427003D8557 /* Assets.xcassets */; };
19ECD0E2232ED427003D8557 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 19ECD0E0232ED427003D8557 /* LaunchScreen.storyboard */; };
19ECD0ED232ED428003D8557 /* ReactTestAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19ECD0EC232ED428003D8557 /* ReactTestAppTests.swift */; };
Expand Down Expand Up @@ -220,8 +220,8 @@
19ECD0CA232ED425003D8557 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1100;
LastUpgradeCheck = 1220;
LastSwiftUpdateCheck = 1230;
LastUpgradeCheck = 1230;
ORGANIZATIONNAME = Microsoft;
TargetAttributes = {
19ECD0D1232ED425003D8557 = {
Expand Down
37 changes: 10 additions & 27 deletions ios/ReactTestApp/RCTDevSupport+UIScene.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,9 @@
#import <React/RCTUtils.h>
#import <React/RCTVersion.h>

#if DEBUG
#import "React+Compatibility.h"

void swizzleSelector(Class class, SEL originalSelector, SEL swizzledSelector)
{
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

BOOL didAddMethod = class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
#if DEBUG

// MARK: - RCTRedBoxWindow

Expand All @@ -56,11 +39,11 @@ + (void)load
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
swizzleSelector(class,
@selector(showErrorMessage:withStack:isUpdate:),
@selector(rta_showErrorMessage:withStack:isUpdate:));
swizzleSelector(class, @selector(dismiss), @selector(rta_dismiss));
swizzleSelector(class, @selector(makeKeyAndVisible), @selector(rta_makeKeyAndVisible));
RTASwizzleSelector(class,
@selector(showErrorMessage:withStack:isUpdate:),
@selector(rta_showErrorMessage:withStack:isUpdate:));
RTASwizzleSelector(class, @selector(dismiss), @selector(rta_dismiss));
RTASwizzleSelector(class, @selector(makeKeyAndVisible), @selector(rta_makeKeyAndVisible));
});
}

Expand Down Expand Up @@ -103,9 +86,9 @@ + (void)initialize
}

if (@available(iOS 13.0, *)) {
swizzleSelector([self class],
@selector(showMessage:color:backgroundColor:),
@selector(rta_showMessage:color:backgroundColor:));
RTASwizzleSelector([self class],
@selector(showMessage:color:backgroundColor:),
@selector(rta_showMessage:color:backgroundColor:));
}
}

Expand Down
2 changes: 2 additions & 0 deletions ios/ReactTestApp/React+Compatibility.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@

@class RCTBridge;

IMP RTASwizzleSelector(Class class, SEL originalSelector, SEL swizzledSelector);

void RTATriggerReloadCommand(RCTBridge *, NSString *reason);
86 changes: 86 additions & 0 deletions ios/ReactTestApp/React+Compatibility.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,33 @@

#include <TargetConditionals.h>

#import <objc/runtime.h>

#import <React/RCTBridge.h>

IMP RTASwizzleSelector(Class class, SEL originalSelector, SEL swizzledSelector)
{
Method originalMethod = class_getInstanceMethod(class, originalSelector);
IMP originalImpl = method_getImplementation(originalMethod);

Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

BOOL didAddMethod = class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
const char *type = method_getTypeEncoding(originalMethod);
class_replaceMethod(class, swizzledSelector, originalImpl, type);
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}

return originalImpl;
}

// MARK: - [0.62] RCTTriggerReloadCommandListeners replaces -[RCTBridge reload]

// `RCTReloadCommand.h` is excluded from `react-native-macos`
// See https://github.com/microsoft/react-native-macos/blob/v0.61.39/React-Core.podspec#L66
#if REACT_NATIVE_VERSION >= 6200
Expand All @@ -25,3 +50,64 @@ void RTATriggerReloadCommand(RCTBridge *bridge, NSString *reason)
RCTTriggerReloadCommandListeners(reason);
#endif
}

// MARK: - [0.63.2] Images do not render on iOS 14
// See https://github.com/facebook/react-native/pull/29420

#if !TARGET_OS_OSX && REACT_NATIVE_VERSION < 6302

#import <React/RCTUIImageViewAnimated.h>

@implementation RCTUIImageViewAnimated (ReactTestApp)

static void (*orig_displayLayer)(id, SEL, CALayer *);

+ (void)initialize
{
if ([self class] != [RCTUIImageViewAnimated class]) {
return;
}

if (@available(iOS 14.0, *)) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
IMP impl = RTASwizzleSelector(
[self class], @selector(displayLayer:), @selector(rta_displayLayer:));
orig_displayLayer = (void (*)(id, SEL, CALayer *))impl;
});
}
}

- (void)rta_displayLayer:(CALayer *)layer
{
/* The fix for images not rendering is to let UIImageView handle it when we
* are not animating. The following change was made in react-native#29420:
*
* diff --git a/Libraries/Image/RCTUIImageViewAnimated.m b/Libraries/Image/RCTUIImageViewAnimated.m
* index 93c6a2f02f5..f6fb5bc60cc 100644
* --- a/Libraries/Image/RCTUIImageViewAnimated.m
* +++ b/Libraries/Image/RCTUIImageViewAnimated.m
* @@ -285,6 +285,8 @@ static NSUInteger RCTDeviceFreeMemory() {
* if (_currentFrame) {
* layer.contentsScale = self.animatedImageScale;
* layer.contents = (__bridge id)_currentFrame.CGImage;
* + } else {
* + [super displayLayer:layer];
* }
* }
*
* The patch calls `super` when `_currentFrame` is `nil`. For our monkey
* patch, we'll invert the logic to let the original method handle the case
* where `_currentFrame` is missing.
*/
if ([self respondsToSelector:@selector(currentFrame)] &&
[self performSelector:@selector(currentFrame)] == nil) {
[super displayLayer:layer];
} else {
orig_displayLayer(self, @selector(displayLayer:), layer);
}
}

@end

#endif
12 changes: 6 additions & 6 deletions macos/ReactTestApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@

/* Begin PBXBuildFile section */
193EF063247A736200BE8C79 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 193EF062247A736200BE8C79 /* AppDelegate.swift */; };
193EF065247A736200BE8C79 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 193EF064247A736200BE8C79 /* ViewController.swift */; };
193EF067247A736300BE8C79 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 193EF066247A736300BE8C79 /* Assets.xcassets */; };
193EF06A247A736300BE8C79 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 193EF068247A736300BE8C79 /* Main.storyboard */; };
193EF08F247A799D00BE8C79 /* Manifest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 193EF08E247A799D00BE8C79 /* Manifest.swift */; };
193EF093247A830200BE8C79 /* UIViewController+ReactTestApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 193EF091247A830200BE8C79 /* UIViewController+ReactTestApp.m */; };
193EF098247B130700BE8C79 /* ReactInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 193EF097247B130700BE8C79 /* ReactInstance.swift */; };
193EF065247A736200BE8C79 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 193EF064247A736200BE8C79 /* ViewController.swift */; };
196C22652490CBAB00449D3C /* React+Compatibility.m in Sources */ = {isa = PBXBuildFile; fileRef = 196C22632490CBAB00449D3C /* React+Compatibility.m */; };
193EF093247A830200BE8C79 /* UIViewController+ReactTestApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 193EF091247A830200BE8C79 /* UIViewController+ReactTestApp.m */; };
193EF067247A736300BE8C79 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 193EF066247A736300BE8C79 /* Assets.xcassets */; };
193EF06A247A736300BE8C79 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 193EF068247A736300BE8C79 /* Main.storyboard */; };
19E791C024B08E1400FA6468 /* ReactTestAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19E791BF24B08E1400FA6468 /* ReactTestAppTests.swift */; };
19E791C324B08E4D00FA6468 /* ReactTestAppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19E791C224B08E4D00FA6468 /* ReactTestAppUITests.swift */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -211,8 +211,8 @@
193EF057247A736100BE8C79 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1150;
LastUpgradeCheck = 1220;
LastSwiftUpdateCheck = 1230;
LastUpgradeCheck = 1230;
ORGANIZATIONNAME = Microsoft;
TargetAttributes = {
193EF05E247A736100BE8C79 = {
Expand Down

0 comments on commit 93eaf0c

Please sign in to comment.