Original link React Native achieved with a screening function room search list (3)
In the previous two articles already describes how to implement a pull-down refresh and support the LAC upload more Redux list and how to use one-way data flow, and that this will be the last module introduces the development of next screening function, namely React Native native communication with iOS.
Before you begin, or look at the final results achieved
React Native on how to communicate with the native iOS, there are very clear tutorial official website, I will not elaborate here. I share mainly how to use those interfaces to achieve such an effect.
In the project, the screening strip and the corresponding submenus are using native code implementation, and use the list js code implementation. We have to do is to add these to the original page by page js bridging manner.
Bridging implemented in FHTFilterMenuManager
this class, which is implemented as follows:
- Native to achieve
// .h
#import <React/RCTViewManager.h>
#import "FHTFilterMenu.h"
@interface FHTFilterMenu (RNBridge)
@property (nonatomic, copy) RCTBubblingEventBlock onUpdateParameters;
@property (nonatomic, copy) RCTBubblingEventBlock onChangeParameters;
@end
@interface RCTConvert (FHTFilterMenu)
@end
@interface FHTFilterMenuManager : RCTViewManager <RCTBridgeModule>
@end
// .m
#import "FHTFilterMenuManager.h"
#import <React/RCTUIManager.h>
#import <objc/runtime.h>
#import "FilterMenuRentTypeController.h"
#import "FilterMenuGeographicController.h"
#import "FilterMenuOrderByController.h"
#import "FilterMenuMoreController.h"
#import "FilterMenuRentalController.h"
static ConstString kFilterParams = @"filterParams";
typedef NS_ENUM(NSInteger, FilterMenuType) {
FilterMenuTypeNone,
FilterMenuTypeEntireRent, //整租
FilterMenuTypeSharedRent, //合租
FilterMenuTypeApartment, //独栋公寓
FilterMenuTypeBelowThousand, //千元房源
FilterMenuTypePayMonthly, //月付
FilterMenuTypeVR, //VR
};
@implementation RCTConvert (FHTFilterMenu)
RCT_ENUM_CONVERTER(FilterMenuType,
(@{@"None": @(FilterMenuTypeNone),
@"EntireRent": @(FilterMenuTypeEntireRent),
@"SharedRent": @(FilterMenuTypeSharedRent),
@"Apartment": @(FilterMenuTypeApartment),
@"BelowThousand": @(FilterMenuTypeBelowThousand),
@"PayMonthly": @(FilterMenuTypePayMonthly),
@"VR":@(FilterMenuTypeVR)}),
FilterMenuTypeNone,
integerValue);
@end
@implementation FHTFilterMenu (RNBridge)
#pragma mark - Setter & Getter
- (void)setOnUpdateParameters:(RCTBubblingEventBlock)onUpdateParameters {
objc_setAssociatedObject(self,
@selector(onUpdateParameters),
onUpdateParameters,
OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (RCTBubblingEventBlock)onUpdateParameters {
return objc_getAssociatedObject(self, @selector(onUpdateParameters));
}
- (void)setOnChangeParameters:(RCTBubblingEventBlock)onChangeParameters {
objc_setAssociatedObject(self,
@selector(onChangeParameters),
onChangeParameters,
OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (RCTBubblingEventBlock)onChangeParameters {
return objc_getAssociatedObject(self, @selector(onChangeParameters));
}
- (void)setFilterMenuType:(FilterMenuType)filterMenuType {
switch (filterMenuType) {
case FilterMenuTypeEntireRent: {
UIViewController *vc = (UIViewController *)self.filterControllers[0];
[vc presetWithOptionTitles:@[@"整租"]];
}
break;
case FilterMenuTypeSharedRent: {
UIViewController *vc = (UIViewController *)self.filterControllers[0];
[vc presetWithOptionTitles:@[@"合租"]];
}
break;
case FilterMenuTypeApartment: {
UIViewController *vc = (UIViewController *)self.filterControllers[3];
[vc presetWithOptionTitles:@[@"房源类型/独栋公寓"]];
}
break;
case FilterMenuTypeBelowThousand: {
UIViewController *vc = (UIViewController *)self.filterControllers[2];
[vc presetWithOptionTitles:@[@"1500以下"]];
}
break;
case FilterMenuTypePayMonthly: {
UIViewController *vc = (UIViewController *)self.filterControllers[3];
[vc presetWithOptionTitles:@[@"房源亮点/月付"]];
}
break;
case FilterMenuTypeVR: {
UIViewController *vc = (UIViewController *)self.filterControllers[3];
[vc presetWithOptionTitles:@[@"房源亮点/VR"]];
}
break;
default:
break;
}
};
@end
@implementation FHTFilterMenuManager
RCT_EXPORT_MODULE();
RCT_EXPORT_VIEW_PROPERTY(onUpdateParameters, RCTBubblingEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onChangeParameters, RCTBubblingEventBlock);
RCT_EXPORT_VIEW_PROPERTY(filterMenuType, FilterMenuType);
RCT_CUSTOM_VIEW_PROPERTY(cityId, NSString, FHTFilterMenu) {
FilterMenuGeographicController *vc = view.filterControllers[1];
vc.cityId = (NSString *)json;
};
RCT_CUSTOM_VIEW_PROPERTY(subwayData, NSArray, FHTFilterMenu) {
FilterMenuGeographicController *vc = view.filterControllers[1];
vc.originalSubwayData = (NSArray *)json;
};
RCT_EXPORT_METHOD(showFilterMenuOnView:(nonnull NSNumber *)containerTag filterMenuTag:(nonnull NSNumber *)filterMenuTag) {
RCTUIManager *uiManager = self.bridge.uiManager;
dispatch_async(uiManager.methodQueue, ^{
[uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *,UIView *> *viewRegistry) {
UIView *view = viewRegistry[containerTag];
FHTFilterMenu *filterMenu = (FHTFilterMenu *)viewRegistry[filterMenuTag];
[filterMenu showFilterMenuOnView:view];
}];
});
}
- (dispatch_queue_t)methodQueue {
return dispatch_get_main_queue();
}
- (UIView *)view {
FilterMenuRentTypeController *rentTypeVC =
[[FilterMenuRentTypeController alloc] initWithStyle:UITableViewStylePlain];
FilterMenuGeographicController *geographicVC = [FilterMenuGeographicController new];
FilterMenuRentalController *rentalVC =
[[FilterMenuRentalController alloc] initWithStyle:UITableViewStylePlain];
FilterMenuMoreController *moreVC = [FilterMenuMoreController new];
FilterMenuOrderByController *orderByVC =
[[FilterMenuOrderByController alloc] initWithStyle:UITableViewStylePlain];
CGRect frame = CGRectMake(0, FULL_NAVIGATION_BAR_HEIGHT, SCREEN_WIDTH, 44);
FHTFilterMenu *filterMenu = [[FHTFilterMenu alloc] initWithFrame:frame];
filterMenu.filterControllers = @[rentTypeVC, geographicVC, rentalVC, moreVC, orderByVC];
[filterMenu dismissSubmenu:NO];
[filterMenu resetFilter];
[rentTypeVC presetWithOptionTitles:@[]];
__weak FHTFilterMenu *weakFilterMenu = filterMenu;
rentTypeVC.didSetFilterHandler = ^(NSDictionary * _Nonnull params) {
BLOCK_EXEC(weakFilterMenu.onUpdateParameters, @{kFilterParams: params});
};
geographicVC.didSetFilterHandler = ^(NSDictionary * _Nonnull params) {
NSMutableDictionary *tmpParams = [@{@"regionId": nn_makeSureString(params[@"regionId"]),
@"zoneIds": nn_makeSureArray(params[@"zoneIds"]),
@"subwayRouteId": nn_makeSureString(params[@"subwayRouteId"]),
@"subwayStationCodes": nn_makeSureArray(params[@"subwayStationCodes"])} mutableCopy];
BLOCK_EXEC(weakFilterMenu.onUpdateParameters, @{kFilterParams: [tmpParams copy]});
};
rentalVC.didSetFilterHandler = ^(NSDictionary * _Nonnull params) {
NSDictionary *tmpParams = @{@"minPrice": nn_makeSureString(params[@"minPrice"]),
@"maxPrice": nn_makeSureString(params[@"maxPrice"])};
BLOCK_EXEC(weakFilterMenu.onUpdateParameters, @{kFilterParams: tmpParams});
};
moreVC.didSetFilterHandler = ^(NSDictionary * _Nonnull params) {
NSArray *typeArray = params[@"typeArray"];
NSString *type = typeArray.count == 1 ? (typeArray.lastObject)[@"type"] : @"";
NSDictionary *tmpParams = @{@"roomAttributeTags": params[@"highlightArray"],
@"chamberCounts": params[@"chamberArray"],
@"type": type};
BLOCK_EXEC(weakFilterMenu.onUpdateParameters, @{kFilterParams: tmpParams});
};
orderByVC.didSetFilterHandler = ^(NSDictionary * _Nonnull params) {
BLOCK_EXEC(weakFilterMenu.onUpdateParameters, @{kFilterParams: params});
};
filterMenu.filterDidChangedHandler = ^(FHTFilterMenu * _Nonnull filterMenu, id<FHTFilterController> _Nonnull filterController) {
BLOCK_EXEC(weakFilterMenu.onChangeParameters, nil);
};
return filterMenu;
}
@end
复制代码
- JS achieve
import React, { Component } from 'react';
import { requireNativeComponent, NativeModules, findNodeHandle } from 'react-native';
const FilterMenu = requireNativeComponent('FHTFilterMenu', SearchFilterMenu);
const filterMenuManager = NativeModules.FHTFilterMenuManager;
export const FilterMenuType = {
NONE: 'None',
ENTIRERENT: 'EntireRent',
SHAREDRENT: 'SharedRent',
APARTMENT: 'Apartment',
BELOWTHOUSAND: 'BelowThousand',
PAYMONTHLY: 'PayMonthly',
VR: 'VR'
}
export default class SearchFilterMenu extends Component {
componentDidUpdate() {
const filterMenuTag = findNodeHandle(this.refs.filterMenu);
const containerTag = findNodeHandle(this.props.containerRef);
if (filterMenuTag && containerTag) filterMenuManager.showFilterMenuOnView(containerTag, filterMenuTag);
}
render() {
return <FilterMenu ref='filterMenu' {...this.props} />;
}
}
复制代码
- JS call
<SearchFilterMenu
style={styles.filterMenu}
cityId={`${home.cityId}`}
subwayData={home.subwayData}
containerRef={this.refs.container}
filterMenuType={this.params.filterMenuType}
onChangeParameters={() => this._loadData(true)}
onUpdateParameters={({ nativeEvent: { filterParams } }) => {
this.filterParams = {
...this.filterParams,
...filterParams,
};
}}
/>
复制代码
Creating ViewManager
FHTFilterMenuManager
Is inherited from RCTViewManager
each of the native UI needs to be a RCTViewManager
sub-class to create and manage. The program is running, RCTViewManager
will create a native UI and to provide to the view RCTUIManager
, RCTUIManager
in turn, entrusted RCTViewManager
property when needed to set up and update the view. There is a point of note: ViewManager naming format is a native component Manager name + .
The most important is the ViewManager must be implemented - (UIView *)view
, used to return the native UI that you want to bridge.
React Native transfer properties from the native component to
We know that property is the most simple cross-component communication, if the RN component receives a property, they can pass RCT_EXPORT_VIEW_PROPERTY
and RCT_CUSTOM_VIEW_PROPERTY
passed to the native component of the way.
RCT_EXPORT_VIEW_PROPERTY
JS may be exposed to the original green component carrying properties. So FHTFilterMenuManager
, I own three properties exposed to JS, respectively,
// 点击确定按钮执行网络请求的block
RCT_EXPORT_VIEW_PROPERTY(onUpdateParameters, RCTBubblingEventBlock);
// 点击子菜单item,参数变更的block
RCT_EXPORT_VIEW_PROPERTY(onChangeParameters, RCTBubblingEventBlock);
// 筛选菜单的类型,
RCT_EXPORT_VIEW_PROPERTY(filterMenuType, FilterMenuType);
复制代码
Here I used the way the classification of the properties of native components to expand, because in the project, I do not want to have too many native components RN bridge-related code, so the use of classified expand, so the code can also be decoupled.
There is a point Note: If you own property is a block type, then the property name must start with ON .
RCT_CUSTOM_VIEW_PROPERTY
It allows us to add some more complex properties. Because cityId
and subwayData
is FilterMenuGeographicController
this submenu only required if they are set to FHTFilterMenu
the property is not appropriate, but FilterMenuGeographicController
we do not exposed to JS, so we can use RCT_CUSTOM_VIEW_PROPERTY
to not be exposed to the object JS property transfer.
RCT_CUSTOM_VIEW_PROPERTY(cityId, NSString, FHTFilterMenu) {
FilterMenuGeographicController *vc = view.filterControllers[1];
vc.cityId = (NSString *)json;
};
RCT_CUSTOM_VIEW_PROPERTY(subwayData, NSArray, FHTFilterMenu) {
FilterMenuGeographicController *vc = view.filterControllers[1];
vc.originalSubwayData = (NSArray *)json;
};
复制代码
React Native native component method call
RCT_EXPORT_METHOD
To provide native method calls to the JS. RCT_EXPORT_METHOD(showFilterMenuOnView:(nonnull NSNumber *)containerTag filterMenuTag:(nonnull NSNumber *)filterMenuTag)
Used to implement added to a submenu SearchHousePage
, which achieve the following:
RCT_EXPORT_METHOD(showFilterMenuOnView:(nonnull NSNumber *)containerTag filterMenuTag:(nonnull NSNumber *)filterMenuTag) {
RCTUIManager *uiManager = self.bridge.uiManager;
dispatch_async(uiManager.methodQueue, ^{
[uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *,UIView *> *viewRegistry) {
UIView *view = viewRegistry[containerTag];
FHTFilterMenu *filterMenu = (FHTFilterMenu *)viewRegistry[filterMenuTag];
[filterMenu showFilterMenuOnView:view];
}];
});
}
复制代码
Wherein containerTag
the code used to represent SearchHousePage
, filterMenuTag
said filter criteria. Note that there is a point: You can not use self.view way because it will create a new FHTFilterMenu objects, but can not - (UIView *)view
use for reference refers to a creation out of the View, then if your components are used in multiple pages using , it will go wrong .
Further, since the iOS UI operation to put the main thread is done, it is preferable methodQueue
to specify the main thread.
end
Here the whole SearchHousePage
development of the page has been completed, if you need to see the complete code words in the code portal --NNHybrid in.