I'm currently writing an application on react-native version 0.7.1. I'm attempting to write a chat component.
With chat, you usually have the text input at the bottom and messages get pushed from the bottom to the top of the screen. Also, when the chat screen initializes, it initializes at the bottom of the screen and allows the user to scroll up. I would like to implement the same behavior in react-native. The current behavior of the ScrollView pushes items from the top to the bottom. I've attempted a few different solutions to this:
The first solution involves attempting to measure the ScrollView component. The concept is, if I know the height of the view, I can this.refs.messages.scrollTo(contentHeight). Some people mentioned the following function: this.refs.messages.measure(callbackWithTheseParams((a, b, width, height, px,py )). But the measure function turns out to be undefined.
The second method involved the package https://www.npmjs.com/package/react-native-invertible-scroll-view. This package actually sorted my data correctly too (reverse). You can think about it like flipping the pixels on the screen so that (0,0) is the bottom. This allows me to call scrollTo(0,0). However, seems like this functionality is only available in the 0.8.0-rc. Which I'm not sure is stable (or as stable rather).
EDIT - Adding Example
My constructor:
componentDidMount() {
this.measureComponent()
}
// measureComponent
measureComponent() {
console.log('this gets called');
this.refs.messages.measure((ox, oy, width, height) => {
console.log(height); // does not get called.
});
}
My render function is:
return (
<ScrollView
scrollEventThrottle={200}
ref="messages"
style={styles.container}>
<View style={styles.messages}>
{messages.map(this.renderMessage)}
</View>
<TextInput
style={styles.newMessage}
value={this.state.message}
onSubmitEditing={this.sendMessage}
onChangeText={(text) => this.setState({message: text})} />
</ScrollView>
);
I'm not sure if there is an easier way to get the height of a ScrollView, but I did notice this in the React Native source code for ListView (/Libraries/CustomComponents/ListView/ListView.js):
_measureAndUpdateScrollProps: function() {
var scrollComponent = this.getScrollResponder();
...
RCTUIManager.measureLayout(
scrollComponent.getInnerViewNode(),
React.findNodeHandle(scrollComponent),
logError,
this._setScrollContentLength
);
...
},
_setScrollContentLength: function(left, top, width, height) {
this.scrollProperties.contentLength = !this.props.horizontal ?
height : width;
},
Which gets either the height or the width depending on if the horizontal property is set. RCTUIManager is part of the native modules module (require('NativeModules').UIManager).
this will probably turn out undefined if you did not bind the function in the constructor.
this.refs.messages.measure(callbackWithTheseParams((a, b, width, height, px,py )).
try something like:
_measureFunc() {
this.refs.MESSAGES.measure((ox, oy, width, height) => {
setState ({ h: height });
});
}
For example this would be your messages component:
<View ref='MESSAGES' ></View>
then add bind (placed in constructor):
this._measureFunc = this._measureFunc.bind(this);
Related
In some very specific cases I need to set the height of a View to the full height of the device useful area (without using flex).
I was using a hardcoded "notch height" to calculate this useful height but I just discovered that the notch can have different heights depending on the device. (3 points of difference between iPhone XS and iPhone XS Max).
Is there a way to know the useful height of a device with notch and safe area?
use 'react-native-safe-area-context'
https://www.npmjs.com/package/react-native-safe-area-context#usesafeareainsets
import { useSafeAreaInsets } from 'react-native-safe-area-context';
function Screen() {
const insets = useSafeAreaInsets();
console.log(insets);
//{"bottom": 34, "left": 0, "right": 0, "top": 48}
return <View />;
}
As #mohammed-ashfaq said, react-native-safe-area solves the problem. However, it returns the insets with a promise and I needed those values statically.
Given that, I created react-native-static-safe-area-insets that enables access to the insets values as constants.
You can use the react-native-safe-area. it provides function to Get safe area inset top, bottom, left, right.
import SafeArea, { type SafeAreaInsets } from 'react-native-safe-area'
//Retrieve safe area insets for root view
SafeArea.getSafeAreaInsetsForRootView()
.then((result) => {
console.log(result)
// { safeAreaInsets: { top: 44, left: 0, bottom: 34, right: 0 } }
})
You can get the screen, which users phone, width and height from Dimensions component.
import { Dimensions } from 'react-native'
const { width, height } = Dimensions.get('window'); // if you use the width you can get the screen width by pixels. And also height is the height pixels of the phone.
const screenWidthSomePart = width * 0,6 // Some times you can get the percentage of the screen so you can use this. screen %60
If you wanna see the safe are for the Iphone X. You can use the SafeAreaView Componenet
import { SafeAreaView } from 'react-native'
return(
<SafeAreaView>
..... // your screen componenet
</SafeAreaView>
);
For anyone still looking for a solution to this without installing any packages, this is what i did.
A SafeAreaView has to be used as the wrapper of the screen with a simple View as the only child and then the rest of your desired component tree inside said View.
SafeAreaView uses padding at the top and bottom to take care of insets in devices with notch.
Then at the View inside the SafeAreaView, onLayout callback can be used to get the actual "drawable" height of the View without the insets.
The height from onLayout can then be saved to a state to do any desired logic.
<SafeAreaView>
<View onLayout={({ nativeEvent: layout }) => console.log(layout.height)}>
{children}
</View>
</SafeAreaView>
Use Dimension module from 'react-native' like there :
import { Dimensions, Platform, StatusBar } from 'react-native'
const windowHeight = Dimensions.get('window').height
const screenHeight = Dimensions.get('screen').height
Problem:
I have a ScrollView with 2 subviews and I want the first of them (lets call it ViewA) to have {flex: 1} so the other one (ViewB) will stick to the bottom of the screen - but only if their total height is smaller that the screen. Of course that if they are higher than the screen I want it to scroll as usual.
Case 1 (GOOD): ViewA with long text, ViewB scrolls with it. https://rnplay.org/apps/slCivA
Case 2 (BAD): ViewA with short text, ViewB doesn't stick to the bottom. https://rnplay.org/apps/OmQakQ
Tried Solution:
So I set the ScrollView's style AND contentContainerStyle to be flex: 1. I
Also set ViewA's style to flex:1. But when I do it, the ScrollView's contentContainer view is fixed to the screen height, thus not able to scroll if needed and even worse - ViewB overlaps ViewA.
Case 3 (BAD): ViewB sticks to the bottom, but the whole thing doesn't scroll. https://rnplay.org/apps/wZgtWA
If it's a bug - how to fix/workaround it?
If it's the expected behaviour - how can I achieve what I've described?
Thanks.
Try feeding {flexGrow: 1} to the contentContainerStyle prop instead
Ok, so i wasn't able to get it work by styling alone, but here's how I did it:
Measure the height of the footer after it rendered (using onLayout, and measure)
on the measure callback, I add the height (if needed) to a spacer view between ViewA and ViewB in order to place ViewB on the bottom.
In order to avoid showing 'jumps' to the user, I hide (with opacity) ViewB until its position is fixed.
my CJSX code (stripped and simplified version):
Dimensions = require('Dimensions')
windowSize = Dimensions.get('window')
MyClass = React.createClass
mixins: [TimerMixin]
render: ->
<ScrollView style={styles.container} automaticallyAdjustContentInsets={false}>
<View style={styles.mainContainer}>
... THIS IS VIEW A ...
</View>
{
if !#state.footerWasFixed
<View>
<ActivityIndicatorIOS/>
</View>
}
<View style={[styles.spacer, {height: #state.spacerSize || 0}]}></View>
<View onLayout={#measureFooterPosition} style={[styles.extras, {opacity: if #state.footerWasFixed then 1 else 0 }]} ref='extras'>
... THIS IS VIEW B ...
</View>
</ScrollView>
measureFooterPosition: ->
#refs.extras.measure(#fixFooterPosition)
fixFooterPosition: (ox, oy, width, height) ->
footerPosition = Math.round(oy + height)
diff = windowSize.height - footerPosition
if diff > 0
#setState(spacerSize: diff)
if !#state.footerWasFixed
#setTimeout (=>
#setState(footerWasFixed: true)
), 30
styles = StyleSheet.create(
container:
backgroundColor: 'grey'
flex: 1
spacer:
backgroundColor: 'white'
mainContainer:
flex: 1
backgroundColor: 'white'
extras:
backgroundColor: 'white')
It's very simplified code, (my view has requirements much more specific then this..) but I hope it helps anyone.
Ok, I've made a sample here, and I think I've resolved the problem. The main thing I did was to make a main container view with two inner views, then set the inner views to flex at 80% and 20% (you can adjust to make it look the way you want).
https://rnplay.org/apps/O4UxFA
Originally I was going to create a view component that was the size of half the screen and wrap it in a TouchableHighlight, but that seems messy.
Have a look at the Gesture Responder system, which can let you set a view to react to a touch:
http://facebook.github.io/react-native/docs/gesture-responder-system.html#content
Specifically if you pass your view a onStartShouldSetResponder prop which is a function that returns true. You can then also pass a onResponderGrant function as a prop which will receive an event object with the details you need.
Fixed it.
I added a wrapper <View {...this.panResponder.panHandlers}> to the element and filled in the onPanResponderGrant function:
onPanResponderGrant: ({ nativeEvent: { touches } }, { x0, y0, moveX }) =>{
// if on right side of screen
if (x0 > (Dimensions.get('window').width / 2)){
_this.nextPhoto();
}
}
I think this is rather easy achieved, but I couldn't find out how to- and couldn't find much documentary about it.
I hate those 'scroll to top' buttons that appear after you already scrolled just 300px. Like I'm that lazy to scroll to top on myself. Therefor I would like to have a scroll to top button that only appears when you reached the bottom of the page (minus 100vh (100% viewport height).
Let's take in account the button is called .scrollTopButton and it's CSS is opacity: 0 and it's position: fixed on default.
How would I make the button appear when you reached the bottom of the page, minus 100vh and scroll along?
I was thinking of comparing the body height minus 100vh with (window).scrollTop().
var vH = $(window).height(),
bodyMinus100vh = ($('body').height() - vH);
if (bodyMinus100VH < $(window).scrollTop) {
$('.scrollTopButton').toggle();
};
Fixed it myself. Quite easy, honestly.
$(window).scroll(function () {
var vH = $(window).height(),
bodyHeight = ($(document).height() - (vH * 2)),
// When you open a page, you already see the website as big
// as your own screen (viewport). Therefor you need to reduce
// the page by two times the viewport
scrolledPX = $(window).scrollTop();
if (scrolledPX > bodyHeight) {
$('.scrollTopButton').css('opacity', '1');
} else {
$('.scrollTopButton').css('opacity', '0')
};
});
I'm having trouble getting my highchart to reduce in size when the window is resized down.
I have created an example of my code in JSFiddle http://jsfiddle.net/britboy/UvFaQ/
<table border='1' width='100%'><tr><td width='100%'>
<div id="container" width='100%'></div>
</td></tr></table>
Its a simple Highchart chart displayed in a table - if I make the window bigger the chart expands, however if I make the window smaller the chart doesn't want to reduce instead the table gets scroll bars.
I've tried setting up a resize event and then adjusting the chart size with chart.setSize( ) but the problem is the div containing the chart never reduces any further in size, so the setSize() does not get triggered. Since the chart resizes automatically when the chart container gets bigger I would have though the same should work when the chart gets smaller. I think the problem is that the chart's size is preventing its container from shrinking.
How do I code a chart in a table that will reduce in size when the table is reduced?
Thanks
There are n number of ways to accomplish it.
One would be as pointed out by #Azeem. in the comment.
Other way, I actually, bind it with window. Whenever the window re-sizes, bind event would trigger a function to re-size the div element used to render the chart.
$(window).bind("resize", resizeChart);
then,
function resizeChart() {
var width = $(document).width() - 55;
var height = $(document).height() - 60;
$("#container").css("width", width);
$("#container").css("height", height);
}
Check out the sample fiddle here.
Try this method :
var chart = $('#graph1').highcharts();
var DetailsWidth = $('#graph1');
http://jsfiddle.net/cjohn/5ghzk4t8/4/
[Adding a solution for react-highcharts here, because this pops up as first SO solution when searching for "react-highcharts shrink" on Google]
For react-highcharts you can simply call the React Component forceUpdate() method in the window resize event listener. It will trigger Highcharts to redraw to the new, smaller area. See also Rerender view on browser resize with React
NOTE: my code was tested using flex-auto (see CSS Flexible Box Layout) in the parent and the Highcharts DOM elements. It would be interesting to know if this works for other layouts too.
import React from 'react';
import Highcharts from 'react-highcharts';
...
export default class Chart extends React.Component {
constructor(props) {
...
// install callbacks
this.resize = () => {
// throttle resize events as redraw is expensive
if (this.timeout)
return;
this.timeout = setTimeout(
() => {
// force Highcharts to always adhere
// to changes in the view area
this.forceUpdate();
this.timeout = undefined;
},
50); // milliseconds
};
...
}
...
componentDidMount() {
...
// listen to window resize events
window.addEventListener('resize', this.resize);
...
}
...
componentWillUnmount() {
...
// remove listener when component goes away
window.removeEventListener('resize', this.resize);
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = undefined;
}
...
}
...
render() {
...
return (
<Highcharts
config={...}
domProps={{
className: 'd-flex flex-auto',
...
}}
...
/>
);
}
...
}