I have a compass that I created in react native. It has a couple of issues and I don't know how to best approach them.
Firstly, the arrow in the middle is hardcoded with top and left which seems to make it not responsive. I tried setting parent to relative and arrow to absolute.
Secondly, the angle doesn't stay consistent to the heading. Realistically, heading will fluctuate as the user moves the phone. Going anywhere from 0
to 360
in value.
The issue is that the angle needs to follow the heading. At 0
... they seem to be fine but if the heading is changed to 200... the angle should be facing towards 0
not 200
.
I tried to change the transform for arrow to this:
270 - heading - angle
But seems to be off sometimes, regardless
How can i go about solving this?
export const App: React.FC = () => {
const [angle, setAngle] = useState(0);
const [heading, setHeading] = useState(200);
const updateHeading = (headingObject) => {
const roundedHeading = Math.round(headingObject.trueHeading);
setHeading(roundedHeading);
};
return (
<View style={{ backgroundColor: 'gray' }}>
<View style={styles.row}>
<Text style={styles.angle}>Angle is: {angle}°</Text>
<View style={styles.arrowContainer}>
<Image
source={require('./assets/arrow.png')}
style={{
width: width / 10,
height: width / 10,
left: '47%',
top: 160,
resizeMode: 'contain',
transform: [{ rotate: angle + 'deg' }],
}}
/>
</View>
</View>
<View
style={styles.compassWrapper}>
<Image
source={require('./assets/compass_bg.png')}
style={{
height: width - 80,
width: width,
resizeMode: 'contain',
transform: [{ rotate: 270 - heading + 'deg' }],
}}
/>
</View>
</View>
);
};
export default App;
Demo:
Your math is close. The arrow.png image is oriented already pointing "north", or up, but the compass_bg.png image is oriented with "north" pointing "east", or +90°. This image needs either -90° or +270° applied as a starting point and then the heading
value added to it same as the arrow.
transform: [{ rotate: -90 + heading + 'deg' }],
Next is the issue of arrow positioning:
export const App: React.FC = () => {
const [angle, setAngle] = useState(75);
const [heading, setHeading] = useState(200);
const updateHeading = (headingObject) => {
const roundedHeading = Math.round(headingObject.trueHeading);
setHeading(roundedHeading);
};
return (
<View style={{ backgroundColor: 'gray' }}>
{
Number.isInteger(angle) ? (
<View style={styles.row}>
<Text style={styles.angle}>Angle is: {angle}°</Text>
<View style={styles.arrowContainer}>
<Image
source={require('./assets/arrow.png')}
style={{
position: 'absolute',
width: width / 10,
height: width / 10,
left: '50%', // <-- center left/right
top: 160,
resizeMode: 'contain',
transform: [
{ translateX: '-50%'}, // <-- center image horizontally
{ translateY: '50%' }, // <-- center image vertically
{ rotate: angle + 'deg' }
],
}}
/>
</View>
</View>
) : ( null )
}
<View
style={styles.compassWrapper}>
<Image
source={require('./assets/compass_bg.png')}
style={{
height: width - 80,
width: width,
resizeMode: 'contain',
transform: [{ rotate: -90 + heading + 'deg' }],
}}
/>
</View>
</View>
);
}
We can improve this however by co-locating the arrow and compass images together.
Example:
export const App: React.FC = () => {
const [angle, setAngle] = useState(75);
const [heading, setHeading] = useState(200);
const updateHeading = (headingObject) => {
const roundedHeading = Math.round(headingObject.trueHeading);
setHeading(roundedHeading);
};
return (
<View style={{ backgroundColor: 'gray' }}>
{Number.isInteger(angle) && (
<View style={styles.row}>
<Text style={styles.angle}>Angle is: {angle}°</Text>
<View style={styles.compassWrapper}>
<Image
source={require('./assets/arrow.png')}
style={[styles.arrow, { transform: [{ rotate: angle + 'deg' }] }]}
/>
<Image
source={require('./assets/compass_bg.png')}
style={[
styles.compass_bg,
{ transform: [{ rotate: -90 + heading + 'deg' }] },
]}
/>
</View>
</View>
)}
</View>
);
};
export default App;
const styles = StyleSheet.create({
row: {
alignItems: 'center',
},
compassWrapper: {
alignItems: 'center',
justifyContent: 'center',
position: 'relative',
},
angle: {
color: '#fff',
fontSize: height / 25,
marginBottom: 30,
},
arrow: {
position: 'absolute',
width: 35,
height: 35,
resizeMode: 'contain',
},
compass_bg: {
height: width - 80,
width: width,
resizeMode: 'contain',
},
});
https://snack.expo.dev/@drew.w.reese/react-native-compass-angle-issues