#import "React/RCTEventEmitter.h"
@interface FFRNEventEmitter : RCTEventEmitter
// 导出你所有的方法名字
- (NSArray<NSString *> *)supportedEvents;
-(void)iseCallback:(NSString*)code result:(NSString*) result;
-(void)iseVolume:(NSString*)code result:(NSString*) result;
-(void)playCallback:(NSString*)code result:(NSString*) result;
@property (nonatomic, weak) RCTBridge *bridge;
@end
#import "FFRNEventEmitter.h"
@implementation FFRNEventEmitter
@synthesize bridge = _bridge;
RCT_EXPORT_MODULE()
// 导出你所有的方法名字
- (NSArray<NSString *> *)supportedEvents
{
return @[@"iseCallback", @"iseVolume", @"playCallback"];//有几个就写几个
}
-(void)iseCallback:(NSString*)code result:(NSString*) result
{
[self sendEventWithName:@"iseCallback"
body:@{
@"code": code,
@"result": result,
}];
}
-(void)iseVolume:(NSString*)code result:(NSString*) result
{
[self sendEventWithName:@"iseCallback"
body:@{
@"code": code,
@"result": result,
}];
}
-(void)playCallback:(NSString*)code result:(NSString*) result
{
[self sendEventWithName:@"iseCallback"
body:@{
@"code": code,
@"result": result,
}];
}
@end
#import <Foundation/Foundation.h>
#import "FFRNCatgoryListCtrl.h"
@interface FFRNSington : NSObject
+ (FFRNSington *)sharedInstance;
@property (nonatomic, weak) FFRNCatgoryListCtrl *RNCatgoryListCtrl;
@end
#import "FFRNSington.h"
@implementation FFRNSington
+ (FFRNSington *)sharedInstance
{
static FFRNSington *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
@end
#import <Foundation/Foundation.h>
typedef void (^OperateBlock)(id object);
@interface RNSQLManager : NSObject
@property (nonatomic, copy) OperateBlock operateBlock;
@end
#import "RNSQLManager.h"
@implementation RNSQLManager
- (void)query:(NSString *)queryData successCallback:(OperateBlock)responseSender
{
}
@end
#import <UIKit/UIKit.h>
#import "FFEasyLifeCatagoryModel.h"
#import <React/RCTBridgeModule.h>
#import <React/RCTLog.h>
#import "React/RCTEventEmitter.h"
#import "FFRNEventEmitter.h"
@interface FFRNCatgoryListCtrl : UIViewController<RCTBridgeModule>
@property (nonatomic, strong) FFEasyLifeCatagoryModel *selectModel;
@end
#import "FFRNCatgoryListCtrl.h"
#import "FFEasyLifeActivityDetailCtrl.h"
#import "FFEasyLifeCataProduListModel.h"
#import "FFRNSington.h"
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <React/RCTBridge.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTEventEmitter.h>
@interface FFRNCatgoryListCtrl ()
@property (strong, nonatomic) YSTopBarView *topBarView;
@end
@implementation FFRNCatgoryListCtrl
RCT_EXPORT_MODULE()
RCT_EXPORT_METHOD(print:(NSString *)text) {
NSLog(@"---- %@",text);
}
// 接收传过来的 NSString + NSDictionary
RCT_EXPORT_METHOD(addEventTwo:(NSString *)name details:(NSDictionary *)details)
{
RCTLogInfo(@"接收传过来的NSString+NSDictionary: %@ %@", name, details);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
FFEasyLifeCataProduListModel *model = [[FFEasyLifeCataProduListModel alloc] initWithDic:details];
model.title2 = @"";
FFEasyLifeActivityDetailCtrl *vc = [[FFEasyLifeActivityDetailCtrl alloc] initWithNibName:kFFEasyLifeActivityDetailCtrl bundle:nil];
vc.actType = model.type;
vc.idx = model.goodsId;
vc.activityId = model.activityId;
[[FFRNSington sharedInstance].RNCatgoryListCtrl.navigationController pushViewController:vc animated:YES];
}];
}
//回调函数,在官方的文档中是有上面的一个警告,不过在使用过程暂时未发现问题。在OC中,我们添加一个getNativeClass方法,将当前模块的类名回调给JS。
RCT_EXPORT_METHOD(getNativeClass:(RCTResponseSenderBlock)callback) {
callback(@[NSStringFromClass([self class])]);
}
+ (NSArray *)__rct_export__230
{
return @[ @"", @"addEvent:(NSString *)name location:(NSString *)location" ];
}
//原生模块可以导出一些常量,这些常量在JavaScript端随时都可以访问。用这种方法来传递一些静态数据,可以避免通过bridge进行一次来回交互。
//但是注意这个常量仅仅在初始化的时候导出了一次,所以即使你在运行期间改变constantToExport返回的值,也不会影响到JavaScript环境下所得到的结果。
- (NSDictionary *)constantsToExport {
return @{ @"BGModuleName" : @"BGNativeModuleExample",
@"TestName": @"我是从原生定义的~",
};
}
// 对外提供调用方法,演示Callback
RCT_EXPORT_METHOD(testCallbackEventOne:(NSString *)name callback:(RCTResponseSenderBlock)callback)
{
NSLog(@"%@",name);
NSArray *events=@[@"1", @"2", @"3",@"4"]; //准备回调回去的数据
callback(@[[NSNull null],events]);
}
// 对外提供调用方法,演示Promise使用
RCT_REMAP_METHOD(testCallbackEventTwo,
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSArray *events =@[@"one ",@"two ",@"three"];//准备回调回去的数据
if (events) {
resolve(events);
} else {
NSError *error=[NSError errorWithDomain:@"我是Promise回调错误信息..." code:101 userInfo:nil];
reject(@"no_events", @"There were no events", error);
}
}
/**
注:不知道为什么RN所在的类中self的内存地址会被修改,意思就是你想要的self跟你在于js交互中用到的self不是同一个self.同时出现的情况就会出现pop不掉我们的RN类的这个界面.
解决办法:使用单例来保存我们原本要用的self,在viewDidLoad方法中做保存
*/
- (void)viewDidLoad {
[super viewDidLoad];
self.topBarView = [[YSTopBarView alloc] initWithFrame:CGRectMake(0, 0, k_SCREEN_WIDTH, 64)];
self.topBarView.leftLabel.text = @"RN_分类";
[_topBarView.leftButton addTarget:self action:@selector(actionBack) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.topBarView];
// http://reactnative.cn
[FFRNSington sharedInstance].RNCatgoryListCtrl = self;
NSDictionary *props = @{@"cid" : [NSString limitStringNotEmpty:self.selectModel.idx]};
NSURL *jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"EasyLife"
initialProperties:props
launchOptions:nil];
rootView.frame = CGRectMake(0, 64, k_SCREEN_WIDTH, k_SCREEN_HEIGHT - 64);
[self.view addSubview:rootView];
}
- (void)actionBack {
[self.navigationController popViewControllerAnimated:YES];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
@end
js
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React, { Component } from 'react';
import {
SegmentedControlIOS,
AppRegistry,
StyleSheet,
Text,
Alert,
FlatList,
ActivityIndicator,
Animated,
ScrollView,
Image,
View,
TouchableOpacity,
} from 'react-native';
var{NativeModules} =require('react-native');
var{NativeEventEmitter} =require('react-native');
var FFRNCatgoryListCtrl=NativeModules.FFRNCatgoryListCtrl;
var FFRNEventEmitter=NativeModules.FFRNEventEmitter;
const myNativeEvt = new NativeEventEmitter(FFRNEventEmitter); //创建自定义事件接口
var ITEM_HEIGHT = 100;
const AnimatedFlatList = Animated.createAnimatedComponent(FlatList);
// const REQUEST_URL = 'https://api.github.com/search/repositories?q=javascript&sort=stars';
const REQUEST_URL = 'http://192.168.11.23:8086/app-client-web/commodity/searchCommodity.do?¶ms=%7BcityId:%22%22,sort:%22sales%22,pageSize:20,cid:%227556cb32899248edb5617a722ab0eb53%22,title:%22%22,client_type:2,sys_user_id:%22%22,page:1,desc:1%7D';
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
render: {
padding: 10,
paddingTop:20,
flex: 1,
},
icon: {
marginLeft:10,
alignItems: 'center',
width: ITEM_HEIGHT,
height: ITEM_HEIGHT,
},
topicCard: {
flex: 1,
flexDirection: 'row',
padding: 10,
backgroundColor: '#ffffff',
borderColor: '#f0f0f0',
borderStyle: 'solid',
borderWidth: 1,
borderRadius: 0
},
avatarImg: {
width: 110,
height: 110,
justifyContent:'center',
borderRadius: 2
},
titleMeta: {
flex: 7,
marginLeft:15,
marginRight:10,
//backgroundColor: 'green',
},
topicTitle: {
marginTop:5,
fontSize: 16,
lineHeight: 20,
fontWeight: 'bold',
letterSpacing: 1,
color: '#333333',
//backgroundColor: '#ff6b13',
},
metaarea: {
flexDirection: 'row',
marginTop: 10,
justifyContent: 'flex-start',
flexWrap: 'wrap',
//backgroundColor: '#800080',
},
metainfo: {
color: '#999999',
fontSize: 12,
lineHeight: 16,
letterSpacing: 1,
marginRight: 5
},
metaareatag: {
flexDirection: 'row',
marginTop: 2,
justifyContent: 'flex-start',
flexWrap: 'wrap',
//backgroundColor: '#556B2F',
},
nodename: {
color: '#999999',
fontSize: 12,
lineHeight: 16
},
replieCountBg: {
backgroundColor: '#e74c3c',
position: 'absolute',
bottom: 0,
right: 0,
paddingBottom: 5,
paddingTop: 5,
paddingLeft: 10,
paddingRight: 10,
borderRadius: 11
},
replieCount: {
fontSize: 12,
lineHeight: 12,
color: '#ffffff',
fontWeight: 'bold'
},
});
export default class EasyLife extends Component {
static navigationOptions = {
title: 'SegmentedControlIOS',
};
constructor(props) {
super(props);
this.state = {
refreshing: true,
loadMore: false,
isLoading: true,
//网络请求状态
error: false,
errorInfo: "",
dataArray: [],
sort: "sales",
desc: 1,
url: REQUEST_URL,
index: 0,
cid: "",
}
// { (this: any).requestData() = this.requestData().bind(this)}
// { (this: any)._renderCell() = this._renderCell().bind(this)}
// { (this: any)._header() = this._header().bind(this)}
// { (this: any)._footer() = this._footer().bind(this)}
}
//渲染完成后调用一次,这个时候DOM结构已经渲染了。这个时候就可以初始化其他框架的设置了,如果利用jQuery绑定事件等等。
componentDidMount() {
//请求数据
this.requestData();
}
//在组件中使用
componentWillMount() {
this.listener = myNativeEvt.addListener('iseCallback', this.iseCallback.bind(this)); //对应了原生端的名字
}
componentWillUnmount() {
this.listener && this.listener.remove(); //记得remove哦
this.listener = null;
}
/**
*
* 一些事件回调
*
* */
iseCallback(data) {//接受原生传过来的数据 data={code:,result:}
alert('数据');
if (data.code == CB_CODE_RESULT) {
//
}
else {//..真的是未知的错误
logf('传回其他参数', data.result);
}
}
// 传原生一个字符串 + 回调
callBackOne = () => {
FFRNCatgoryListCtrl.testCallbackEventOne(('我是RN给原生的'), (error, events) => {
if (error) {
console.error(error);
} else {
this.state.cid = events;
this.renderData();
}
})
}
callErrorBack = () => {
// try{
// var events=await FFRNCatgoryListCtrl.testCallbackEventTwo();
// alert(events)
// }catch(e){
// console.error(e);
// }
}
//加载等待的view
renderLoadingView() {
return (
<View style={styles.container}>
<ActivityIndicator animating={true} style={[styles.gray, {height: 80}]} color='red' size="large"
/>
</View>
);
}
//加载失败view
renderErrorView(error) {
return (
<View style={styles.container}>
<Text> Fail: {error} </Text>
</View>
);
}
/**
*
* 网络请求
*
* */
requestData() {
let formData = new FormData();
var test_url = 'http://192.168.11.23:8086/app-client-web/commodity/searchCommodity.do?';
formData.append("title":"");
formData.append("cid":"");
formData.append("cityId":"");
formData.append("sort":"");
formData.append("desc":"");
formData.append("page":"");
formData.append("pageSize":"");
let params = '¶ms={cityId:"",sort:"'
+ this.state.sort
+ '",pageSize:20,cid:"'
+ this.state.cid
+ '",title:"",client_type:2,sys_user_id:"",page:1,desc:'
+ this.state.desc + '}';
fetch(test_url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: params,
})
.then((response) => response.json())
.then((responseData) => {
let data = responseData.resultData.items;
let dataBlob = [];
let i = 0;
//遍历data数组中的每个元素,并按照return中的计算方式 形成一个新的元素,放入返回的数组中 如:dataBlob
//数组判断
if (Array.isArray(data)) {
data.map(function (item) {
dataBlob.push({
key: i,
goodsId: item.goodsId,
activityId: item.activityId,
type: item.type,
name: item.name,
imgPath: item.imgPath,
discountPrice: item.discountPrice,
buycount: item.buycount,
tempTest: item.tempTest,
})
i++;
});
}
this.setState({
//复制数据源
dataArray: dataBlob,
isLoading: false,
refreshing: false,
loadMore: false,
})
data = null;
dataBlob = null;
});
}
/**
*
* 选择事件
*
* */
changeSegment = (event) => {
this.state.index = event.nativeEvent.selectedSegmentIndex;
if (event.nativeEvent.selectedSegmentIndex == 0) {
this.state.sort = "sales";
this.state.desc = 1;
} else if (event.nativeEvent.selectedSegmentIndex == 1) {
this.state.sort = "price";
this.state.desc = 1;
} else {
this.state.sort = "price";
this.state.desc = 0;
}
//网络请求
this.requestData();
}
/**
*
* 按钮事件
*
* */
_onPressItem = (item: Object) => {
FFRNCatgoryListCtrl.addEventTwo('occ', item);
//FFRNCatgoryListCtrl.print("Hello World");
};
/**
*
* 列表单元格
*
* */
_renderCell = (item) => {
var title = '';
var discountPrice = '';
var buycount = '';
var imgPath = '';
var tempTest;
var subData;
if (this.state.dataArray.length > item.index) {
subData = this.state.dataArray[item.index];
title = this.state.dataArray[item.index].name;
discountPrice = '¥' + this.state.dataArray[item.index].discountPrice;
buycount = '销售指数' + this.state.dataArray[item.index].buycount;
imgPath = this.state.dataArray[item.index].imgPath;
if (imgPath.includes('size')) {
imgPath = imgPath.replace('size', 'origin');
}
tempTest = this.state.dataArray[item.index].tempTest;
}
return (
<TouchableOpacity onPress={()=>{this._onPressItem(subData)}} activeOpacity={0.8}>
<View style={styles.topicCard}>
<View>
<Image style={styles.avatarImg} source={{uri: imgPath}} resizeMode='cover'/>
</View>
<View style={styles.titleMeta}>
<Text style={styles.topicTitle} numberOfLines={2}>{title}</Text>
<View style={styles.metaarea}>
<Text style={styles.metainfo}>{discountPrice}</Text>
<Text style={styles.metainfo}>{tempTest}</Text>
</View>
<View style={styles.metaareatag}>
{/*<Text style={styles.nodename}>{'occ'}</Text>*/}
<View style={styles.replieCountBg}>
<Text style={styles.replieCount}>{buycount}</Text>
</View>
</View>
</View>
</View>
</TouchableOpacity>
);
}
_header = () => {
return <Text style={[styles.txt, {backgroundColor: 'white'}]}></Text>;
}
_footer = () => {
return <Text style={[styles.txt, {backgroundColor: 'white'}]}></Text>;
}
_separator = () => {
return <View style={{height: 2, backgroundColor: 'yellow'}}/>;
}
/**
*
* 刷新事件
*
* */
_onRefresh = () => {
this.setState({refreshing: true})
this.requestData();
}
/**
*
* 加载更多 <未实现,有问题>
*
* */
_loadMoreData = () => {
// alert("-- 加载更多");
// this.state.loadMore = true;
// this.requestData();
}
renderData() {
return (
<View style={{flex: 1}}>
<SegmentedControlIOS
//enabled={false}
selectedIndex={this.state.index}
//momentary={true}
onChange={this.changeSegment}
tintColor=''
values={['按销量排序', '按价格高到低', '按价格低到高']}/>
<AnimatedFlatList
data={this.state.dataArray}
ListHeaderComponent={this._header}
ListFooterComponent={this._footer}
ItemSeparatorComponent={this._separator}
renderItem={this._renderCell}
//如果设置了此选项,则会在列表头部添加一个标准的RefreshControl控件,以便实现“下拉刷新”的功能。同时你需要正确设置refreshing属性。
onRefresh={this._onRefresh}
refreshing={this.state.refreshing}
//当所有的数据都已经渲染过,并且列表被滚动到距离最底部不足onEndReachedThreshold个像素的距离时调用。
//onEndReached={(info) => { alert("滑动到底部了");}}
//onEndReached={this._loadMoreData()}
//onEndReachedThreshold={100}
/>
</View>
);
}
render() {
//在JS中,我们通过以下方式获取到原生模块的类名
// FFRNCatgoryListCtrl.getNativeClass(name => {
// console.log("nativeClass: ", name);
// //alert(name);
// });
// console.log("FFRNCatgoryListCtrl value is ", FFRNCatgoryListCtrl.TestName);
//alert(FFRNCatgoryListCtrl.TestName);
//alert(this.props.cid);
this.state.cid = this.props.cid;
//this.callBackOne();
//this.callErrorBack();
//第一次加载等待的view
if (this.state.isLoading && !this.state.error) {
return this.renderLoadingView();
} else if (this.state.error) {
//请求失败view
return this.renderErrorView(this.state.errorInfo);
}
//加载数据
return this.renderData();
}
}
AppRegistry.registerComponent('EasyLife', () => EasyLife);