在React Native App上之前使用的是通过Webview渲染一张Web地图(https://map.qq.com/api/gljs?v=1.exp&key=XXX),这么做的弊端就是速度慢而且不稳定,之前也用过高德地图,为了和微信小程序保持一致,需要用腾讯地图。
参考
实现的功能
- 地图中心点
- 缩放比例
- 地图控件(指南针,比例尺子)
- 多个标记点
- 根据多点设置最合适的视野
- 初次渲染完成回调
- 点击事件
- 主动移动地图后返回中心点坐标
申请apiKey
- 前往控制台
- 应用管理
- 我的应用
- 创建应用
- 要选中SDK
配置腾讯地图SDK
require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
pod 'AlipaySDK-iOS'
pod 'Tencent-MapSDK' <= 添加这个
...
cd ios && pod install
初始化地图服务
参考:配置开发秘钥、 隐私合规
创建TencentMap.h
和TencentMap.m
- TencentMap.h
#import <React/RCTViewManager.h>
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <QMapKit/QMapKit.h>
@interface TencentMap : RCTViewManager
@end
- TencentMap.m
#import "TencentMap.h"
#import <React/RCTUIManager.h>
#import <React/UIView+React.h>
#import <QMapKit/QMSSearchKit.h>
@implementation TencentMap
RCT_EXPORT_MODULE();
// 初始化Key
RCT_EXPORT_METHOD(initQMap: (NSString *)key) {
[QMapServices sharedServices].APIKey = key;
[QMSSearchServices sharedServices].apiKey = key;
if ([QMapServices sharedServices].APIKey.length == 0 || [QMSSearchServices sharedServices].apiKey.length == 0){
NSLog(@"Fail");
} else {
NSLog(@"Success");
}
return;
}
// 同意隐私协议
RCT_EXPORT_METHOD(agreementQMap) {
[[QMapServices sharedServices] setPrivacyAgreement:YES];
return;
}
@end
- React Native 在我的App同意隐私协议用户协议之后调用
import {NativeModules} from 'react-native';
const TencentMap = NativeModules.TencentMap;
const initNativeQQMap = () => {
if (TencentMap && TencentMap["initQMap"]) {
TencentMap["initQMap"](KEY); <= 之前申请的apikey
if (TencentMap["agreementQMap"]) {
TencentMap["agreementQMap"]();
}
}
}
创建地图
创建TencentMapView.h
和TencentMapView.m
以及工具TencentMapUtils.h
和TencentMapUtils.m
- TencentMapView.h
#import <UIKit/UIKit.h>
#import <QMapKit/QMapKit.h>
#import <React/RCTComponent.h>
#import <React/RCTViewManager.h>
@interface TencentMapView : UIView <QMapViewDelegate>
@property (nonatomic, strong) QMapView *mapView;
@property (nonatomic, assign) float zoomLevel; // 缩放级别
@property (nonatomic, assign) BOOL scaleViewFadeEnable; // 比例尺
@property (nonatomic, assign) BOOL showCompass; // 指南针
@property (nonatomic, strong) NSArray *coordinates; // 合适区域
@property (nonatomic, assign) CLLocationCoordinate2D mapCenter; // 中心点
@property (nonatomic, strong) NSArray *markers; // 标记点
@property (nonatomic, strong) NSArray *linePoints; // 线条点
@property (nonatomic, assign) BOOL isFirstRenderComplete; // 标记地图首次渲染是否完成
@property (nonatomic, strong) NSMutableArray<QPointAnnotation *> *markerAnnotations; // 多个标记点
@property (nonatomic, strong) NSMutableArray<QPolyline *> *markerLines; // 多个线条点
@property (nonatomic, copy) RCTBubblingEventBlock onClick; // 点击事件回调
@property (nonatomic, copy) RCTBubblingEventBlock onMapLoad; // 完成初次渲染
@property (nonatomic, copy) RCTBubblingEventBlock onRegionChange; // 移动回调
@end
@interface TencentMapViewManager : RCTViewManager
@end
@interface CustomPointAnnotation : QPointAnnotation
@property (nonatomic, strong) NSDictionary *userInfo; // 存储额外数据
@end
- TencentMapView.m
#import "TencentMapView.h"
#import "TencentMapUtils.h"
@implementation CustomPointAnnotation
@end
@implementation TencentMapView
- (instancetype)init {
self = [super init];
if (self) {
// 初始化地图视图
self.mapView = [[QMapView alloc] initWithFrame:self.bounds];
self.mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.mapView setMapType:QMapTypeStandard];
[self.mapView setLogoScale:0.7];
[self.mapView setZoomLevel:10];
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(39.9042, 116.4074) animated:YES];
self.mapView.delegate = self; // 设置代理
[self addSubview:self.mapView];
_isFirstRenderComplete = NO;
_markerAnnotations = [NSMutableArray array];
_markerLines = [NSMutableArray array];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
self.mapView.frame = self.bounds; // 确保地图视图填充父视图
}
// props传参数
#pragma mark - Props
// 设置中心点
- (void)setMapCenter:(CLLocationCoordinate2D)mapCenter {
if (!CLLocationCoordinate2DIsValid(mapCenter)) {
return;
}
_mapCenter = mapCenter;
[self.mapView setCenterCoordinate:mapCenter animated:YES];
}
// 设置是否显示指南针
- (void)setShowCompass:(BOOL)showCompass {
_showCompass = showCompass;
self.mapView.showsCompass = showCompass;
}
// 设置比例尺
- (void)setScaleViewFadeEnable:(BOOL)scaleViewFadeEnable{
_scaleViewFadeEnable = scaleViewFadeEnable;
self.mapView.showsScale = scaleViewFadeEnable;
}
// 设置地图缩放级别
- (void)setZoomLevel:(float)zoomLevel {
_zoomLevel = zoomLevel;
[self.mapView setZoomLevel:zoomLevel animated:YES];
}
// 计算边界矩形
- (void)setCoordinates:(NSArray *)coordinates {
_coordinates = coordinates;
if (coordinates.count > 0 && _isFirstRenderComplete) {
[self adjustMapRegionToFitCoordinates];
}
}
- (void) setLinePoints:(NSArray *)linePoints {
_linePoints = linePoints;
NSUInteger count = linePoints.count;
if(count < 2){
return; // 至少需要两个点才能绘制路线
}
if(_markerLines.count > 0){
[self.mapView removeOverlays:_markerLines];
[_markerLines removeAllObjects];
}
CLLocationCoordinate2D routeCoordinates[count];
for (NSUInteger i = 0; i < count; i++) {
NSDictionary *point = linePoints[i];
routeCoordinates[i] = CLLocationCoordinate2DMake(
[point[@"latitude"] doubleValue],
[point[@"longitude"] doubleValue]
);
}
QPolyline *polyline = [QPolyline polylineWithCoordinates:routeCoordinates count:count];
[self.mapView addOverlay:polyline];
[_markerLines addObject:polyline];
}
- (void)setMarkers:(NSArray *)markers {
_markers = markers;
// 移除旧的标记点
if (_markerAnnotations.count > 0) {
[self.mapView removeAnnotations:_markerAnnotations];
[_markerAnnotations removeAllObjects];
}
// 添加新的标记点
for (NSDictionary *marker in markers) {
NSDictionary *coordinateDict = marker[@"coordinate"]; // 图标位置
NSDictionary *sizeDict = marker[@"size"]; // 图标大小
NSDictionary *offsetDict = marker[@"offset"]; // 偏移
NSString *icon = marker[@"icon"]; // 网络图标 URL
if(!icon){
icon = @"";
}
if(!sizeDict){
if(icon.length){
sizeDict = @{@"width": @40, @"height": @40};
} else {
sizeDict = @{@"width": @36, @"height": @51};
}
}
if (!offsetDict){
offsetDict = @{@"x": @0, @"y": @0};
}
CustomPointAnnotation *pointAnnotation = [[CustomPointAnnotation alloc] init];
pointAnnotation.coordinate = CLLocationCoordinate2DMake([coordinateDict[@"latitude"] doubleValue], [coordinateDict[@"longitude"] doubleValue]);
pointAnnotation.userInfo = @{ @"icon": icon, @"size": sizeDict, @"offset": offsetDict, };
// 将点标记添加到地图中
[self.mapView addAnnotation:pointAnnotation];
[_markerAnnotations addObject:pointAnnotation];
}
}
#pragma mark - Helper Methods
// 计算边界矩形并调整地图区域
- (void)adjustMapRegionToFitCoordinates {
NSUInteger count = self.coordinates.count;
if (count == 0) {
return;
}
CLLocationCoordinate2D coordinates[count];
for (NSUInteger i = 0; i < count; i++) {
NSDictionary *coordinate = self.coordinates[i];
CLLocationDegrees lat = [coordinate[@"latitude"] doubleValue];
CLLocationDegrees lng = [coordinate[@"longitude"] doubleValue];
coordinates[i] = CLLocationCoordinate2DMake(lat, lng);
}
QCoordinateRegion region = QBoundingCoordinateRegionWithCoordinates(coordinates, count);
[self.mapView setRegion:region edgePadding:UIEdgeInsetsMake(30, 30, 30, 30) animated:YES];
}
#pragma mark - QMapViewDelegate
- (void)mapView:(QMapView *)mapView regionDidChangeAnimated:(BOOL)animated gesture:(BOOL)bGesture {
if (bGesture) {
// 获取地图视图的中心点坐标
CLLocationCoordinate2D centerCoordinate = mapView.centerCoordinate;
// 触发事件,将中心点坐标传递给 React Native
if (self.onRegionChange) {
self.onRegionChange(@{
@"latitude": @(centerCoordinate.latitude),
@"longitude": @(centerCoordinate.longitude)
});
}
}
}
- (void)mapView:(QMapView *)mapView didTapAtCoordinate:(CLLocationCoordinate2D)coordinate {
// 地图点击事件
if (self.onClick) {
self.onClick(@{
@"latitude": @(coordinate.latitude), // 纬度
@"longitude": @(coordinate.longitude) // 经度
});
}
}
- (void)mapViewFirstRenderDidComplete:(QMapView *)mapView {
// 地图首次渲染完成后调用
_isFirstRenderComplete = YES;
if (self.coordinates.count > 0) {
[self adjustMapRegionToFitCoordinates];
}
if (self.onMapLoad){
self.onMapLoad(@{});
}
}
- (QOverlayView *)mapView:(QMapView *)mapView viewForOverlay:(id<QOverlay>)overlay {
if ([overlay isKindOfClass:[QPolyline class]]) {
QPolylineView *polylineView = [[QPolylineView alloc] initWithPolyline:overlay];
polylineView.strokeColor = [UIColor colorWithRed:118/255.0 green:192/255.0 blue:111/255.0 alpha:1.0]; // 设置路线颜色
polylineView.lineWidth = 6; // 设置路线宽度
polylineView.borderWidth = 1;
polylineView.borderColor = [UIColor whiteColor];
return polylineView;
}
return nil;
}
- (QAnnotationView *)mapView:(QMapView *)mapView viewForAnnotation:(id<QAnnotation>)annotation {
if ([annotation isKindOfClass:[CustomPointAnnotation class]]) {
static NSString *annotationIdentifier = @"pointAnnotation";
QPinAnnotationView *pinView = (QPinAnnotationView *)[self.mapView dequeueReusableAnnotationViewWithIdentifier:annotationIdentifier];
if (pinView == nil) {
pinView = [[QPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:annotationIdentifier];
pinView.canShowCallout = NO;
NSString *icon = ((CustomPointAnnotation *)annotation).userInfo[@"icon"];
NSDictionary* size = ((CustomPointAnnotation *)annotation).userInfo[@"size"];
NSDictionary* offset = ((CustomPointAnnotation *)annotation).userInfo[@"offset"];
CGFloat width = [size[@"width"] doubleValue];
CGFloat height = [size[@"height"] doubleValue];
CGFloat offsetX = [offset[@"x"] doubleValue];
CGFloat offsetY = [offset[@"y"] doubleValue];
// 设置偏移(没法准确)
// pinView.centerOffset = CGPointMake(offsetX, offsetY);
// 默认图标marker添加到Images.xcassets
if (icon.length > 0) {
[TencentMapUtils loadImageFromURL:icon completion:^(UIImage *image) {
if (image) {
CGSize newSize = CGSizeMake(width, height);
UIImage *resizedImage = [TencentMapUtils resizeImage:image toSize:newSize];
pinView.image = resizedImage;
} else {
// 设置默认marker大小
UIImage *image = [UIImage imageNamed:@"marker"];
CGSize newSize = CGSizeMake(width, height);
UIImage *resizedImage = [TencentMapUtils resizeImage:image toSize:newSize];
pinView.image = resizedImage;
}
}];
} else {
// 设置默认marker大小
UIImage *image = [UIImage imageNamed:@"marker"];
CGSize newSize = CGSizeMake(width, height);
UIImage *resizedImage = [TencentMapUtils resizeImage:image toSize:newSize];
pinView.image = resizedImage;
}
}
return pinView;
}
return nil;
}
@end
// 导出地图组件
@implementation TencentMapViewManager
RCT_EXPORT_MODULE(TencentMapView)
- (UIView *)view {
return [[TencentMapView alloc] init];
}
RCT_EXPORT_VIEW_PROPERTY(mapCenter, CLLocationCoordinate2D)
RCT_EXPORT_VIEW_PROPERTY(zoomLevel, float)
RCT_EXPORT_VIEW_PROPERTY(scaleViewFadeEnable, BOOL)
RCT_EXPORT_VIEW_PROPERTY(showCompass, BOOL)
RCT_EXPORT_VIEW_PROPERTY(coordinates, NSArray)
RCT_EXPORT_VIEW_PROPERTY(markers, NSArray)
RCT_EXPORT_VIEW_PROPERTY(linePoints, NSArray)
RCT_EXPORT_VIEW_PROPERTY(onClick, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onMapLoad, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onRegionChange, RCTBubblingEventBlock)
@end
- TencentMapUtils.h
#import <UIKit/UIKit.h>
@interface TencentMapUtils : NSObject
// 调整图片大小
+ (UIImage *)resizeImage:(UIImage *)image toSize:(CGSize)size;
// 从 URL 加载图片
+ (void)loadImageFromURL:(NSString *)urlString completion:(void (^)(UIImage *))completion;
@end
- TencentMapUtils.m
#import "TencentMapUtils.h"
#import <Foundation/Foundation.h>
@implementation TencentMapUtils
// 调整图片大小
+ (UIImage *)resizeImage:(UIImage *)image toSize:(CGSize)size {
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
[image drawInRect:CGRectMake(0, 0, size.width, size.height)];
UIImage *resizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return resizedImage;
}
// 从 URL 加载图片
+ (void)loadImageFromURL:(NSString *)urlString completion:(void (^)(UIImage *))completion {
NSURL *url = [NSURL URLWithString:urlString];
if (!url) {
completion(nil);
return;
}
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (data && !error) {
UIImage *image = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
completion(image);
});
} else {
dispatch_async(dispatch_get_main_queue(), ^{
completion(nil);
});
}
}];
[task resume];
}
@end
React Native 使用
import React from 'react';
import {requireNativeComponent, StyleSheet, ViewStyle} from 'react-native';
const TencentMapView = requireNativeComponent('TencentMapView');
interface Marker {
// 标记点位置
coordinate: {
latitude: number;
longitude: number;
};
// 标记点位置
size?: {
width: number;
height: number;
};
// 标记点网络图片
icon?: string;
// 偏移度
// offset?: {
// x: number;
// y: number;
// }
}
interface Location {
latitude: number;
longitude: number;
}
interface Props {
// 比例尺
scaleViewFadeEnable?: boolean;
// 样式
style?: ViewStyle;
// 比例
zoom?: number;
// 中心点经纬度
lng?: number;
lat?: number;
// 指南针
showCompass?: boolean;
// 标记点
markers?: Marker[];
// 点击事件
onClick?: (e: Location) => void;
// 移后事件
onRegionChange?: (e: Location) => void;
// 初次完成
onLoad?: () => void;
// 安全区域
coordinates?: Location[];
// 线条
linePoints?: Location[];
}
const NativeMapComponent: React.FC<Props> = (props) => {
const defaultPoint = React.useRef({latitude: 39.9042, longitude: 116.4074}).current;
const {style, zoom, lat, lng, scaleViewFadeEnable, showCompass, markers, coordinates, linePoints, onClick, onLoad, onRouteDistance, onRegionChange} = props;
const checkLocation = React.useCallback((lat, lng) => {
if (lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180) {
return {latitude: lat, longitude: lng};
}
return defaultPoint;
}, [defaultPoint]);
const center = React.useMemo(() => {
if (typeof lng !== "undefined" && typeof lat !== "undefined") {
const latitude = Number(lat);
const longitude = Number(lng);
return checkLocation(latitude, longitude);
}
return defaultPoint;
}, [lat, lng, defaultPoint, checkLocation]);
return <TencentMapView
linePoints={linePoints || []}
scaleViewFadeEnable={scaleViewFadeEnable || false}
zoomLevel={zoom || 10}
style={StyleSheet.flatten([styles.container, StyleSheet.flatten(style)])}
mapCenter={center}
showCompass={showCompass || false}
markers={markers || []}
coordinates={coordinates || []}
onRegionChange={onRegionChange ? (event) => {
if (onRegionChange) {
const {latitude, longitude} = event.nativeEvent;
onRegionChange({latitude, longitude});
}
} : undefined}
onClick={onClick ? (event) => {
if (onClick) {
const {latitude, longitude} = event.nativeEvent;
onClick({latitude, longitude});
}
} : undefined}
onMapLoad={onLoad ? () => {
if (onLoad) {
onLoad();
}
} : undefined}
/> ;
};
const styles = StyleSheet.create({
container: {
width: "100%",
height: "100%",
backgroundColor: "#f2f1ee"
},
});
export default NativeMapComponent;
使用
- 基础地图
<NativeMapComponent
onRegionChange={(e) => {
}}
onClick={(e) => {
}}
markers={[{
coordinate: {
longitude: 120.450178,
latitude: 29.054048,
},
}]}
showCompass={true}
scaleViewFadeEnable={true}
zoom={10}
lng={120.450178}
lat={29.054048}
style={{
width: "100%",
height: "100%",
}}
/>
- 自定义图标
<NativeMapComponent
onRegionChange={(e) => {
}}
onClick={(e) => {
}}
markers={[{
icon: "https://xxxEnd.png",
coordinate: {
longitude: 120.450178,
latitude: 29.054048,
},
size: {
width: 21,
height: 52
},
}]}
// showCompass={true}
// scaleViewFadeEnable={true}
zoom={10}
lng={120.450178}
lat={29.054048}
style={{
width: "100%",
height: "100%",
}}
/>
- 线路图 (调整合适区域)
<NativeMapComponent
linePoints={linePoints}
markers={[
{
coordinate: {latitude: 39.9042, longitude: 116.4074},
icon: "https://xxxEnd.png",
size: {width: 21, height: 52}
},
{
coordinate: {latitude: 31.2304, longitude: 121.4737},
icon: "https://xxxgreenMark.png",
size: {width: 14, height: 14}
},
{
coordinate: {latitude: 23.1291, longitude: 113.2644},
icon: "https://xxxStart.png",
size: {width: 21, height: 52}
},
]}
coordinates={[
{latitude: 39.9042, longitude: 116.4074},
{latitude: 31.2304, longitude: 121.4737},
{latitude: 23.1291, longitude: 113.2644},
]}
/>
对比Web地图
第一张是web地图,第二张是IOS原生地图
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。