react-native-tvos - TouchableOpacity Causes SVG Elements to Disappear - react-native

I am using react-native-tvos to create an app.
I am trying to display SVG elements that respond to the remote.
I have tried using TouchableOpacity as well as Pressable on the elements.
Whenever I wrap the elements with those they no longer appear.
I've looked through dozens of S/O posts and have tried various things. Such as zIndex, flex on container. I also tried the react-native-gesture-handler, but that doesn't seem to work for tvos.
I've broken the code down to something very simple. Just a circle in the middle of the screen.
import Svg, {Circle} from 'react-native-svg';
import * as React from 'react';
import {TouchableOpacity, View,} from 'react-native';
const App = () => {
return (
<View>
<Svg x="0px" y="0px" viewBox="0 0 1920 1080" style="enable-background:new 0 0 1920 1080;">
<TouchableOpacity style={{
opacity: 1,
fill: "#FF0000",
stroke: "5",
zIndex: 100,
}} onPress={() => alert('PRESSED')}>
<Circle cx="960" cy="540" r="100" opacity="1" fill="#FF0000"/>
</TouchableOpacity>
</Svg>
</View>
);
};
export default App;

Related

Reanimated: animated view doesn't react to value changes

What you will see below is a minimised version of a bigger draggable solution I'm trying to implement—and it requires to have an animated view that would react to changes in animated style. This example isn't containing any gesture code since it's irrelevant here.
I have two rectangles: first is a Button that changes offset value randomly; second one is AnimatedRectangle that is supposed to be changing position each time the Button is pressed. That's it.
Expected result: AnimatedRectangle moving when Button is pressed.
Actual result: nothing moves.
FYI: share values, as well as animated style are changing, but the animated view doesn't seem to react to these changes.
Weird part is that when I was trying the same code in another project it worked in some files but not in others, although the styling and the way these different components were defined are the same. I have no idea why it happens.
Steps to reproduce:
Click on the Button
Observe the blue rectangle
Repo link: https://github.com/tumanov-alex/reanimated-not-working
import React from 'react';
import {View, TouchableOpacity, Animated, Text} from 'react-native';
import {useAnimatedStyle, useSharedValue} from 'react-native-reanimated';
const App = () => {
const offset = useSharedValue({x: 0, y: 0});
const animatedStyle = useAnimatedStyle(() => ({
transform: [{translateX: offset.value.x}, {translateY: offset.value.y}],
}));
const Button = () => (
<TouchableOpacity
onPress={() => {
offset.value = {
x: Math.random() * 100,
y: Math.random() * 100,
};
}}>
<View style={{width: 500, height: 500, backgroundColor: 'grey'}} />
</TouchableOpacity>
);
const AnimatedRectangle = () => (
<Animated.View
style={[
animatedStyle,
{
width: 100,
height: 100,
backgroundColor: 'blue',
},
]}>
<Text style={{color: 'white'}}>Why I'm not moving?</Text>
</Animated.View>
);
return (
<View style={{flex: 1}}>
<Button />
<AnimatedRectangle />
</View>
);
};
export default App;
Reanimated version: 2.11.0
React Native version: 0.70.3
Platforms: Android, iOS
Device: iOS simulator
I think that you have to import Animated directly from react-native-reanimated library and not from react-native:
Like this.
import Animated, {useAnimatedStyle, useSharedValue} from 'react-native-reanimated';

LinearGradient Will no Render on Expo App

I have a rather deeply nested component with a wrapper around the entire thing. I made sure its properly installed and imported from expo, so no errors and the component renders, just with no Linear Gradient above it. The code is as follows, NOTE that the colors are 'black' and 'white' for testing, but eventually 'white' will be changed with a transparent value
import React from 'react';
import { View, TouchableWithoutFeedback, Keyboard, ImageBackground, Image } from 'react-native';
import { LandingStyles } from '../../Styles/LandingPageStyles';
import LandingPageContainer from './LandingComponents/LandingPageContainer';
import Title from './LandingComponents/Title';
import backgroundImage from '../../assets/loginBackground.png'
import {LinearGradient} from 'expo-linear-gradient';
const LandingPage = ({ handleLoggedIn }) => {
return (
<LinearGradient colors={['black' ,'white']} start={{x: 0.5, y: 1 }} end={{x: 0.5, y: 0.7 }} style={{flex: 1}}>
<View style={LandingStyles.container}>
<ImageBackground style={LandingStyles.backdrop} source={backgroundImage} resizeMode="cover">
<TouchableWithoutFeedback onPress={() => Keyboard.dismiss()}>
<View style={{backgroundColor: 'rgba(52, 52, 52, 0.4) !important',}}>
<View style={LandingStyles.titleIcon}>
<Title />
</View>
<LandingPageContainer handleLoggedIn={handleLoggedIn} />
</View>
</TouchableWithoutFeedback>
</ImageBackground>
</View>
</LinearGradient>
);
};
export default LandingPage;
Whenever I run the app to test, there's just no difference. I can comment out the Gradient and nothing changes. I've tried having it wrap the component (as shown above) or just being placed in as a single component (<LinearGradient /> instead of <LinearGradient> </LinearGradient>) but nothing works. Any ideas?

fontWeight doesnt work when using fontFamily in react native

I added Rubik-regular which is customFont to my folder :
const customFonts = {
RubikRegular: require('./assets/fonts/Rubik-Regular.ttf'),
};
problem is that, if i use font like this :
<Text style={{fontFamily: 'RubikRegular'}}>6453 </Text>
every this is fine. However, when i want to give fontWeight my text:
<Text style={{fontFamily: 'RubikRegular', fontWeight: 'bold'}}>6453 </Text>
fontWeight doesnt work. if i remove fontFamily then it works. what is the problem here i didnt understand
You add Rubik-regular font, it means you only add regular (400) weight of Rubik font.
If you want to use fontWeight: 'bold', you have to add Rubik-bold font
you can do something like this
import React, { useState } from 'react';
import { View, StyleSheet } from 'react-native';
import SVG, { Path } from 'react-native-svg';
function ManBodySVG(props: Props) {
const [defaultOpacity, setDefault] = useState(10);
return (
<View style={styles.manBodyContainer}>
<View style={styles.frontBody}>
<SVG xmlns="http://www.w3.org/2000/SVG" viewBox="0 0 303.08 958.84" height={height} width={width}>
<Path
d="M172.47,123c-7.65,0-17.12-4.25-23.9-17.81C138.24,84.51,133,50.37,134.47,41c3.46-21.93,18.08-41,38-41,20.31,0,34.25,15.69,39.26,37.19,2.15,9.25-1.7,45-13.63,68.17C191.34,118.51,178.5,123,172.47,123Z"
fillOpacity={`${(defaultOpacity / 100).toString()}`}
fill="black"
/>
<Path
d="M162.83,153.88a2,2,0,0,0,1.91-2.54,97.07,97.07,0,0,0-8.5-20.07c-1.45-2.57-4.57-5.56-7.46-8.36A11.43,11.43,0,0,0,135.51,121c-11,5.77-31.38,16-45.4,20.78a43,43,0,0,0-8.79,4,2,2,0,0,0,.88,3.68c4.24.4,10.45.91,14.84,1,7.28.08,38.68-4.22,54.26,3.55C156.39,156.52,159.36,154,162.83,153.88Z"
fillOpacity={`${(dmgNeck / 100).toString()}`}
fill="black"
/>
...

Why is my image (and child components) not showing with ImageBackground on React Native?

I am having an issue with the image just showing up on the application. Through search results, I mostly see the solution being that that the height and width was required and missing, but I have already implemented with no results.
I have looked at the routes multiple times and don't see any issues on that end.
Lastly, I thought it could be something to do with the image. I have altered the size and it worked with the <Image /> tag. However, I need it to be a background image.
Current Code
import React from 'react'
import { View, Text, StyleSheet, ImageBackground } from 'react-native';
import { MaterialCommunityIcons } from '#expo/vector-icons'
export default function TitleScreen () {
return (
<View>
<ImageBackground
source={require('../assets/images/title_background.png')}
style={styles.backgroundImage}
imageStyle={{resizeMode: 'cover'}}
>
<Text>testing</Text>
{/* <MaterialCommunityIcons
name={'cards-outline'}
size={100}
/> */}
</ImageBackground>
</View>
)
}
const styles = StyleSheet.create({
backgroundImage: {
flex: 1,
width: '100%',
height: '100%',
}
});
Folder Layout
assets
-- images
--- title_background.png
components
-- TitleScreen.js
Answered! The answer was to add flex: 1 to the parent view container - for those running into the same problem.

How can I automatically scale an SVG element within a React Native View?

I am trying to put a react-native-svg element inside of a View such that it's rendered with a certain, fixed aspect ratio, but then scaled to be as large as possible, within the confines of the containing view.
The Svg element (from react-native-svg) seems to only accept absolute width and height attributes (I've tried using percentages, but nothing renders, and debugging confirms that percent values are NSNull by the time they get to the native view). I'm not sure how to achieve the desired effect. Here's what I've tried so far:
// I have a component defined like this:
export default class MySvgCircle extends React.Component {
render() {
return (
<View style={[this.props.style, {alignItems: 'center', justifyContent: 'center'}]} ref="containingView">
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center', aspectRatio: 1.0}}>
<Svg.Svg width="100" height="100">
<Svg.Circle cx="50" cy="50" r="40" stroke="blue" strokeWidth="1.0" fill="transparent" />
<Svg.Circle cx="50" cy="50" r="37" stroke="red" strokeWidth="6.0" fill="transparent" />
</Svg.Svg>
</View>
</View>
);
}
}
// And then consumed like this:
<MySvgCircle style={{height: 200, width: 200, backgroundColor: "powderblue"}}/>
And this is what I see when it renders.
I want the red and blue circles to be scaled up to fill the 200x200 area (staying circular if the containing view is rectangular and not square), without having foreknowledge of the size desired by the consumer/user of the component.
As mentioned, I tried using percentages, like this (the rest is the same):
<Svg.Svg width="100%" height="100%">
But then the SVG part doesn't draw at all. Like this:
There are no error messages, or other indications of why this doesn't work, in the console logs.
The methods for measuring UI elements after layout in RN appears to be asynchronous, which seems like a poor match to what I'm trying to do. Is there some sort of scaling or transform magic that I could use?
The desired output would look like this (obtained by hardcoding values):
And when the containing view isn't a perfect square I'd like it to work like this:
Here is a component that behaves like your images:
import React from 'react';
import { View } from 'react-native';
import Svg, { Circle } from 'react-native-svg';
const WrappedSvg = () =>
(
<View style={{ aspectRatio: 1, backgroundColor: 'blue' }}>
<Svg height="100%" width="100%" viewBox="0 0 100 100">
<Circle r="50" cx="50" cy="50" fill="red" />
</Svg>
</View>
);
In context:
const WrappedSvgTest = () => (
<View>
<View style={{
width: '100%',
height: 140,
alignItems: 'center',
backgroundColor: '#eeeeee'
}}
>
<WrappedSvg />
</View>
{/* spacer */}
<View style={{ height: 100 }} />
<View style={{
width: 120,
height: 280,
justifyContent: 'space-around',
backgroundColor: '#eeeeee'
}}
>
<WrappedSvg />
</View>
</View>
);
The trick is to wrap the SVG element in a view that preserves its aspect ratio, then set the SVG sizing to 100% width and height.
I believe there is some complex interaction between the SVG element size and the viewbox size that makes the SVG render smaller than you would expect, or in some cases not render at all. You can avoid this by keeping your <View> tags at a fixed aspect ratio and setting the <Svg> tags to 100% width and height, so the viewbox aspect ratio always matches the element ratio.
Be sure to set aspectRatio to viewbox.width / viewbox.height.
the trick in
preserveAspectRatio="xMinYMin slice"
you should do that
<Svg
height="100%"
preserveAspectRatio="xMinYMin slice"
width="100%"
viewBox="0 0 100 100"
>
You have to play with the width and height together with the viewBox. Usually the viewBox you have to place the original dimensions of your desired shape. And by defining the width/height based on your needs your shape will be down/up scaled properly.
Please have a look to this tutorial where this concepts have been explained pretty clear.
https://www.sarasoueidan.com/blog/svg-coordinate-systems/
For my SVG, I was using those provided at https://material.io/resources/icons
What fixed it for me, was to make sure you don't mess with the viewBox or given values in the Paths (like I did) but only change the height and width to fill and then use the containers like the other answers:
<View style={{
height: 100, display: 'flex',
}}>
<TouchableOpacity style={{
display: 'flex', justifyContent: 'center',
alignItems: 'center', aspectRatio: 1,
}}>
<Svg fill="white" height="100%"
width="100%" viewBox="0 0 24 24">
<Path d="M0 0h24v24H0z" fill="none"/>
<Path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>
</Svg>
</TouchableOpacity>
</View>
i'm using react-native-svg-transformer without using react-native-svg which i found very heavy in term of size,
so i can resize and change the stroke color also the fill color, but just instead of passing a fill prop, just pass color as seen below, it works perfectly
import React from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
} from 'react-native';
import { StatusBar } from 'expo-status-bar';
import Logo from "../../assets/profile.svg";
function FirstScreen(props) {
return (
<View style={styles.container}>
<TouchableOpacity
onPress={() => { props.navigation.navigate('SecondScreen'); }}
>
<Text>Welcome</Text>
<View style={{ aspectRatio: 1,justifyContent:"center",alignItems:"center", backgroundColor: 'blue',width:200,height:200 }}>
<Logo color="white" stroke="black" height={50} width={50} />
</View>
</TouchableOpacity>
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
export default FirstScreen;
the svg code
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><title>profile</title><g fill="currentColor" class="nc-icon-wrapper"><path d="M38,37H26A19.021,19.021,0,0,0,7,56a1,1,0,0,0,.594.914C7.97,57.081,16.961,61,32,61s24.03-3.919,24.406-4.086A1,1,0,0,0,57,56,19.021,19.021,0,0,0,38,37Z"></path><path data-color="color-2" d="M32,32c8.013,0,14-8.412,14-15.933a14,14,0,1,0-28,0C18,23.588,23.987,32,32,32Z"></path></g></svg>
dependencies
"dependencies": {
"#expo/webpack-config": "~0.16.2",
"#react-navigation/native": "^6.0.10",
"#react-navigation/native-stack": "^6.6.2",
"expo": "~45.0.0",
"expo-font": "^10.1.0",
"expo-status-bar": "~1.3.0",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-native": "0.68.2",
"react-native-svg-transformer": "^1.0.0",
},
metro.config.js file to add in the root
const { getDefaultConfig } = require('expo/metro-config');
module.exports = (() => {
const config = getDefaultConfig(__dirname);
const { transformer, resolver } = config;
config.transformer = {
...transformer,
babelTransformerPath: require.resolve('react-native-svg-transformer'),
};
config.resolver = {
...resolver,
assetExts: resolver.assetExts.filter((ext) => ext !== 'svg'),
sourceExts: [...resolver.sourceExts, 'svg'],
};
return config;
})();
I put this whole thing into an example Snack, maybe it helps.
SNACK:
https://snack.expo.dev/#changnoi69/fbf937
When you change the marginLeft and marginRight of that view that is wrapped around the SVG-Component the SVG resizes according to it.
<View style={{marginLeft:"20%", marginRight:"20%", backgroundColor: "pink"}}>
<NoInternetConnectionSVG />
</View>
Original Stackoverflow post is here:
https://stackoverflow.com/a/73511233/12647753
You will need this variables
const originalWidth = 744;
const originalHeight = 539.286;
const aspectRatio = originalWidth / originalHeight;
Wrap your svg in a view with this properties:
<View style={{ width: '100%', aspectRatio }}></View>
or
<View style={{ width: Dimensions.get('window').width, aspectRatio }}>
</View>
Use the svg inside, with this properties:
<Svg
width='100%'
height='100%'
viewBox={`0 0 ${originalWidth} ${originalHeight}`}
>
And you should be ok!
In my case, I had to scale a SVG icon based on the device size and it was using <G> and <Path> for drawing the icon. After hours of trial and error method, I found a solution - give a dynamic scale value (based on the device size) to the inner component of Svg component. Here, the inner component is <G>
<Svg width={RfH(24)} height={RfH(24)} style={{backgroundColor: 'salmon'}}>
<G
scale={RfH(1)} // Scaling added to the inner component
fill="none"
fillRule="evenodd">
<G
stroke={props?.isFocused ? '#302F4C' : '#8B8B88'}
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={1.5}>
<Path
d="M9.393 2.792 3.63 7.022c-.9.7-1.63 2.19-1.63 3.32v7.41c0 2.32 1.89 4.22 4.21 4.22h11.58c2.32 0 4.21-1.9 4.21-4.21v-7.28c0-1.21-.81-2.76-1.8-3.45l-5.807-4.36c-1.4-.98-3.65-.93-5 .12Z"
fill={props?.isFocused ? '#7BBDFF' : 'none'}
fillRule="nonzero"
/>
<Path fill="#FFF" d="M12 17.993v-2.924" />
</G>
</G>
- iPad home icon with scaling
- iPad home icon without scaling
- iPhone home icon with scaling
- iPhone home icon without scaling
Rfh just converts an input value to the current device equivalent.
import {Dimensions} from 'react-native';
const STANDARD_SCREEN_DIMENSIONS = {height: 812, width: 375};
const RfH = (value) => {
const dim = Dimensions.get('window');
return dim.height * (value / STANDARD_SCREEN_DIMENSIONS.height);
};