React Native / Expo custom font: German umlauts out of bounds - react-native

I've added a custom font (FS Me as an OTF file) to a React Native / Expo app using useFonts from expo-font. Unfortunately, I'm running into an issue where German umlauts (ä, ö, ü) are not visible due to the viewbox of the Text element being too small. For example, take this text: Über uns.
In the app, this simply renders as Uber uns because the two dots above the "U" are out of the Text boundary. When I increase the line height to 1.4x the font size, the umlauts show. However, this a) adds some sort of padding on the bottom of the text and b) it's obviously not a very good solution to increase the line height everywhere. Increasing it on a case-to-case basis isn't a viable solution either.
I'm using a custom Text component which sets a default font like this:
import React from 'react';
import {
TextProps,
Text as RNText, // eslint-disable-line no-restricted-imports
StyleSheet,
StyleProp,
} from 'react-native';
// Animated.createAnimatedComponent requires a class component
// eslint-disable-next-line react/prefer-stateless-function
class Text extends React.Component<TextProps> {
render() {
const { children, style } = this.props;
let styles = style;
let objStyle: StyleProp<any> = {};
if (Array.isArray(style)) objStyle = StyleSheet.flatten(style);
else if (style) objStyle = { ...(style as object) };
if (!objStyle.fontFamily || objStyle.fontFamily === 'FS_Me') {
objStyle.fontFamily = 'FS_Me_400_Regular';
if (objStyle.fontWeight === 'bold' || objStyle.fontWeight === '700') {
objStyle.fontFamily = 'FS_Me_700_Bold';
objStyle.fontWeight = undefined;
}
if (objStyle.fontWeight === 'light' || objStyle.fontWeight === '300') {
objStyle.fontFamily = 'FS_Me_300_Light';
if (objStyle.fontStyle === 'italic') {
objStyle.fontFamily = 'FS_Me_300_Light_Italic';
}
objStyle.fontWeight = undefined;
}
objStyle.backgroundColor = 'red';
styles = objStyle;
}
return (
<RNText allowFontScaling={false} {...this.props} style={styles}>
{children}
</RNText>
);
}
}
export default Text;
However, this also happens when I'm using the core RN Text component and only change the font family.
Does anyone have an idea why this happens?

Related

commas in Decimals using <Text>

I have a value which is in a Holder which is supposed to show currency and it shows the currency directly from the REST API, fine and good , but rather than show the value like this
123,456,789.00
It shows like this 123456789.00 I have been trying to format it to no avail. Is there a way to do this in React Native? The holder is looking like this
{this.state.balance}
Searched the internet and could not come up with something good at this point.
Use toLocaleString() to add those commas.
Sample Example:
let num = 123456789.00
console.log(num.toLocaleString())
Full Working Example: Expo Snack
import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import Constants from 'expo-constants';
let num = 123456789.1;
export default function App() {
const toCommaConvertion = (num) => {
num += 0.001;
let str = [...num.toLocaleString()];
str.pop();
return str.join('');
};
return (
<View style={styles.container}>
<Text style={styles.paragraph}>{toCommaConvertion(num)}</Text>
</View>
);
}
You should convert the number to comma seperated formate
class BalanceExample extends Component {
constructor(props) {
super(props);
this.state = {
balance: 123456789,
};
}
numberWithCommas = (x) => x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
render() {
return (
<Text>{this.numberWithCommas(this.state.balance)}</Text>
);
}
}
export default BalanceExample;

Trying to load obj & mtl file with Three.js in React Native

Main objective : Load animated models exported from Maya into React Native app
Exported files : obj, mtl & png file
I have setup https://github.com/react-community/react-native-webgl in my React Native project and it is working properly.
Now, when I am trying to load the MTL file using the MTLLoader, I am getting following error:
Can't find variable: document
Apparently, the MTLLoader is calling TextureLoader which internally calls some load function which has 'document' reference. So what could be the solution to this ?
Here are the two files that I am using:
three.js
const THREE = require("three");
global.THREE = THREE;
if (!window.addEventListener)
window.addEventListener = () => { };
// require("three/examples/js/renderers/Projector");
require("three/examples/js/loaders/MTLLoader");
require("three/examples/js/loaders/OBJLoader");
export default THREE;
ThreeView.js
import React, { Component } from "react";
import { StyleSheet, View } from "react-native";
import { WebGLView } from "react-native-webgl";
import THREE from "./three";
import { image } from "src/res/image";
export default class ThreeView extends Component {
requestId: *;
componentWillUnmount() {
cancelAnimationFrame(this.requestId);
}
onContextCreate = (gl: WebGLRenderingContext) => {
const rngl = gl.getExtension("RN");
const { drawingBufferWidth: width, drawingBufferHeight: height } = gl;
const renderer = new THREE.WebGLRenderer({
canvas: {
width,
height,
style: {},
addEventListener: () => { },
removeEventListener: () => { },
clientHeight: height
},
context: gl
});
renderer.setSize(width, height);
renderer.setClearColor(0xffffff, 1);
let camera, scene;
let cube;
function init() {
camera = new THREE.PerspectiveCamera(75, width / height, 1, 1100);
camera.position.y = 150;
camera.position.z = 500;
scene = new THREE.Scene();
var mtlLoader = new THREE.MTLLoader();
mtlLoader.load('female-croupier-2013-03-26.mtl', function (materials) {
materials.preload();
var objLoader = new THREE.OBJLoader();
objLoader.setMaterials(materials);
objLoader.load('female-croupier-2013-03-26.obj', function (object) {
scene.add(object);
}, onLoading, onErrorLoading);
}, onLoading, onErrorLoading);
}
const onLoading = (xhr) => {
console.log((xhr.loaded / xhr.total * 100) + '% loaded');
};
const onErrorLoading = (error) => {
console.log('An error happened', error);
};
const animate = () => {
this.requestId = requestAnimationFrame(animate);
renderer.render(scene, camera);
// cube.rotation.y += 0.05;
gl.flush();
rngl.endFrame();
};
init();
animate();
};
render() {
return (
<View style={styles.container}>
<WebGLView
style={styles.webglView}
onContextCreate={this.onContextCreate}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center"
},
webglView: {
width: 300,
height: 300
}
});
This error is as others have said caused by threejs trying to use features from a browser which react-native does not have.
I've gotten so far as to be able to load the textures (which is the stage you're getting the error from) by monkey patching the texture loader to use the loader in react-native-webgl. Add this in your init function (right near the top preferably).
//make sure you have defined renderer and rngl
/*
const renderer = new THREE.WebGLRenderer(...)
const rngl = gl.getExtension("RN");
*/
const loadTexture = async function(url, onLoad, onProgress, onError) {
let textureObject = new THREE.Texture();
console.log("loading",url,'with fancy texture loader');
let properties = renderer.properties.get(textureObject);
var texture = await rngl.loadTexture({yflip: false, image: url});
/*
rngl.loadTexture({ image: url })
.then(({ texture }) => {
*/
console.log("Texture [" + url + "] Loaded!")
texture.needsUpdate = true;
properties.__webglTexture = texture;
properties.__webglInit = true;
console.log(texture);
if (onLoad !== undefined) {
//console.warn('loaded tex', texture);
onLoad(textureObject);
}
//});
return textureObject;
}
THREE.TextureLoader.prototype.load = loadTexture;
This solves the problem of loading textures and I can see them load in Charles but they still don't render on a model so I'm stuck past that point. Technically a correct answer but you'll be stuck as soon as you've implemented it. I'm hoping you can comment back and tell me you've gotten further.
I had a similar setup and encountered same issue. My option was to switch to JSONLoader which doesn’t need document to render in react-native. So, I just loaded my model in Blender with a three-js addon, then exported it as json. Just check out this process of adding a three-js adon to Blender
https://www.youtube.com/watch?v=mqjwgTAGQRY
All the best
this might get you closer:
The GLTF format supports embedding texture images (as base64). If your asset pipeline allows it, you could convert to GLTF and then load into three/react-native.
I had to provide some "window" polyfills for "decodeUriComponent" and "atob" because GLTFLoader uses FileLoader to parse the base64:
I've successfully loaded embedded buffers, but you'll need more polyfills to load textures. TextureLoader uses ImageLoader, which uses document.createElementNS
You are using the MTLLoader which uses TextureLoader, and the TextureLoader uses the ImageLoader.
The imageloader uses the document.createElementNS() function.
what i did to solve this was to directly call the THREEjs TextureLoader:
let texture = new THREE.Texture(
url //URL = a base 64 JPEG string in this case
);
(for the use of Texture check the Texture documentation)
Then i used the Image class from React native (instead of the THREEjs Image, which requires the DOM to be constructed) to give that to the Texture as a property:
import { Image } from 'react-native';
var img = new Image(128, 128);
img.src = url;
texture.normal = img;
And then finally map the texture over the target material:
const mat = new THREE.MeshPhongMaterial();
mat.map = texture;
In the react native documentation it will explain how the react native Image element can be used, it supports base64 encoded JPEG.
Maybe there's a way for you to single out the part where it calls for the TextureLoader and replace that part with this answer. Let me know how it works out.
side note, i havent tried to display this yet in my webGLView, but in the logs it looked like normal threejs objects, it's worth the try
Use TextureLoader from expo-three
import { TextureLoader } from "expo-three";
export function loadTexture(resource) {
if (textureCache[resource]) {
return textureCache[resource].clone();
}
const texture = new TextureLoader().load(resource);
texture.magFilter = NearestFilter;
texture.minFilter = NearestFilter;
textureCache[resource] = texture;
return texture;
}
Source: https://github.com/EvanBacon/Expo-Crossy-Road/blob/master/src/Node/Generic.js

React Native - How to render text that has breaklines "\n"

I'm trying to find a way to render texts that come from the database with their breaklines "\n".
How can a change the "\n" to the actual breaklines?
You could use this approach.
import React, { Component } from 'react';
import { AppRegistry, ScrollView, Text } from 'react-native';
export default class Main extends Component {
renderText() {
const textFromDB = 'This text\nfrom\ndatabase.'.split('\n');
const rendered = textFromDB.map(x => <Text>{x}</Text>);
return rendered;
}
render() {
return (
<ScrollView>
{this.renderText()}
</ScrollView>
);
}
}
// skip these lines if using Create React Native App
AppRegistry.registerComponent(
'AwesomeProject',
() => Main);
Using Wisnu answer I kinda found out how to do it, because his approach didn't work for me.
function renderTextWithBreakLines(text) {
return text.split(`\n`).map((txt, i) => <Text key={i}>{ txt }{ '\n' }</Text>)
}
And a shorter method would be:
const renderTextWithBreakLines = text => text.split(`\n`).map((txt, i) => <Text key={i}>{ txt }{ '\n' }</Text>);
You can wrap your text inside {} like so
<Text>{`Large Room~\nFully Furnished`}</Text>
Or declare your text as a variable and render like so
const someText = "Large Room\nFully Furnished"
<Text>{someText}</Text>

Can I use multiple or nested elements in the Label of a Picker Item in React Native?

I'm using React Native with NativeBase and would like to make the labels of my Picker more complicated than just one plain string of text.
But is it even possible to pass elements as the label, say multiple child elements wrapped in a single top-level element?
Or do Pickers only support plain text as labels?
As requested by bennygenel, here's a version of what I've tried:
export default class ThingPicker extends React.Component {
render() {
const {
initialThing,
things,
onThingChanged,
} = this.props;
const orderedThings = things.sort();
return (
<Picker
selectedValue={initialThing}
onValueChange={onThingChanged}>
{buildThingItems(orderedThings)}
</Picker>
);
}
}
function buildThingItems(orderedThings) {
let items = orderedThings.map(th => {
const it = th === "BMD" ? (<Text key={th} label={"foo"} value={"bar"}}>Hello</Text>)
: (<Picker.Item key={th} label={th} value={th} />);
return it;
});
}
Yes! It is possible, it just might not look very "right" for React/JSX code. Just create the elements you need and assign them to the label field:
function buildThingItems(orderedThings) {
let items = orderedThings.map(th => {
const it = (<Picker.Item
key={th}
label={currency === "BMD" ? (<Text>Hello</Text>) : th}
value={th} />);
return it;
});
}

How detect tablet landscape on React Native

I want to border margin of of screen S on phone and tablet to be different. There are variants for tablet landscape and portrait mode.
How to create different margin dimension for the variants on phone, tablet portrait, tablet landscape ?
For those curious how to do on Android , we just create some resource files at the right folder :
values for default
values-sw600dp for tablet default
values-sw600dp-land for tablet landscape
The other answers have already addressed the screen detection task. However, there is still the issue of detecting if the code is running on a Tablet device. You can detect that using the react-native-device-info package, in particular its isTablet method. So, as an example, in your component:
constructor(){
super();
this.state = {orientation: 'UNKNOWN'}
this._onOrientationChanged = this._onOrientationChanged.bind(this);
}
_onOrientationChanged(orientation){
this._setState({orientation})
}
componentDidMount(){
Orientation.addOrientationListener(this._onOrientationChanged);
}
componentWillUnmount(){
Orientation.removeOrientationListener(this._orientationDidChange);
}
render(){
let layoutStyles;
if(DeviceInfo.isTablet()){
layoutStyles = this.state.orientation == 'LANDSCAPE' ? landscapeTabletStyle : portraitTabletLandscape; // Basic example, this might get more complex if you account for UNKNOWN or PORTRAITUPSIDEDOWN
}else{
layoutStyles = this.state.orientation == 'LANDSCAPE' ? landscapeStyle : portraitLandscape;
}
render(){
<View style={[styles.container, layoutStyles]} // And so on...
}
}
Note that the state holds the UNKNOWN value on the beginning. Have a look at the getInitialOrientation() of the package function. I am intentionally leaving that bit out because it simply reads a property that is set when the JS code loads, and I am not sure if that satisfies your usecase (i.e. this is not your first screen). What I usually like to do is store the rotation value in a redux store (where I initialize the orientation value to that of getInitialOrientation() and then subscribe only once to the orientation listener).
I think this library will be helpful for you: https://github.com/yamill/react-native-orientation
You can do something like that with it:
Orientation.getOrientation((err,orientation)=> {
console.log("Current Device Orientation: ", orientation);
if(orientation === 'LANDSCAPE') {
//do stuff
} else {
//do other stuff
}
});
// Extract from the root element in our app's index.js
class App extends Component {
_onLayout = event => this.props.appLayout(event.nativeEvent.layout);
render() {
return (
<View onLayout={this._onLayout}>
{/* Subviews... */}
</View>
);
}
}
export const SET_ORIENTATION = 'deviceStatus/SET_ORIENTATION';
export function appLayout(event: {width:number, height:number}):StoreAction {
const { width, height } = event;
const orientation = (width > height) ? 'LANDSCAPE' : 'PORTRAIT';
return { type: SET_ORIENTATION, payload: orientation };
}
Code Copied from Here