Skip to content

Commit

Permalink
feat: Add native code for iOS
Browse files Browse the repository at this point in the history
  • Loading branch information
Riccardo Cipolleschi committed Jun 13, 2022
1 parent 2b820e5 commit c2de0d4
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 3 deletions.
2 changes: 1 addition & 1 deletion docs/the-new-architecture/landing-page.md
Expand Up @@ -23,6 +23,6 @@ To **migrate an existing app** to the New Architecture, follow [Adopting the New

First, read up on the core concepts outlined in the [Pillars](pillars) section.

Then, for a **how-to guide** on supporting the New Architecture, check out the [Migration](../new-architecture-library-info) guide.
Then, for a **how-to guide** on supporting the New Architecture, check out the [Migration](../new-architecture-library-intro) guide.

For information on **supporting both the Old and New Architectures**, see the [Backwards Compatibility](backward-compatibility) guide.
229 changes: 229 additions & 0 deletions docs/the-new-architecture/pillars-fabric-components.md
Expand Up @@ -285,8 +285,237 @@ dependencies {

### Native Code

The last step requires us to write some native code to connect the JS side of our Component to what is offered by the platforms. This process requires two main steps:

- Run the **CodeGen** to see what would be generated
- Write the native code that will make it work

:::caution
The code generated by the **CodeGen** in this step should not be committed to the versioning system. React Native apps are able to generate
the code when the app is built. This allows to avoid any ABI incompatibility and to ensure that a consistent version of the codege is used.
:::

#### iOS

##### Generate the code

To generate the code starting from the JS specs for iOS, we need to open a terminal and run the following command:

```sh
cd MyApp
yarn add ../RTNCenteredTextFlow
cd ..
node MyApp/node_modules/react-native/scripts/generate-artifacts.js \
--path MyApp/ \
--outputPath RTNCenteredTextFlow/generated/
```

This script first makes our Component visible to the app, so that we can generate the **CodeGen** from it, using `yarn add` to add a local version of the NPM package.

Then, it uses `node` to invoke the `generate-artifacts.js` script, which is the responsible to generate the code for iOS. It requires us to pass the path to a React Native app which contains our Component, using the `--path` parameter.

Then, we specify the `--outputPath` where we want it to generate the code.

The output of this process is the following folder structure:

```sh
generated
└── build
└── generated
└── ios
├── FBReactNativeSpec
│ ├── FBReactNativeSpec-generated.mm
│ └── FBReactNativeSpec.h
├── RCTThirdPartyFabricComponentsProvider.h
├── RCTThirdPartyFabricComponentsProvider.mm
└── react
└── renderer
└── components
├── RNTCenteredTextSpecs
│ ├── ComponentDescriptors.h
│ ├── EventEmitters.cpp
│ ├── EventEmitters.h
│ ├── Props.cpp
│ ├── Props.h
│ ├── RCTComponentViewHelpers.h
│ ├── ShadowNodes.cpp
│ └── ShadowNodes.h
└── rncore
├── ComponentDescriptors.h
├── EventEmitters.cpp
├── EventEmitters.h
├── Props.cpp
├── Props.h
├── RCTComponentViewHelpers.h
├── ShadowNodes.cpp
└── ShadowNodes.h
```

The relevant path for the Component we are writing is `generated/build/generated/ios/react/renderer/components/RNTCenteredTextSpecs`.
This folder contains all the genereted code required by our Component.

See the [CodeGen](./pillars-codegen) section for further details on the generated files.

##### Write the Native iOS Code

Now that we can see the iOS code we need, it's time to write the Native code for our Fabric Component.
We need to create three files:

1. The `RNTCenteredTextManager.mm`, an Objective-C++ file which declares what the Component exports.
2. The `RNTCenteredText.h`, an header file for the actual view.
3. The `RNTCenteredText.mm`, the implementation of the view.

###### RNTCenteredTextManager.mm

```obj-c title="RNTCenteredTextManager.mm"
#import <React/RCTLog.h>
#import <React/RCTUIManager.h>
#import <React/RCTViewManager.h>

@interface RTNCenteredTextManager : RCTViewManager
@end

@implementation RTNCenteredTextManager

RCT_EXPORT_MODULE(RTNCenteredText)

RCT_EXPORT_VIEW_PROPERTY(text, NSString)

- (UIView *)view
{
return [[UIView alloc] init];
}

@end
```
This file is the manager for the Fabric Component.
The most important call is to the `RCT_EXPORT_MODULE` which is required to export the module so that fabric can actually retrieve it and instantiate it.
Then, we have to expose a property for the Fabric Component. This is done with the `RCT_EXPORT_VIEW_PROPERTY` macro, specifieng a name and a type.
Finally, we need to add a `- ((UIView *)view)` method for legacy reasons.
:::info
There are other macros that can be used to export custom properties, emitters and other constructs. You can look them up [here](https://github.com/facebook/react-native/blob/main/React/Views/RCTViewManager.h)
:::
###### RNTCenteredText.h
```Objective-C title="RNTCenteredText.h"
#import <React/RCTViewComponentView.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface RTNCenteredText : RCTViewComponentView
@end
NS_ASSUME_NONNULL_END
```

This file defines the interface for the `RTNCenteredText` view. Here, we can add any native method we may want to invoke on the view. For this guide, we don't need anything, therefore the interface is empty.

###### RNTCenteredText.mm

```C++ title="RNTCenteredText.mm"
#import "RTNCenteredText.h"

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

#import "RCTFabricComponentsPlugins.h"

using namespace facebook::react;

@interface RTNCenteredText () <RCTRTNCenteredTextViewProtocol>
@end

@implementation RTNCenteredText {
UIView *_view;
UILabel *_label;
}

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

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

_view = [[UIView alloc] init];
_view.backgroundColor = [UIColor redColor];

_label = [[UILabel alloc] init];
_label.text = @"Initial value";
[_view addSubview:_label];

_label.translatesAutoresizingMaskIntoConstraints = false;
[NSLayoutConstraint activateConstraints:@[
[_label.leadingAnchor constraintEqualToAnchor:_view.leadingAnchor],
[_label.topAnchor constraintEqualToAnchor:_view.topAnchor],
[_label.trailingAnchor constraintEqualToAnchor:_view.trailingAnchor],
[_label.bottomAnchor constraintEqualToAnchor:_view.bottomAnchor],
]];

_label.textAlignment = NSTextAlignmentCenter;

self.contentView = _view;
}

return self;
}

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

if (oldViewProps.text != newViewProps.text) {
_label.text = [[NSString alloc] initWithCString:newViewProps.text.c_str() encoding:NSASCIIStringEncoding];
}

[super updateProps:props oldProps:oldProps];
}

@end

Class<RCTComponentViewProtocol> RTNCenteredTextCls(void)
{
return RTNCenteredText.class;
}
```
This file contains the actual implementation of the view.
It starts with some import which requires us to read from the files generated by the **CodeGen**.
It has to conform to a specific protocol, in this case `RCTRTNCenteredTextViewProtocol`, which is generated by the **CodeGen**.
It defines a static `(ComponentDescriptorProvider)componentDescriptorProvider` method, which is used by Fabric to retrieve the Descriptor provider to instantiate the object.
Then, we can initialize the view as we usually do with iOS views. In the `init` method, it is important to create a `defaultProps` struct using the `RTNCenteredTextProps` type from the **CodeGen**. We need to assign it to the private `_props` property to correctly initialize the Fabric Component. The remaining part of the initializer is standard Objective-C code to create views and layout them with AutoLayout.
The last two pieces are the `updateProps` method and the `RTNCenteredTextCls` method.
The `updateProps` method is invoked by Fabric every time a prop changes in JS. We can then cast the props passed as parameters to the proper `RTNCenteredTextProps` type and update the native code if it needs to be updated.
Notice that the superclass method `[super updateProps]` must be invoked as the last statement of this method, otherwise the `props` and `oldProps` struct will have the same values.
Finally, the `RTNCenteredTextCls` is another static method used to retrieve the correct instance of the class at runtime.
:::caution
Differently from Native Components, Fabric requires us to manually implement the `updateProps` method. It's not enough to export properties with the `RCT_EXPORT_XXX` and `RCT_REMAP_XXX` macros.
:::
#### Android
### Adding the Fabric Component To Your App
2 changes: 1 addition & 1 deletion docs/the-new-architecture/pillars.md
Expand Up @@ -25,5 +25,5 @@ Finally, we dive a little deeper into the [CodeGen](pillars-codegen) process tha
To integrate a TurboModule or a Fabric Component in an app, the app has to run with the New Architecture enabled.

To create a new app adopting the New Architecture, refer to the [Using the App Template](use-app-template) section.
To migrate an existing app to the New Architecture, refer to the [Migration](/docs/new-architecture-intro) guide.
To migrate an existing app to the New Architecture, refer to the [Migration](../new-architecture-intro) guide.
:::
2 changes: 1 addition & 1 deletion docs/the-new-architecture/use-app-template.md
Expand Up @@ -101,7 +101,7 @@ yarn android
```

:::note
You may notice longer build times with the New Architecture, due to additional step of C++ compilation with the Android NDK. To improve your build time, see [Speeding Up Your Build Phase](docs/build-speed.md).
You may notice longer build times with the New Architecture, due to additional step of C++ compilation with the Android NDK. To improve your build time, see [Speeding Up Your Build Phase](../build-speed.md).
:::

</TabItem>
Expand Down

0 comments on commit c2de0d4

Please sign in to comment.