Skip to main content

iOS implementation

Let's use XCode, to write iOS code. Open XCode, by running this command from the root directory of your app:

xed ios

When workspace is opened, locate Pods project and expand it. Search for Development Pods and find ConicGradientPackage inside. When it's expanded, it will show all files that we created under conic-gradient-package/ios directory.

info

Extending the <View /> is available only in Objective-C

ConicGradientViewComponentView.h

Let's go to the header file for new architecture Fabric component at ios/ConicGradientViewComponentView.h

ios/ConicGradientViewComponentView.h
#if RCT_NEW_ARCH_ENABLED
#import <React/RCTViewComponentView.h>
#import <QuartzCore/QuartzCore.h>

/**
* Declare the ObjC interface for that Fabric component class.
*
* It must extend RCTViewComponentView
*/
@interface ConicGradientViewComponentView : RCTViewComponentView

@property(nonatomic, readonly, strong) CAGradientLayer * _Nonnull layer;

@end

#endif

We declare the class that extends RCTViewComponentView base class. You can notice, that layer property is declared - it's imported from QuartzCore framework and will be responsible for displaying our gradient background.

ConicGradientViewComponentView.mm

Navigate to ios/ConicGradientViewComponentView.mm where we'll implement our Fabric component.

ios/ConicGradientViewComponentView.mm
#if RCT_NEW_ARCH_ENABLED
#import "ConicGradientViewComponentView.h"

#import <React/RCTConversions.h>
#import <RCTTypeSafety/RCTConvertHelpers.h>

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

#import "RCTFabricComponentsPlugins.h"

using namespace facebook::react;

@interface ConicGradientViewComponentView () <RCTConicGradientViewViewProtocol>
@end

@implementation ConicGradientViewComponentView

@dynamic layer;

+ (Class)layerClass
{
return [CAGradientLayer classForCoder];
}

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

self.layer.type = kCAGradientLayerConic;
self.layer.needsDisplayOnBoundsChange = YES;
}

return self;
}

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

@end

Class<RCTComponentViewProtocol> ConicGradientViewCls(void)
{
return ConicGradientViewComponentView.class;
}
#endif

Here we start with a bunch of imports for conversion helpers and code-generated spec classes. Next up, we import facebook::react C++ namespace to not write it every time before C++ classes which belong to that namespace. After that, we implement code-generated RCTConicGradientViewViewProtocol protocol.

Inside the implementation block, you can find @dynamic layer; line. This will make the layer property be able to be implemented at runtime and not at compile time. And this will be done at runtime with + layerClass getter that returns the class type for our layer (CAGradientLayer in that case). For more reference about the gradient class, check out CAGradientLayer docs section.

In the class - initWithFrame: initializer we are declaring default props used by the component as well as gradient-layer's properties.

Each Fabric component needs to declare static componentDescriptorProvider and RCTComponentViewProtocol class type (here it's ConicGradientViewCls).

When our Fabric component has its boilerplate, we can start with adding support for gradient props - let's add - updateProps:oldProps: method:

ios/ConicGradientViewComponentView.mm
// ...

@implementation ConicGradientViewComponentView

// ...

- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
const auto &oldViewProps = *std::static_pointer_cast<const ConicGradientViewProps>(_props);
const auto &newViewProps = *std::static_pointer_cast<const ConicGradientViewProps>(props);

if (oldViewProps.colors != newViewProps.colors) {
auto colors = RCTConvertVecToArray(newViewProps.colors, ^id(SharedColor item){
return (id)RCTUIColorFromSharedColor(item).CGColor;
});
self.layer.colors = colors;
}

if (oldViewProps.locations != newViewProps.locations) {
auto locations = RCTConvertVecToArray(newViewProps.locations, ^id(double item){
return @(item);
});
self.layer.locations = locations;
}

if (oldViewProps.centerPoint.x != newViewProps.centerPoint.x || oldViewProps.centerPoint.y != newViewProps.centerPoint.y) {
auto centerPoint = CGPointMake(newViewProps.centerPoint.x, newViewProps.centerPoint.y);
self.layer.startPoint = centerPoint;
self.layer.endPoint = CGPointMake(1, centerPoint.y);
}

[super updateProps:props oldProps:oldProps];
}

// ...

@end

// ...

The newly added method is responsible for comparing new and old props and updating our component's gradient layer if needed. For first two props, we are using RCTConvertVecToArray helper to map C++ array to Objective-C array. Additionally, C++ SharedColor type is mapped here to Objective-C UIColor thanks to RCTUIColorFromSharedColor helper.

ConicGradientView.h

Next step is to implement similar experience for the old architecture component - to do that, let's go to ios/ConicGradientView.h and declare a class that extends RCTView.

ios/ConicGradientView.h
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#import <React/RCTView.h>

/**
* Declare the ObjC interface for that old arch component class.
*
* If it needs to be enhanced `<View />`, it will extend `RCTView`, otherwise it can extend any UIView-based class.
*/
@interface ConicGradientView : RCTView

@property(nonatomic, readonly, strong) CAGradientLayer * _Nonnull layer;

@property (nonatomic, copy) NSArray<UIColor *> * _Nonnull colors;
@property (nonatomic, copy) NSArray<NSNumber *> * _Nonnull locations;
@property (nonatomic, assign) CGPoint centerPoint;

@end

As in the Fabric component, we declare the layer property. Also we have to declare each prop that will be transferred from JS world.

ConicGradientView.mm

ios/ConicGradientView.mm
#import "ConicGradientView.h"

@implementation ConicGradientView

@dynamic layer;

+ (Class)layerClass
{
return [CAGradientLayer classForCoder];
}

- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}

- (void)setup
{
self.layer.type = kCAGradientLayerConic;
self.layer.needsDisplayOnBoundsChange = YES;
}

- (void)setColors:(NSArray<UIColor *> *)colors
{
_colors = colors;
NSMutableArray *cgColors = [NSMutableArray arrayWithCapacity:colors.count];
for (int i = 0; i < colors.count; i++) {
cgColors[i] = (id)colors[i].CGColor;
}
self.layer.colors = cgColors;
}

- (void)setLocations:(NSArray<NSNumber *> *)locations
{
_locations = locations;
self.layer.locations = locations;
}

- (void)setCenterPoint:(CGPoint)centerPoint
{
_centerPoint = centerPoint;
self.layer.startPoint = centerPoint;
self.layer.endPoint = CGPointMake(1, centerPoint.y);
}

@end

Gradient class is returned from + layerClass static function that is used to set the layer property, we setup the gradient layer type in the initializer and we handle gradient configuration when props are changed.

ConicGradientViewManager.h

Let's navigate to ios/ConicGradientViewManager.h - the header file for the ConicGradientViewManager view manager class. It will extend RCTViewManager class.

"ios/ConicGradientViewManager.h
#import <React/RCTViewManager.h>

/**
* Declare the ObjC interface for that view manager class.
*
* It must extend RCTViewManager
*/
@interface ConicGradientViewManager : RCTViewManager

@end

ConicGradientViewManager.mm

Wrap everything up in ios/ConicGradientViewManager.mm

ios/ConicGradientViewManager.mm
#import "ConicGradientViewManager.h"

#if RCT_NEW_ARCH_ENABLED
#else
#import "ConicGradientView.h"
#endif

@implementation ConicGradientViewManager

RCT_EXPORT_MODULE(ConicGradientView)

RCT_EXPORT_VIEW_PROPERTY(colors, NSArray<UIColor>)
RCT_EXPORT_VIEW_PROPERTY(locations, NSArray<NSNumber *>)
RCT_EXPORT_VIEW_PROPERTY(centerPoint, CGPoint)

#if RCT_NEW_ARCH_ENABLED
#else
- (UIView *)view
{
ConicGradientView *view = [ConicGradientView new];
return view;
}
#endif

@end

In the implementation file for ConicGradientViewManager class we use RCT_EXPORT_MODULE macro with the ConicGradientView name that will be accessed on the JS side. The props are declared with RCT_EXPORT_VIEW_PROPERTY macro (1st arg - name of the prop; 2nd arg - its native type). Additionally, we are declaring managed view for the old architecture mode - for the new architecture mode, we just need to register the name of our module and the props it accepts.

You can check training repo for Objective-C-only implementation here.

That's iOS part, now let's go to Android!