I have a view in react native witch contains some child views. I need to perform some functions right after each views rendered.
my views are in this order :
Base Parent view ---> parent view ---> child view
so I need to calculate some layout dimention in parent view but exactly after child view is created.
in Android I dont have any problem
bu in iOS these views created in no order so I cant do my calculation.
How can I be sure to run a function exactly after all childs are created?
this is my base parent view :
<View onLayout={this.getGridExactPos} style={{width: '100%' , aspectRatio: 1, backgroundColor: 'red'}} {...this._panResponder.panHandlers}>
{this.constructViews()}
</View>
this is my childs in above container:
constructViews = () => {
let strings = 'a,b,c,d|s,d,f,g|h,b,a,u|m,l,k,o';
let answers = '01,12,23';
let arrayST = strings.split("|");
for(i=0;i<arrayST.length;i++){
arrayST[i] = arrayST[i].split(",");
}
let rows = [];
let currentIndex = 0
let currentRowIndex = 0
for (let i = 1; i <=arrayST.length; i++) {
let row = [];
for (let j = 1; j <=arrayST.length; j++) {
const boxIndex = currentIndex++
let stateKey = `${i}${j}`;
row.push(
<View onLayout={ (e) => this.getExactPos(e, stateKey, boxIndex)} style={[styles.box, {backgroundColor : this.state.boxes[boxIndex].bgColor}]} key={stateKey}><Text>{this.state.boxes[boxIndex].title}</Text></View>
);
}
const rowIndex = currentRowIndex++
rows.push(
<View onLayout={ (e) => this.fixPos(e, rowIndex)} style={styles.row} key={i}>{row}</View>
);
}
return rows;
}
so I need to run 'this.fixPos' exactly after 'this.getExactPos' and finally after this loop finished I need to run 'this.getGridExactPos'.
in android I dont have problem but in iOS there is no order so makes me mistake.
thanks for your advices.
Related
I am trying to pause and play video using scrollView but i am unable to do that I wrote a code but it have some condition issue, can anybody please tell me what is wrong in my code, I want to play the video when it is visible and pause all other videos in the list than when we scroll the video show on screen it start playing automatically and the previous video pause or stops automatically.
handleVideoLayout = (e:any) => {
const {height} = Dimensions.get("window");
console.log(e.nativeEvent.layout.y,e.nativeEvent.layout.height);
this.position.start = -(e.nativeEvent.layout.y - height + 100);
this.position.end = e.nativeEvent.layout.y + e.nativeEvent.layout.height + 100;
}
handleScroll = (e:any) => {
const scrollPosition = e.nativeEvent.contentOffset.y;
const paused = this.state.paused;
const {start,end} = this.position;
console.log("========================================");
console.log(start,end,scrollPosition);
console.log("========================================");
if(scrollPosition > start && scrollPosition < end && paused){
this.setState({paused:false});
} else if((scrollPosition > end || scrollPosition < start) && !paused){
this.setState({paused:true});
}
My render function:
<ScrollView
onScroll={(event) => {
this.handleScroll(event)
}}
onLayout={this.handleVideoLayout}
>
{data.map((item,index)=> {
return (
<VideoComponent/>
)
})}
</ScrollView>
I don’t have idea about doing this using scroll view but you have an option in flatlist. In that, you will get the visible items on the screen. and then based on your requirements you can apply conditions.
see my below boilerplate.
const onViewableItemsChanged = ({viewableItems, changed}) => {
//here 'viewableItems' is an item that is currently visible on screen.
};
const viewabilityConfigCallbackPairs = useRef([{onViewableItemsChanged}]);
<FlatList
data={data}
renderItem={renderPost}
showsVerticalScrollIndicator={false}
viewabilityConfigCallbackPairs={
viewabilityConfigCallbackPairs.current
}
/>
find the current index of your list and also the total index's of your list and then put like this
`
const [paused, setPaused] = useState(false);
<Video
videoRef={videoRef}
source={{uri: userData?.Video?.path}}
poster={VideoToImageUrlCoverter(userData?.Video?.path)}
posterResizeMode={'cover'}
pauseOnPress={paused}
shouldPlay={currentIndex === index}
repeat={true}
autoplay
resizeMode="cover"
paused={
currentIndex == index
? paused
: !paused && currentIndex == index
? false
: true
}
/>
`
I want to achieve something like in the 1st image, where view will be placed in the same line if a condition is true, if not the next view must be placed in the next line.
In my app i have now this result:
The code for that :
<ScrollView contentContainerStyle={styles.exercisesContainer}>
{item.category_workouts.map((exercice, index) => (
<Exercises
key={index}
img={AssetsManager.Images.exerciseImg}
title={exercice.workout_name}
level={exercice.level}
/>
))}
//
//
exercisesContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'center',
},
Found a solution to my issue. Using a function i calculate the with of each element depending on his prop level.
Examples:
if the level of the first element is 'beginner' and the next one is 'medium', the first element will take '100%' width thus taking the hole line and because he is the only 'beginner' element.
if we have three elements with the same level maybe 'advanced' they will be given '33%' width each.
here is my function, however it needs some refactoring :
const getExercicesWidth = exercices => {
const exercicesLevels = [];
exercices.forEach(exercice => {
exercicesLevels.push(exercice.level);
});
let widthArray = [];
let counter = 1;
for (let i = 0; i < exercicesLevels.length; i++) {
if (i === 0) {
continue;
}
if (exercicesLevels[i] == exercicesLevels[i - 1]) {
counter++;
} else {
widthArray.push(counter);
counter = 1;
}
}
widthArray.push(counter);
const percentageWidthArray = [];
widthArray.forEach(number => {
let c = number;
while (c > 0) {
percentageWidthArray.push(`${Math.round(100 / number)}%`);
c--;
}
});
return percentageWidthArray;
};
the result now is like this:
I am having trouble with saving the value of text inputs(which i am fetching from an API) to an array. Currently I can save the values but if I try to edit one text input value, it will save the new value and still keep the old one as well. I want to save only the latest value when the 'onChangeText' has ended.
I appreciate any suggestion!
Here is my code:
textfieldsObject = () => {
const obje = this.props.navigation.state.params.item;
var keyvalue_to_json = JSON.parse(obje.keyValues);
var textinputName = [];
var foundTextFields = [];
for (let i = 0; i < keyvalue_to_json.inputFields.length; i++) {
if (keyvalue_to_json.inputFields[i].type === 'textfield') {
foundTextFields.push(<TextInput onEndEditing={(e) => {
keyvalue_to_json.inputFields[i].inputValues = e.nativeEvent.text;
this.myInputFields.myTextFields.push(keyvalue_to_json.inputFields[i])
}}
>{keyvalue_to_json.inputFields[i].placeholderText}</TextInput>)
}
}
return (
<View>
{foundTextFields}
</View>
)
}
You can't make a push every time you edit your textinput. You will get an array of every edit you made, I think that's not what you want.
Maybe this:
this.myInputFields.myTextFields.[i]=keyvalue_to_json.inputFields[i]
onPressFavourites(item) {
let realm = Realm.open({
path: RNFS.DocumentDirectoryPath +'/trees.realm',
schema: [sightingSchema, treeSchema],
schemaVersion: 7,
}).then(realm => {
if(item.favourites === 0) {
realm.write(() => {
item.favourites = 1;
item.favouritesColour = '#91b54d';
});
} else {
realm.write(() => {
item.favourites = 0;
item.favouritesColour = 'transparent';
});
}
});
alert(item.favourites)// Update a property value
}
I am trying to update an object in the Realm when a button is clicked however I get the error
"Possible Unhandled Promise Rejection (id: 0):
Error: Cannot modify managed objects outside of a write transaction."
This code was working a couple days ago but is now throwing the above error.
I am still learning React Native and Realm but from my understanding and following examples and the Realm docs, I am using the correct code so it should work.
EDIT
Seems the realm and write transactions were fine.
We were able to find a roundabout way to fix it however now the updates don't display until the app is refreshed.
It seems the error Possible Unhandled Promise Rejection (id: 0):
Error: Cannot modify managed objects outside of a write transaction. was produced when using the argument item to define the chosen Realm Object. However, if we create a variable related to the item, it doesn't produce that error. Any ideas why this would happen?
onPressFavourites(item) {
//console.log(realm);
let realm = Realm.open({
path: RNFS.DocumentDirectoryPath +'/trees.realm',
schema: [sightingSchema, treeSchema],
schemaVersion: 7,
}).then(realm => {
let trees = realm.objects('TreeInfo');
for(var i=0; i<trees.length;i++){
if(trees[i].commonName == item.commonName){
var chosen = trees[i];
break;
}
}
console.log(chosen);
if(item.favourites === 0) {
realm.write(() => {
//item.favourites = 1;
//item.favouritesColour = '#91b54d';
chosen.favourites = 1;
chosen.favouritesColour = '#91b54d';
});
}
else {
realm.write(() => {
chosen.favourites = 0;
chosen.favouritesColour = 'transparent';
});
}
});
alert(item.favourites)// Update a property value
}
EDIT
render() {
var treeList = this.state.trees;
console.log(treeList);
const {navigate} = this.props.navigation;
//var tree = treeList[0];
return(
<Container>
<Header searchBar style={styles.searchBar}>
<Item style={styles.searchBarInner}>
<Icon name="ios-search" />
<Input placeholder="Search" />
</Item>
</Header>
<List dataArray={treeList}
renderRow={(item) =>
<ListItem style={styles.ListItem} button={true}>
<ListButton item={item}
onSelect={(item) => this.saveTreeInfo(item)}
onSelectFavourites={(item) => this.onPressFavourites(item)
}
/>
</ListItem>
}
>
</List>
</Container>
);
}
Above is where item is being passed to the onPressFavourites function. item is being generated from an array of Realm Objects treelist and displayed in a list.
The treelist array comes from the variable this.state.trees which is displayed below.
filterContent(){
Realm.open({
path: RNFS.DocumentDirectoryPath +'/trees.realm',
schema: [sightingSchema, treeSchema],
schemaVersion: 7,
}).then(realm => {
let trees = realm.objects('TreeInfo');
let length = trees.length;
let treeRealm = trees.sorted('commonName');
console.log(this.state.leafEdges);
if (this.state.leafEdges === 'smooth') {
var smoothLeaf = treeRealm.filtered('leafEdges CONTAINS "Smooth"');
this.setState({trees:smoothLeaf});
}
if (this.state.leafEdges === 'toothed') {
var toothedLeaf = treeRealm.filtered('leafEdges CONTAINS "Toothed"');
this.setState({trees:toothedLeaf});
}
if (this.state.leafEdges === 'notsure') {
this.setState({trees:treeRealm});
}
else if (this.state.leafEdges === 'null') {
this.setState({trees:treeRealm});
}
});
}
Answer in English:
The trees has something about realm, so you cannot change it. Just copy a new Array like this:
let trees = realm.objects('TreeInfo');
var arr=[];
for (let i = 0;trees && i < trees .length; i++) {
var item=trees[i];
var item2={
id: item.id,
....
}
arr.push(item2);
}
You should improve the syntax
let order = {...bla, bla,bla}
realm.write(() => {
realm.create("order", order);
});
这个trees 里面关联了realm里的一些东西,所以不能改变,要重新复制一个数组,像这样
let trees = realm.objects('TreeInfo');
var arr=[];
for (let i = 0;trees && i < trees .length; i++) {
var item=trees[i];
var item2={
id: item.id,
....
}
arr.push(item2);
}
I'm trying to build a a sticky component like this app
http://www.screencapture.ru/file/E88F08Fc
Deals, Products, Events tabs/segmentControl are actually start from bottom and as you scroll when you hit bottom of the header it stops and start stick while the content keep scrolled
this is my code
<View style={styles.container}>
<ScrollView
style={styles.container}
scrollEventThrottle={16}
onScroll={
Animated.event(
[{nativeEvent:{contentOffset: {y: this.state.scrollY}}}]
)
}
>
{this._renderScrollViewContent()}
</ScrollView>
<View style={styles.stickyStuff}></View>
<Animated.View
style={[styles.header, {height: headerHeight}]}
>
<Animated.Image
source={require('../../assets/images/pvj.jpg')}
style={[styles.backgroundImage, {opacity: imageOpacity}]}
/>
<Animated.View style={[styles.bar, {transform: [{translateY: titleTranslateY}, {translateX: titleTranslateX}]}]}>
<Text style={styles.title}>PARIS VAN JAVA</Text>
</Animated.View>
</Animated.View>
</View>
It is very simple with ScrollView component. There is already something called "stickyHeaderIndices" which takes the index of child to make sticky.In following code, content inside renderComponent2 stays sticky when you scroll.
<ScrollView
stickyHeaderIndices={[1]}
showsVerticalScrollIndicator={false}
>
{this.renderComponent1()}
{this.renderComponent2()}
{this.renderComponent3()}
</ScrollView>
Refer: https://facebook.github.io/react-native/docs/scrollview.html#stickyheaderindices
Thank you for answering my first question. I found it how to do it now. So basically what I do is I cheat on F8App code where they fallback from F8Scrolling native module if it is not available to this:
if (!NativeModules.F8Scrolling) {
var distance = EMPTY_CELL_HEIGHT - this.state.stickyHeaderHeight;
var translateY = 0; this.state.anim.interpolate({
inputRange: [0, distance],
outputRange: [distance, 0],
extrapolateRight: 'clamp',
});
transform = [{translateY}];
}
which gives the idea of animating my sticky view so here is i ended up
const stickySegmentControlX = this.state.scrollY.interpolate({
inputRange: [0, STICKY_SCROLL_DISTANCE],
outputRange: [INIT_STICKY_HEADER, HEADER_MIN_HEIGHT],
extrapolate: 'clamp'
})
...
return(
....
<Animated.View ref="stickyHeader" style={[styles.stickyStuff, {top: stickySegmentControlX}]}>
<Text>Go Stick</Text>
</Animated.View>
....
);
so basically what I've done is animating the top position of an {position: 'absolute'}'s view while as the value of output range is between bottom position to the height of my header (so it'll stop right below my header) and the input range is between 0 and the difference of the top position and the header height. Eventually, the animation will feel natural as you scroll the scrollview.
There you go a sticky header custom view. Here is the result:
If you feel my answer is confusing, its better you heading to janic duplessis Medium post:
React Native ScrollView animated header
This is very easy you just need to know the index component that you want it to sticky during scrolling inside a scrollview component. Here is an example:
<ScrollView
style={styles.screen}
stickyHeaderIndices={[0]}
>
<View><Text>Hello1</Text></View>
<View><Text>Hello2</Text></View>
<View><Text>Hello3</Text></View>
</ScrollView>
So when scrolling Hello1 text will sticky to the top of the ScrollView
Good Luck
This can prove useful
import android.os.Build
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.widget.ScrollView
import androidx.annotation.RequiresApi
import kotlinx.coroutines.handleCoroutineException
#RequiresApi(Build.VERSION_CODES.M)
class StickyViewHistory(val stickyViewInflateListener: StickyViewInflateListener, val scrollView: ScrollView) {
interface StickyViewInflateListener {
fun inflate(view : View)
fun getHeaderViews() : List<View>
fun removeSticky()
fun getStickyHolderView() : View
fun getStickyLayout() : View
}
var currentVisibleHeader : ViewOverLapState? = null
private var isStickyVisible = false
init {
scrollView.setOnScrollChangeListener { view: View, i: Int, newScrollY: Int, i2: Int, oldScrollY: Int ->
val overlapStates = getOverlappStates()
val numberOfHeadersAboveScroll = overlapStates.filter { it == ViewOverLapState.IS_ABOVE }.count()
//If all of the headers are either visible or below the screen, we remove the sticky
if(numberOfHeadersAboveScroll == 0){
if(isStickyVisible == true){
removeSticky()
}
}
else {
if(newScrollY < oldScrollY){
displayHeaderWhenScrollingDown(overlapStates)
}
else {
displayHeaderWhileScrollingUp(overlapStates)
}
}
}
}
/**
* Assumes there is atleast one header view which has scrolled above screen
*/
fun displayHeaderWhileScrollingUp(overlapStates : List<ViewOverLapState>){
val mostRecentAboveHeader = getMostRecentAboveHeader(overlapStates)
val highestVisibleHeader = getHighestVisibleHeader(overlapStates)
//If there is a header on screen which is just about to overlap the current sticky layout we remove the Sticky altogether
highestVisibleHeader?.let {
if(isOverlappingCurrentStickyLayout(it.view)){
removeSticky()
return
}
}
// If the most recent header that has scrolled above screen is the current sticky,
// we dont do anything since its already being displayed as sticky
if(currentVisibleHeader != null && currentVisibleHeader!!.view == mostRecentAboveHeader.view){
//Do nothing
}
else {
//We sticky the most recent header which has scrolled above screen
setSticky(mostRecentAboveHeader)
}
isStickyVisible = true
}
fun displayHeaderWhenScrollingDown(overlapStates : List<ViewOverLapState>){
currentVisibleHeader?.let { cvh ->
val highestVisibleHeader = getHighestVisibleHeader(overlapStates)
highestVisibleHeader?.let { hvh ->
if(isOverlappingCurrentStickyLayout(hvh.view)){
removeSticky()
return
}
}
}
if(currentVisibleHeader == null){
val mostRecentAboveHeader = getMostRecentAboveHeader(overlapStates)
setSticky(mostRecentAboveHeader)
}
}
/**
* Checks if a view is overlapping with any part of the current sticky layout
*/
fun isOverlappingCurrentStickyLayout(childView: View) : Boolean{
val locationOfSticky = IntArray(2)
stickyViewInflateListener.getStickyLayout().getLocationOnScreen(locationOfSticky)
val childLocation = IntArray(2)
childView.getLocationOnScreen(childLocation)
val locationOfScrollView = IntArray(2)
scrollView.getLocationOnScreen(locationOfScrollView)
if(childLocation[1] <= locationOfSticky[1]+stickyViewInflateListener.getStickyLayout().height &&
childLocation[1] >= locationOfScrollView[1]){
return true
}
return false
}
/**
* Get the headerview which most recently scrolled above screen
*/
fun getMostRecentAboveHeader(overlapStates : List<ViewOverLapState>) : ViewOverLapState {
val aboveViews = overlapStates.filter { it == ViewOverLapState.IS_ABOVE }
var lowestAboveView : ViewOverLapState? = null
var highestYCooridnatesoFar = Int.MIN_VALUE
aboveViews.forEach {
val locationOnScreen = IntArray(2)
it.view.getLocationOnScreen(locationOnScreen)
if(locationOnScreen[1] > highestYCooridnatesoFar){
lowestAboveView = it
highestYCooridnatesoFar = locationOnScreen[1]
}
}
return lowestAboveView!!
}
/**
* Highest here does not mean the highest [y] value but its position on the screen, so lower y means higher
*/
fun getHighestVisibleHeader(overlapStates: List<ViewOverLapState>) : ViewOverLapState? {
val insideViews = overlapStates.filter { it == ViewOverLapState.IS_INSIDE }
var highestInsideView : ViewOverLapState? = null
var lowestYCordinatesoFar = Int.MAX_VALUE
insideViews.forEach {
val locationOnScreen = IntArray(2)
it.view.getLocationOnScreen(locationOnScreen)
if(locationOnScreen[1] < lowestYCordinatesoFar){
highestInsideView = it
lowestYCordinatesoFar = locationOnScreen[1]
}
}
return highestInsideView
}
fun getOverlappStates() : List<ViewOverLapState>{
return stickyViewInflateListener.getHeaderViews()
.map {
getOverlapState(scrollView,it)
}
}
/**
* For all of the view headers, get their overlap state with the scroll view
*/
fun getOverlapState(parentView : View, childView : View) : ViewOverLapState{
val scrollViewLocation = IntArray(2)
parentView.getLocationOnScreen(scrollViewLocation)
val childLocation = IntArray(2)
childView.getLocationOnScreen(childLocation)
if(childLocation[1] < scrollViewLocation[1]){
return ViewOverLapState.IS_ABOVE.apply { view = childView }
}
else if(childLocation[1] > (scrollViewLocation[1]+parentView.height)){
return ViewOverLapState.IS_BELOW.apply { view =childView }
}
else {
return ViewOverLapState.IS_INSIDE.apply { view = childView }
}
}
fun setSticky(viewOverLapState: ViewOverLapState){
stickyViewInflateListener.inflate(viewOverLapState.view)
currentVisibleHeader = viewOverLapState
isStickyVisible = true
}
fun removeSticky(){
currentVisibleHeader = null
stickyViewInflateListener.removeSticky()
isStickyVisible = false
}
enum class ViewOverLapState(val text : String) {
IS_INSIDE("inside"),IS_ABOVE("above"),IS_BELOW("below");
lateinit var view : View
}
}