創(chuàng)建RCTViewManager子類來創(chuàng)建和管理原生視圖
原生視圖都需要被一個(gè)RCTViewManager的子類來創(chuàng)建和管理。
這些管理器在功能上有些類似“視圖控制器”,但它們本質(zhì)上都是單例 - React Native只會(huì)為每個(gè)管理器創(chuàng)建一個(gè)實(shí)例。
它們創(chuàng)建原生的視圖并提供給RCTUIManager,RCTUIManager則會(huì)反過來委托它們在需要的時(shí)候去設(shè)置和更新視圖的屬性。RCTViewManager還會(huì)代理視圖的所有委托,并給JavaScript發(fā)回對應(yīng)的事件。
提供原生視圖步驟如下:
首先創(chuàng)建一個(gè)子類 —— 命名規(guī)范為“視圖名稱+Manager”. 視圖名稱可以加上自己的前綴,這里最好避免使用RCT前綴,除非你想給官方pull request
添加RCT_EXPORT_MODULE()標(biāo)記宏 —— 讓模塊接口暴露給JavaScript
實(shí)現(xiàn)-(UIView *)view方法 —— 創(chuàng)建并返回組件視圖
封裝屬性及傳遞事件
下面先貼出完整的代碼,然后會(huì)對屬性和事件進(jìn)行進(jìn)一步的解說。
TestScrollViewManager.h
#import "RCTViewManager.h"
@interface TestScrollViewManager : RCTViewManager
@end
TestScrollViewManager.m
#import "TestScrollViewManager.h"
#import "TestScrollView.h" //第三方組件的頭文件
#import "RCTBridge.h" //進(jìn)行通信的頭文件
#import "RCTEventDispatcher.h" //事件派發(fā),不導(dǎo)入會(huì)引起Xcode警告
@interface TestScrollViewManager() <SDCycleScrollViewDelegate>
@end
@implementation TestScrollViewManager
// 標(biāo)記宏(必要)
RCT_EXPORT_MODULE()
// 事件的導(dǎo)出,onClickBanner對應(yīng)view中擴(kuò)展的屬性
RCT_EXPORT_VIEW_PROPERTY(onClickBanner, RCTBubblingEventBlock)
// 通過宏RCT_EXPORT_VIEW_PROPERTY完成屬性的映射和導(dǎo)出
RCT_EXPORT_VIEW_PROPERTY(autoScrollTimeInterval, CGFloat);
RCT_EXPORT_VIEW_PROPERTY(imageURLStringsGroup, NSArray);
RCT_EXPORT_VIEW_PROPERTY(autoScroll, BOOL);
- (UIView *)view
{
// 實(shí)際組件的具體大小位置由js控制
TestScrollView *testScrollView = [TestScrollView cycleScrollViewWithFrame:CGRectZero delegate:self placeholderImage:nil];
// 初始化時(shí)將delegate指向了self
testScrollView.pageControlStyle = SDCycleScrollViewPageContolStyleClassic;
testScrollView.pageControlAliment = SDCycleScrollViewPageContolAlimentCenter;
return testScrollView;
}
/**
* 當(dāng)事件導(dǎo)出用到 sendInputEventWithName 的方式時(shí),會(huì)用到
- (NSArray *) customDirectEventTypes {
return @[@"onClickBanner"];
}
*/
#pragma mark SDCycleScrollViewDelegate
/**
* banner點(diǎn)擊
*/
- (void)cycleScrollView:(TestScrollView *)cycleScrollView didSelectItemAtIndex:(NSInteger)index
{
// 這也是導(dǎo)出事件的方式,不過好像是舊方法了,會(huì)有警告
// [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]);
// 導(dǎo)出事件
cycleScrollView.onClickBanner(@{@"target": cycleScrollView.reactTag,
@"value": [NSNumber numberWithInteger:index+1]});
}
// 導(dǎo)出枚舉常量,給js定義樣式用
- (NSDictionary *)constantsToExport
{
return @{
@"SDCycleScrollViewPageContolAliment": @{
@"right": @(SDCycleScrollViewPageContolAlimentRight),
@"center": @(SDCycleScrollViewPageContolAlimentCenter)
}
};
}
// 因?yàn)檫@個(gè)類繼承RCTViewManager,實(shí)現(xiàn)RCTBridgeModule,因此可以使用原生模塊所有特性
// 這個(gè)方法暫時(shí)沒用到
RCT_EXPORT_METHOD(testResetTime:(RCTResponseSenderBlock)callback) {
callback(@[@(234)]);
}
@end
屬性
RCT_EXPORT_VIEW_PROPERTY(autoScrollTimeInterval, CGFloat);
通過宏RCT_EXPORT_VIEW_PROPERTY完成屬性的映射和導(dǎo)出。
CGFloat為autoScrollTimeInterval的OC數(shù)據(jù)類型,轉(zhuǎn)化成js則對應(yīng)number。
React Native用RCTConvert來在JavaScript和原生代碼之間完成類型轉(zhuǎn)換。
支持的默認(rèn)轉(zhuǎn)換類型(部分)如下:
string (NSString)
number (NSInteger, float, double, CGFloat, NSNumber)
boolean (BOOL, NSNumber)
array (NSArray) 包含本列表中任意類型
map (NSDictionary) 包含string類型的鍵和本列表中任意類型的值
如果轉(zhuǎn)換無法完成,會(huì)產(chǎn)生一個(gè)“紅屏”的報(bào)錯(cuò)提示,這樣你就能立即知道代碼中出現(xiàn)了問題。如果一切進(jìn)展順利,上面這個(gè)宏就已經(jīng)包含了導(dǎo)出屬性的全部實(shí)現(xiàn)。
ps:更復(fù)雜的類型轉(zhuǎn)換,則涉及到MKCoordinateRegion類型,本文沒做應(yīng)用,具體可參考官方文檔例子。
事件
js和原生之間需要有事件的交互,例如,在原生實(shí)現(xiàn)的代理或者點(diǎn)擊事件,js也需要實(shí)時(shí)獲取到此類事件時(shí),就需要利用事件進(jìn)行交互。
事件的實(shí)現(xiàn)方式有以下兩種:
通過sendInputEventWithName實(shí)現(xiàn)
- 實(shí)現(xiàn)customDirectEventTypes,返回自定義的事件名數(shù)組(on開頭才有效)
- (NSArray *) customDirectEventTypes {
return @[@"onClickBanner"];
}
- sendInputEventWithName實(shí)現(xiàn)事件調(diào)用(reactTag用于實(shí)例的區(qū)分)
[self.bridge.eventDispatcher sendInputEventWithName:@"onClickBanner"
body:@{@"target": cycleScrollView.reactTag,
@"value": [NSNumber numberWithInteger:index+1]
通過RCTBubblingEventBlock實(shí)現(xiàn)
- 在封裝的View中添加RCTBubblingEventBlock的block屬性(on開頭才有效)
@property (nonatomic, copy) RCTBubblingEventBlock onClickBanner;
- 在Manager類中通過宏RCT_EXPORT_VIEW_PROPERTY完成Block屬性的映射和導(dǎo)出
RCT_EXPORT_VIEW_PROPERTY(onClickBanner, RCTBubblingEventBlock)
- 實(shí)現(xiàn)事件調(diào)用(reactTag用于實(shí)例的區(qū)分)
cycleScrollView.onClickBanner(@{@"target": cycleScrollView.reactTag,@"value": [NSNumber numberWithInteger:index+1]});
通過上面兩種方式封裝好的事件,在js中可以直接利用同名函數(shù)調(diào)用即可(后面會(huì)展示)。
不過關(guān)于事件這塊,挺多都沒完全弄懂,希望有大神引導(dǎo)引導(dǎo),比如為什么只能定義on開頭、如何自定義、如何事件數(shù)據(jù)源的回調(diào)等等。。。
樣式
因?yàn)槲覀兯械囊晥D都是UIView的子類,大部分的樣式屬性應(yīng)該直接就可以生效。有些屬性定義,需要用到枚舉,則可以利用通過原生傳遞來的常數(shù)方式來實(shí)現(xiàn),具體實(shí)現(xiàn)如下:
// 導(dǎo)出枚舉常量,給js定義樣式用
- (NSDictionary *)constantsToExport
{
return @{
@"SDCycleScrollViewPageContolAliment": @{
@"right": @(SDCycleScrollViewPageContolAlimentRight),
@"center": @(SDCycleScrollViewPageContolAlimentCenter)
}
};
}
在js中調(diào)用則如下:
// 首先獲取到常量
var TestScrollViewConsts = require('react-native').UIManager.TestScrollView.Constants;
// 調(diào)用
<TestScrollView style={styles.container}
pageControlAliment = {TestScrollViewConsts.SDCycleScrollViewPageContolAliment.right}
/>
ps: 一部分組件會(huì)希望使用自己定義的默認(rèn)樣式,例如UIDatePicker希望自己的大小是固定的。比如大小用原生默認(rèn)大小,這個(gè)例子具體可以參考官方文檔的樣式模塊。