Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fabric support for iOS #955

Merged
merged 15 commits into from
Jan 17, 2023
Merged
11 changes: 5 additions & 6 deletions apps/fabric/ios/Podfile.lock
Expand Up @@ -16,13 +16,12 @@ PODS:
- lottie-ios (3.5.0)
- lottie-react-native (6.0.0-rc.1):
- lottie-ios (~> 3.5.0)
- lottie-react-native/fabric (= 6.0.0-rc.1)
- React-Core
- lottie-react-native/fabric (6.0.0-rc.1):
- lottie-ios (~> 3.5.0)
- RCT-Folly
alfonsocj marked this conversation as resolved.
Show resolved Hide resolved
- RCTRequired
- RCTTypeSafety
- React-Codegen
- React-Core
- React-RCTFabric
- ReactCommon/turbomodule/core
- RCT-Folly (2021.07.22.00):
- boost
- DoubleConversion
Expand Down Expand Up @@ -787,7 +786,7 @@ SPEC CHECKSUMS:
hermes-engine: bb344d89a0d14c2c91ad357480a79698bb80e186
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
lottie-ios: c55158d67d0629a260625cc2ded2052b829e3c3e
lottie-react-native: 7bf794e0ee88accc91af65598efbbfacc2d5295f
lottie-react-native: 2c0a420b4f9ef28fceb6b050fb95fad0720ddfd1
RCT-Folly: 0080d0a6ebf2577475bda044aa59e2ca1f909cda
RCTRequired: 5cf7e7d2f12699724b59f90350257a422eaa9492
RCTTypeSafety: 3f3ead9673d1ab8bb1aea85b0894ab3220f8f06e
Expand Down
26 changes: 26 additions & 0 deletions packages/core/ios/Fabric/LottieAnimationViewComponentView.h
@@ -0,0 +1,26 @@
#ifdef __cplusplus
matinzd marked this conversation as resolved.
Show resolved Hide resolved
#import <React/RCTViewComponentView.h>
#import <react/renderer/components/lottiereactnative/Props.h>

NS_ASSUME_NONNULL_BEGIN

@interface LottieAnimationViewComponentView : RCTViewComponentView
@end

namespace facebook {
namespace react {
// In order to compare these structs we need to add the == operator for each
bool operator==(const LottieAnimationViewColorFiltersStruct& a, const LottieAnimationViewColorFiltersStruct& b)
alfonsocj marked this conversation as resolved.
Show resolved Hide resolved
{
return b.keypath == a.keypath && b.color == b.color;
}
bool operator==(const LottieAnimationViewTextFiltersIOSStruct& a, const LottieAnimationViewTextFiltersIOSStruct& b)
{
return b.keypath == a.keypath && b.text == b.text;
}
}
}

NS_ASSUME_NONNULL_END

#endif
125 changes: 125 additions & 0 deletions packages/core/ios/Fabric/LottieAnimationViewComponentView.mm
@@ -0,0 +1,125 @@
#import "LottieAnimationViewComponentView.h"

#import <react/renderer/components/lottiereactnative/ComponentDescriptors.h>
#import <react/renderer/components/lottiereactnative/EventEmitters.h>
#import <react/renderer/components/lottiereactnative/Props.h>
#import <react/renderer/components/lottiereactnative/RCTComponentViewHelpers.h>

#import "RCTFabricComponentsPlugins.h"
#import <React/RCTView.h>
#import "LottieContainerView.h"
#import "RNLottieFabricConversions.h"

using namespace facebook::react;

@interface LottieAnimationViewComponentView () <RCTLottieAnimationViewViewProtocol>
@end

@implementation LottieAnimationViewComponentView {
LottieContainerView *_view;
}

- (instancetype) initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
static const auto defaultProps = std::make_shared<const LottieAnimationViewProps>();
_props = defaultProps;

_view = [LottieContainerView new];
self.contentView = _view;
}

return self;
}

#pragma mark - RCTComponentViewProtocol

+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<LottieAnimationViewComponentDescriptor>();
}

- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps {
const auto &oldLottieProps = *std::static_pointer_cast<const LottieAnimationViewProps>(_props);
const auto &newLottieProps = *std::static_pointer_cast<const LottieAnimationViewProps>(props);

if(oldLottieProps.resizeMode != newLottieProps.resizeMode) {
[_view setResizeMode:RCTNSStringFromString(newLottieProps.resizeMode)];
}

if(oldLottieProps.sourceJson != newLottieProps.sourceJson) {
[_view setSourceJson:RCTNSStringFromString(newLottieProps.sourceJson)];
}

if(oldLottieProps.sourceName != newLottieProps.sourceName) {
[_view setSourceName:RCTNSStringFromString(newLottieProps.sourceName)];
}

if(oldLottieProps.sourceURL != newLottieProps.sourceURL) {
[_view setSourceURL:RCTNSStringFromString(newLottieProps.sourceURL)];
}

if(oldLottieProps.progress != newLottieProps.progress) {
[_view setProgress:newLottieProps.progress];
}

if(oldLottieProps.loop != newLottieProps.loop) {
[_view setLoop:newLottieProps.loop];
}

if(oldLottieProps.speed != newLottieProps.speed) {
[_view setSpeed:newLottieProps.speed];
}

if(oldLottieProps.colorFilters != newLottieProps.colorFilters) {
[_view setColorFilters:convertColorFilters(newLottieProps.colorFilters)];
}

if(oldLottieProps.textFiltersIOS != newLottieProps.textFiltersIOS) {
[_view setTextFiltersIOS:convertTextFilters(newLottieProps.textFiltersIOS)];
}
matinzd marked this conversation as resolved.
Show resolved Hide resolved

if(oldLottieProps.renderMode != newLottieProps.renderMode) {
[_view setRenderMode:RCTNSStringFromString(newLottieProps.renderMode)];
}

[super updateProps:props oldProps:oldProps];
}

#pragma mark - Native Commands

- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args
{
RCTLottieAnimationViewHandleCommand(self, commandName, args);
}

- (void) play:(NSInteger)startFrame endFrame:(NSInteger)endFrame
{
if (startFrame != -1 && endFrame != -1) {
[_view playFromFrame:@(startFrame) toFrame:endFrame];
} else {
[_view play];
}
}

- (void) reset
{
[_view reset];
}

- (void) pause
{
[_view pause];
}

- (void) resume
{
[_view resume];
}

@end

Class<RCTComponentViewProtocol> LottieAnimationViewCls(void)
{
return LottieAnimationViewComponentView.class;
}
28 changes: 28 additions & 0 deletions packages/core/ios/Fabric/LottieContainerView.h
@@ -0,0 +1,28 @@
@class UITraitCollection;
@class NSDictionary;
@class NSString;
@class NSCoder;

#import <React/RCTView.h>

@interface LottieContainerView : RCTView
alfonsocj marked this conversation as resolved.
Show resolved Hide resolved
- (void)traitCollectionDidChange:(UITraitCollection * _Nullable)previousTraitCollection;
- (void)setSpeed:(CGFloat)newSpeed;
- (void)setProgress:(CGFloat)newProgress;
- (void)reactSetFrame:(CGRect)frame;
- (void)setLoop:(BOOL)isLooping;
- (void)setTextFiltersIOS:(NSArray<NSDictionary *> * _Nonnull)newTextFilters;
- (void)setRenderMode:(NSString * _Nonnull)newRenderMode;
- (void)setSourceURL:(NSString * _Nonnull)newSourceURLString;
- (void)setSourceJson:(NSString * _Nonnull)newSourceJson;
- (void)setSourceName:(NSString * _Nonnull)newSourceName;
- (void)setResizeMode:(NSString * _Nonnull)resizeMode;
- (void)setColorFilters:(NSArray<NSDictionary *> * _Nonnull)newColorFilters;
- (void)play;
- (void)playFromFrame:(NSNumber * _Nullable)fromFrame toFrame:(CGFloat)toFrame;
- (void)reset;
- (void)pause;
- (void)resume;
- (nonnull instancetype)initWithFrame:(CGRect)frame;
- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)coder;
@end
29 changes: 29 additions & 0 deletions packages/core/ios/Fabric/RNLottieFabricConversions.h
@@ -0,0 +1,29 @@
#ifdef __cplusplus
#import <React/RCTConversions.h>

inline NSArray<NSDictionary *> *convertColorFilters(std::vector<facebook::react::LottieAnimationViewColorFiltersStruct> colorFilterStructArr)
matinzd marked this conversation as resolved.
Show resolved Hide resolved
{
NSMutableArray *filters = [NSMutableArray arrayWithCapacity:colorFilterStructArr.size()];

for (auto colorFilter : colorFilterStructArr) {
[filters addObject:@{
@"color": RCTUIColorFromSharedColor(colorFilter.color),
@"keypath": RCTNSStringFromString(colorFilter.keypath),
}];
}
return filters;
}

inline NSArray<NSDictionary *> *convertTextFilters(std::vector<facebook::react::LottieAnimationViewTextFiltersIOSStruct> textFilterStructArr)
{
NSMutableArray *filters = [NSMutableArray arrayWithCapacity:textFilterStructArr.size()];

for (auto textFilter : textFilterStructArr) {
[filters addObject:@{
@"text": RCTNSStringFromString(textFilter.text),
@"keypath": RCTNSStringFromString(textFilter.keypath),
}];
}
return filters;
}
#endif
22 changes: 16 additions & 6 deletions packages/core/ios/LottieReactNative/ContainerView.swift
@@ -1,6 +1,7 @@
import Lottie
import Foundation

@objc(LottieContainerView)
class ContainerView: RCTView {
private var speed: CGFloat = 0.0
private var progress: CGFloat = 0.0
Expand All @@ -13,7 +14,7 @@ class ContainerView: RCTView {
private var renderMode: RenderingEngineOption = .automatic
@objc var onAnimationFinish: RCTBubblingEventBlock?
var animationView: LottieAnimationView?

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if #available(iOS 13.0, tvOS 13.0, *) {
Expand Down Expand Up @@ -190,6 +191,13 @@ class ContainerView: RCTView {
applyProperties()
}

// There is no Nullable CGFloat in Objective-C, so this function uses a Nullable NSNumber and converts it later
@objc(playFromFrame:toFrame:)
func objcCompatiblePlay(fromFrame: NSNumber? = nil, toFrame: AnimationFrameTime) {
let convertedFromFrame = fromFrame != nil ? CGFloat(truncating: fromFrame!) : nil;
play(fromFrame: convertedFromFrame, toFrame: toFrame);
}

func play(fromFrame: AnimationFrameTime? = nil, toFrame: AnimationFrameTime) {
let callback: LottieCompletionBlock = { animationFinished in
if let onFinish = self.onAnimationFinish {
Expand All @@ -201,7 +209,7 @@ class ContainerView: RCTView {
animationView?.play(fromFrame: fromFrame, toFrame: toFrame, loopMode: self.loop, completion: callback);
}

func play() {
@objc func play() {
let callback: LottieCompletionBlock = { animationFinished in
if let onFinish = self.onAnimationFinish {
onFinish(["isCancelled": !animationFinished])
Expand All @@ -212,16 +220,16 @@ class ContainerView: RCTView {
animationView?.play(completion: callback)
}

func reset() {
@objc func reset() {
animationView?.currentProgress = 0;
animationView?.pause()
}

func pause() {
@objc func pause() {
animationView?.pause()
}

func resume() {
@objc func resume() {
play()
}

Expand All @@ -234,7 +242,9 @@ class ContainerView: RCTView {
animationView = next
addSubview(next)
animationView?.contentMode = contentMode
animationView?.reactSetFrame(frame)
print("self frame \(frame)")
animationView?.reactSetFrame(UIScreen.main.bounds)
alfonsocj marked this conversation as resolved.
Show resolved Hide resolved
print(UIScreen.main.bounds)
applyProperties()
}

Expand Down
32 changes: 17 additions & 15 deletions packages/core/lottie-react-native.podspec
Expand Up @@ -19,24 +19,26 @@ Pod::Spec.new do |s|

s.source = { :git => "https://github.com/lottie-react-native/lottie-react-native.git", :tag => "v#{s.version}" }
s.source_files = "ios/**/*.{h,m,mm,swift}"
s.exclude_files = "ios/Fabric"

s.dependency 'React-Core'
s.dependency 'lottie-ios', '~> 3.5.0'

s.compiler_flags = folly_compiler_flags

s.pod_target_xcconfig = {
"HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17",
}

if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
s.subspec "fabric" do |ss|
ss.dependency "React-RCTFabric"
ss.dependency "React-Codegen"
ss.source_files = "ios/Fabric/**/*.{h,m,mm,swift}"
ss.exclude_files = "ios/**/*.{swift}"
end
matinzd marked this conversation as resolved.
Show resolved Hide resolved
s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
s.pod_target_xcconfig = {
"HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
"OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1",
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17",
"DEFINES_MODULE" => "YES",
}

s.dependency "React-RCTFabric"
s.dependency "React-Codegen"
s.dependency "RCT-Folly"
s.dependency "RCTRequired"
s.dependency "RCTTypeSafety"
s.dependency "ReactCommon/turbomodule/core"
else
s.dependency 'React-Core'
s.exclude_files = "ios/Fabric"
end
end
18 changes: 15 additions & 3 deletions packages/core/src/specs/LottieAnimationViewNativeComponent.ts
Expand Up @@ -8,12 +8,22 @@ import codegenNativeComponent, {
NativeComponentType,
} from 'react-native/Libraries/Utilities/codegenNativeComponent';
import codegenNativeCommands from 'react-native/Libraries/Utilities/codegenNativeCommands';
import type { ViewProps } from 'react-native';
import type { ColorValue, ViewProps } from 'react-native';

export type OnAnimationFinishEvent = Readonly<{
isCancelled: boolean;
}>;

type ColorFilterStruct = Readonly<{
keypath: string;
color: ColorValue;
}>;

type TextFilterStruct = Readonly<{
keypath: string;
text: string;
}>;

export interface NativeProps extends ViewProps {
resizeMode?: string;
renderMode?: string;
Expand All @@ -27,9 +37,11 @@ export interface NativeProps extends ViewProps {
enableMergePathsAndroidForKitKatAndAbove?: boolean;
hardwareAccelerationAndroid?: boolean;
cacheComposition?: boolean;
colorFilters?: ReadonlyArray<string>;
colorFilters?: ReadonlyArray<ColorFilterStruct>;
matinzd marked this conversation as resolved.
Show resolved Hide resolved
// dummy that solves codegen issue when there's a ReadonlyArray<Object> without another Object prop
dummy?: Readonly<{ dummy: boolean }>;
matinzd marked this conversation as resolved.
Show resolved Hide resolved
textFiltersAndroid?: ReadonlyArray<string>;
textFiltersIOS?: ReadonlyArray<string>;
textFiltersIOS?: ReadonlyArray<TextFilterStruct>;
onAnimationFinish?: BubblingEventHandler<
OnAnimationFinishEvent,
'onAnimationFinish'
Expand Down