RN调用iOS原生组件

创建RCTViewManager子类来创建和管理原生视图
原生视图都需要被一个RCTViewManager的子类来创建和管理。

这些管理器在功能上有些类似“视图控制器”,但它们本质上都是单例 - React Native只会为每个管理器创建一个实例。

它们创建原生的视图并提供给RCTUIManager,RCTUIManager则会反过来委托它们在需要的时候去设置和更新视图的属性。RCTViewManager还会代理视图的所有委托,并给JavaScript发回对应的事件。
提供原生视图步骤如下:
首先创建一个子类 —— 命名规范为“视图名称+Manager”. 视图名称可以加上自己的前缀,这里最好避免使用RCT前缀,除非你想给官方pull request
添加RCT_EXPORT_MODULE()标记宏 —— 让模块接口暴露给JavaScript
实现-(UIView *)view方法 —— 创建并返回组件视图
封装属性及传递事件
下面先贴出完整的代码,然后会对属性和事件进行进一步的解说。
TestScrollViewManager.h

#import "RCTViewManager.h"

@interface TestScrollViewManager : RCTViewManager

@end

TestScrollViewManager.m

#import "TestScrollViewManager.h"
#import "TestScrollView.h"      //第三方组件的头文件

#import "RCTBridge.h"           //进行通信的头文件
#import "RCTEventDispatcher.h"  //事件派发,不导入会引起Xcode警告

@interface TestScrollViewManager() 

@end

@implementation TestScrollViewManager

//  标记宏(必要)
RCT_EXPORT_MODULE()

//  事件的导出,onClickBanner对应view中扩展的属性
RCT_EXPORT_VIEW_PROPERTY(onClickBanner, RCTBubblingEventBlock)

//  通过宏RCT_EXPORT_VIEW_PROPERTY完成属性的映射和导出
RCT_EXPORT_VIEW_PROPERTY(autoScrollTimeInterval, CGFloat);

RCT_EXPORT_VIEW_PROPERTY(imageURLStringsGroup, NSArray);

RCT_EXPORT_VIEW_PROPERTY(autoScroll, BOOL);

- (UIView *)view
{
    //  实际组件的具体大小位置由js控制
    TestScrollView *testScrollView = [TestScrollView cycleScrollViewWithFrame:CGRectZero delegate:self placeholderImage:nil];
    //  初始化时将delegate指向了self
    testScrollView.pageControlStyle = SDCycleScrollViewPageContolStyleClassic;
    testScrollView.pageControlAliment = SDCycleScrollViewPageContolAlimentCenter;
    return testScrollView;
}

/**
 *  当事件导出用到 sendInputEventWithName 的方式时,会用到
- (NSArray *) customDirectEventTypes {
    return @[@"onClickBanner"];
}
 */

#pragma mark SDCycleScrollViewDelegate
/**
 *  banner点击
 */
- (void)cycleScrollView:(TestScrollView *)cycleScrollView didSelectItemAtIndex:(NSInteger)index
{
//    这也是导出事件的方式,不过好像是旧方法了,会有警告
//    [self.bridge.eventDispatcher sendInputEventWithName:@"onClickBanner"
//                                                   body:@{@"target": cycleScrollView.reactTag,
//                                                          @"value": [NSNumber numberWithInteger:index+1]
//                                                        }];

    if (!cycleScrollView.onClickBanner) {
        return;
    }

    NSLog(@"oc did click %li", [cycleScrollView.reactTag integerValue]);

    //  导出事件
    cycleScrollView.onClickBanner(@{@"target": cycleScrollView.reactTag,
                                    @"value": [NSNumber numberWithInteger:index+1]});
}

// 导出枚举常量,给js定义样式用
- (NSDictionary *)constantsToExport
{
    return @{
             @"SDCycleScrollViewPageContolAliment": @{
                     @"right": @(SDCycleScrollViewPageContolAlimentRight),
                     @"center": @(SDCycleScrollViewPageContolAlimentCenter)
                     }
             };
}

//  因为这个类继承RCTViewManager,实现RCTBridgeModule,因此可以使用原生模块所有特性
//  这个方法暂时没用到
RCT_EXPORT_METHOD(testResetTime:(RCTResponseSenderBlock)callback) {
    callback(@[@(234)]);
}

@end

属性

RCT_EXPORT_VIEW_PROPERTY(autoScrollTimeInterval, CGFloat);

通过宏RCT_EXPORT_VIEW_PROPERTY完成属性的映射和导出。

CGFloat为autoScrollTimeInterval的OC数据类型,转化成js则对应number。
React Native用RCTConvert来在JavaScript和原生代码之间完成类型转换。

支持的默认转换类型(部分)如下:

string (NSString)
number (NSInteger, float, double, CGFloat, NSNumber)
boolean (BOOL, NSNumber)
array (NSArray) 包含本列表中任意类型
map (NSDictionary) 包含string类型的键和本列表中任意类型的值

如果转换无法完成,会产生一个“红屏”的报错提示,这样你就能立即知道代码中出现了问题。如果一切进展顺利,上面这个宏就已经包含了导出属性的全部实现。
ps:更复杂的类型转换,则涉及到MKCoordinateRegion类型,本文没做应用,具体可参考官方文档例子。

事件

js和原生之间需要有事件的交互,例如,在原生实现的代理或者点击事件,js也需要实时获取到此类事件时,就需要利用事件进行交互。

事件的实现方式有以下两种:
通过sendInputEventWithName实现

  1. 实现customDirectEventTypes,返回自定义的事件名数组(on开头才有效)
- (NSArray *) customDirectEventTypes {
 return @[@"onClickBanner"];
}
  1. sendInputEventWithName实现事件调用(reactTag用于实例的区分)
[self.bridge.eventDispatcher sendInputEventWithName:@"onClickBanner"
                                               body:@{@"target": cycleScrollView.reactTag,
                                                      @"value": [NSNumber numberWithInteger:index+1]

通过RCTBubblingEventBlock实现

  1. 在封装的View中添加RCTBubblingEventBlock的block属性(on开头才有效)
@property (nonatomic, copy) RCTBubblingEventBlock onClickBanner;
  1. 在Manager类中通过宏RCT_EXPORT_VIEW_PROPERTY完成Block属性的映射和导出
RCT_EXPORT_VIEW_PROPERTY(onClickBanner, RCTBubblingEventBlock)
  1. 实现事件调用(reactTag用于实例的区分)
cycleScrollView.onClickBanner(@{@"target": cycleScrollView.reactTag,@"value": [NSNumber numberWithInteger:index+1]});

通过上面两种方式封装好的事件,在js中可以直接利用同名函数调用即可(后面会展示)。

不过关于事件这块,挺多都没完全弄懂,希望有大神引导引导,比如为什么只能定义on开头、如何自定义、如何事件数据源的回调等等。。。

样式
因为我们所有的视图都是UIView的子类,大部分的样式属性应该直接就可以生效。有些属性定义,需要用到枚举,则可以利用通过原生传递来的常数方式来实现,具体实现如下:

// 导出枚举常量,给js定义样式用
- (NSDictionary *)constantsToExport
{
    return @{
             @"SDCycleScrollViewPageContolAliment": @{
                     @"right": @(SDCycleScrollViewPageContolAlimentRight),
                     @"center": @(SDCycleScrollViewPageContolAlimentCenter)
                     }
             };
}

在js中调用则如下:

//  首先获取到常量
var TestScrollViewConsts = require('react-native').UIManager.TestScrollView.Constants;

//  调用

ps: 一部分组件会希望使用自己定义的默认样式,例如UIDatePicker希望自己的大小是固定的。比如大小用原生默认大小,这个例子具体可以参考官方文档的样式模块。

你可能感兴趣的:(RN调用iOS原生组件)