创建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() <SDCycleScrollViewDelegate>
@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实现
- 实现customDirectEventTypes,返回自定义的事件名数组(on开头才有效)
- (NSArray *) customDirectEventTypes {
return @[@"onClickBanner"];
}
- sendInputEventWithName实现事件调用(reactTag用于实例的区分)
[self.bridge.eventDispatcher sendInputEventWithName:@"onClickBanner"
body:@{@"target": cycleScrollView.reactTag,
@"value": [NSNumber numberWithInteger:index+1]
通过RCTBubblingEventBlock实现
- 在封装的View中添加RCTBubblingEventBlock的block属性(on开头才有效)
@property (nonatomic, copy) RCTBubblingEventBlock onClickBanner;
- 在Manager类中通过宏RCT_EXPORT_VIEW_PROPERTY完成Block属性的映射和导出
RCT_EXPORT_VIEW_PROPERTY(onClickBanner, RCTBubblingEventBlock)
- 实现事件调用(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;
// 调用
<TestScrollView style={styles.container}
pageControlAliment = {TestScrollViewConsts.SDCycleScrollViewPageContolAliment.right}
/>
ps: 一部分组件会希望使用自己定义的默认样式,例如UIDatePicker希望自己的大小是固定的。比如大小用原生默认大小,这个例子具体可以参考官方文档的样式模块。