#android #ios #react-native #geolocation #react-native-maps
#Android #iOS #react-native #геолокация #react-native-карты
Вопрос:
Я новичок в изучении React Native и был бы признателен за любую помощь, которая может быть предоставлена с моим кодом. В настоящее время я работаю над учебными пособиями и застрял на несколько часов. Надеюсь, этой информации достаточно, чтобы получить некоторую помощь. Заранее спасибо!
Моя цель
Я пытаюсь сделать три вещи (в таком порядке):
- Получить текущее местоположение пользователя
- Извлеките данные элементов из API (пример приведен ниже). Следует отметить, что в конечном итоге содержимое выборки будет зависеть от текущего местоположения пользователя.
- Проанализируйте данные элементов, чтобы создать маркеры и уникальный список категорий, и разместите их на карте с текущим местоположением пользователя в центре для начала.
Я хочу иметь возможность отслеживать местоположение пользователя и соответствующим образом перемещать и обновлять карту. Я также не хочу показывать карту, пока все маркеры не будут на месте.
Вот мои версии react: react-native-cli: 2.0.1 react-native: 0.63.2
Мои ошибки
Я использую как эмулятор Android Studio, так и эмулятор Xcode и в настоящее время сталкиваюсь со следующими проблемами:
- Эмулятор iOS в Xcode в первый раз отображается нормально, но при последующих обновлениях я вижу, что 1 или два из моих 5 маркеров отсутствуют.
- На Android карта загружается идеально, затем появляется, чтобы сразу перерисовать себя и сосредоточиться на Googleplex в Калифорнии вместо текущего местоположения пользователя. Местоположение эмулятора установлено на Лондон, Великобритания
Я подозреваю, что у меня может быть какое-то состояние гонки с выборкой элементов и текущего местоположения до отображения карты, но я не могу быть уверен.
Мой код
// структура ответа моего API
{
"id": "96845",
"title": "Item_title_goes_here",
"image": "https://someURL/image.JPG",
"stories": 46,
"lat": some_lat_number,
"lon": some_lon_number,
"category": "category_name",
"description": "long_description"
},
//ajax.js
export default {
async fetchInitialItems() {
try {
const response = await fetch(apiHost '/api/v1/items/');
const responseJson = await response.json();
return responseJson;
} catch (error) {
console.error(error);
}
},
async fetchItemDetail(itemId) {
try {
const response = await fetch(apiHost '/api/items/' itemId);
const responseJson = await response.json();
return responseJson;
} catch (error) {
console.error(error);
}
},
};
//ExploreScreen.js (my map component)
import React, { Component } from 'react';
import {
View,
Text,
StyleSheet,
Image,
Animated,
Dimensions,
TouchableOpacity,
PermissionsAndroid,
ScrollView,
Platform,
StatusBar,
} from 'react-native';
import MapView, {
PROVIDER_GOOGLE,
Marker,
Callout,
Polygon,
} from 'react-native-maps';
import PropTypes from 'prop-types';
import Geolocation from '@react-native-community/geolocation';
import { mapDarkStyle, mapStandardStyle } from '../model/mapData';
import ajax from '../utils/ajax';
import MapCarousel from './MapCarousel';
import Ionicons from 'react-native-vector-icons/Ionicons';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import Fontisto from 'react-native-vector-icons/Fontisto';
import StarRating from '../components/StarRating';
/**
|--------------------------------------------------
| Variables
|--------------------------------------------------
*/
const { width, height } = Dimensions.get('window');
const SCREEN_HEIGHT = height;
const SCREEN_WIDTH = width;
const ASPECT_RATIO = width / height;
const LATITUDE_DELTA = 0.0422;
const LONGITUDE_DELTA = LATITUDE_DELTA * ASPECT_RATIO;
const darkTheme = false;
/**
|--------------------------------------------------
| Component
|--------------------------------------------------
*/
class ExploreScreen extends React.Component {
/****** Props amp; States ******/
state = {
region: {
latitude: 0,
longitude: 0,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
},
items: [],
markers: [],
categories: [],
currentMapRegion: null,
};
/****** Functions ******/
///when carousel item selected
onCarouselItemSelected() {
alert('carousel item selected');
}
//when the carousel in scrolled
onCarouselIndexChange(itemID) {
const item = this.state.items.find(item => item.id == itemID);
const marker = this.state.markers.find(marker => marker.id == itemID);
const coordinates = { lat: item.lat, lon: item.lon };
this.goToLocation(coordinates);
}
//get current position
getLocation(that) {
Geolocation.getCurrentPosition(
//get the current location
position => {
const region = {
latitude: parseFloat(position.coords.latitude),
longitude: parseFloat(position.coords.longitude),
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
};
that.setState({ region });
},
error => alert(error.message),
{ enableHighAccuracy: true, timeout: 20000 },
);
//get location on location change
that.watchID = Geolocation.watchPosition(position => {
const currentRegion = {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
};
this.setState({ region: currentRegion });
});
}
//move map to a lat/lon
goToLocation(coordinates) {
if (this.map) {
this.map.animateToRegion({
latitude: coordinates.lat,
longitude: coordinates.lon,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
});
}
}
//when the region changes for the map
onRegionChangeComplete(region) {
//I dont know what to do here
}
//move map to center of current location
gotToCenter() {
if (this.map) {
this.map.animateToRegion({
latitude: this.state.region.latitude,
longitude: this.state.region.longitude,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
});
}
}
//map the categories store in the state
mapCategories = () => {
const uniqueCategories = [];
this.state.items.map(item => {
if (uniqueCategories.indexOf(item.category) === -1) {
uniqueCategories.push(item.category);
}
});
this.setState({ categories: uniqueCategories });
};
//map the items to markers and store in the state
mapMarkers = () => {
const markers = this.state.items.map(item => (
<Marker
key={item.id}
coordinate={{ latitude: item.lat, longitude: item.lon }}
image={require('../assets/map_marker.png')}
tracksViewChanges={false}
title={item.title}
description={item.description}
/>
));
this.setState({ markers: markers });
};
/****** Lifecycle Functions ******/
async componentDidMount() {
var that = this;
//Checking for the permission just after component loaded
if (Platform.OS === 'ios') {
//for ios
this.getLocation(that);
} else {
//for android
async function requestLocationPermission() {
try {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
{
title: 'Location Access Required',
message: 'This App needs to Access your location',
},
);
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
//To Check, If Permission is granted
that.getLocation(that);
} else {
alert('Permission Denied');
}
} catch (err) {
alert('err', err);
console.warn(err);
}
}
requestLocationPermission();
}
//get location on location change
that.watchID = Geolocation.watchPosition(position => {
const currentRegion = {
latitude: parseFloat(position.coords.latitude),
longitude: parseFloat(position.coords.longitude),
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
};
this.setState({ region: currentRegion });
});
console.log(this.state.region);
const items = await ajax.fetchInitialItems();
this.setState({ items });
this.mapMarkers();
this.mapCategories();
}
componentWillUnmount = () => {
Geolocation.clearWatch(this.watchID);
};
render() {
const lat = this.state.region.latitude;
const lon = this.state.region.longitude;
if (this.state.items amp;amp; lat amp;amp; lon) {
return (
<View>
<MapView
ref={map => {
this.map = map;
}}
onRegionChangeComplete={this.onRegionChangeComplete.bind(this)}
toolbarEnabled={false}
showsMyLocationButton={false}
provider={PROVIDER_GOOGLE}
style={styles.map}
customMapStyle={darkTheme ? mapDarkStyle : mapStandardStyle}
showsUserLocation={true}
followsUserLocation={true}
region={this.state.region}>
{this.state.markers}
</MapView>
{/* center button */}
<TouchableOpacity
onPress={this.gotToCenter.bind(this)}
style={styles.centerButtonContainer}>
<Ionicons name="md-locate" size={20} />
</TouchableOpacity>
<View style={styles.carousel}>
<MapCarousel
data={this.state.items}
onPressItem={() => {
this.onCarouselItemSelected.bind(this);
}}
onUpdateLocation={this.onCarouselIndexChange.bind(this)}
/>
</View>
</View>
);
} else {
return (
<View style={styles.container}>
<Text style={styles.header}>Loading...</Text>
</View>
);
}
}
}
/**
|--------------------------------------------------
| Styles
|--------------------------------------------------
*/
const styles = StyleSheet.create({
container: {
...StyleSheet.absoluteFillObject,
height: '100%',
width: '100%',
justifyContent: 'center',
alignItems: 'center',
},
map: {
height: '100%',
},
carousel: {
position: 'absolute',
bottom: 25,
},
header: {
fontSize: 50,
},
//character name
name: {
fontSize: 15,
marginBottom: 5,
},
//character image
image: {
width: 170,
height: 80,
},
//center button
centerButtonContainer: {
width: 40,
height: 40,
position: 'absolute',
bottom: 260,
right: 10,
borderColor: '#191919',
borderWidth: 0,
borderRadius: 30,
backgroundColor: '#d2d2d2',
justifyContent: 'center',
alignItems: 'center',
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 9,
},
shadowOpacity: 0.48,
shadowRadius: 11.95,
elevation: 18,
opacity: 0.9,
},
});
export default ExploreScreen;
Комментарии:
1. Сначала немного очистите код, у вас есть две точки наблюдения (вам действительно нужна эта функция?). Проблема в ios может заключаться в том, что вы устанавливаете элементы setState, но вы не думаете об этом, что вызов является асинхронным, поэтому, когда вы запускаете mapMarkers state.items может быть пустым, точно так же в вашем состоянии рендеринга.items всегда присутствует, потому что это [], поэтому, когда lat и lon есть, это будет рендеринг. Также вы можете удалить onRegionChangeComplete. Обновите меня результатами и изменениями.
2. Я не уверен, как решить проблему асинхронности. Делаю ли я вызовы синхронными? Если да, то каким образом?
3. Вы можете просто передавать элементы напрямую, если this.state.items равно null.
4. Я не уверен, как поступить здесь. Я сделал эти вызовы синхронными, но сейчас я ничего не вижу на карте.
5. что вы получили, когда mapMarkers отображается в консоли, регистрируйте this.state.items и в консоли метода визуализации. запишите это.состояние.маркеры