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.
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
#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.
#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:
// ...
@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
.
#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
#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.
#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
#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!