alexbrillant / react-native-deck-swiper

tinder like react-native deck swiper
ISC License
1.56k stars 465 forks source link

Card width and height calculated from window dimensions, but it should use the container's #20

Closed guillermodlpa closed 6 years ago

guillermodlpa commented 7 years ago

Problem

Card height/width are currently calculated from window dimensions.

screenshot 2017-06-27 20 32 24

[...]

screenshot 2017-06-27 20 32 52

This works well for a full screen deck swiper. However, it's not correct if the deck swiper is used inside a container with other dimensions. Cards currently will overflow and most likely render partially outside of the screen. My specific use case is having a header and a deck container that grows to fill remaining screen size.

screen recording 2017-06-27 at 08 38 pm

Solution

I suggest updating implementation so that card dimensions are calculated from the Swiper component dimensions, instead of the window.

Thoughts?

alexbrillant commented 7 years ago

Do you have any ideal on how to do this ? I would like the dimensions to be calculated like this too, but I haven't found a good way to do it yet. You can use the margin props to achieve your desired effect. Any ideals @gpuenteallott

guillermodlpa commented 7 years ago

Hi @alexbrillant thanks for replying! :)

I tried to fork and play with it locally, but I wasn't able to kick off the example in my simulator. Apart from the error I was getting, I noticed that the package.json version specified is ^1.2.1 but in npm the latest published is 1.2.0.

To determine width/height, there's the onLayout way, and the measure way. I've never tried measure myself though. Did you try them?

Also, googling around I found another react native swiper that doesn't use card dimensions in its calculations. It's interesting to look at that code. It's using a hardcoded number for the swipe threshold.

PatNeedham commented 7 years ago

@gpuenteallott I was curious about this also. That cardHeight variable inside Swiper.js is being used inside the cardStyle object. But just about everywhere cardStyle is being used, there is a corresponding this.customCardStyle. For example, lines 517 to 537:

calculateSwipableCardStyle = () => {
    const opacity = this.props.animateCardOpacity
      ? this.interpolateCardOpacity()
      : 1
    const rotation = this.interpolateRotation()

    return [
      styles.card,
      this.cardStyle,
      {
        zIndex: 3,
        opacity: opacity,
        transform: [
          { translateX: this.state.pan.x },
          { translateY: this.state.pan.y },
          { rotate: rotation }
        ]
      },
      this.customCardStyle
    ]
  }

customCardStyle is initialized inside initializeCardStyle and is assigned the cardStyle prop:

this.customCardStyle = this.props.cardStyle

I'm using react-native-deck-swiper npm version 1.3.5

asciifaceman commented 6 years ago

In a fork I am using locally, i added width and height as a proptype specific to the card itself. If it is present it uses that, otherwise it defaults to the library default with some modifications. I am also adding a division modifier proptype to control how tall the cards are because I was having problems getting consistent sizing behavior. Overall this feels much more stable and I'm considering cleaning it up for a PR that would add a ton of control to width/height and allow you to properly use flex to style inside the card container without worrying about overflow being cut off by the old margin (and be consistent across devices of different sizes - because the current margin controls can cut off the card on smaller devices because of the naive device width/height calcs that are absolute)

asciifaceman commented 6 years ago

@alexbrillant excerpt my modifications that I may clean up and put in a PR if you are interested in checking it out (divisors default to 1, thus no change if nothing given). It largely preserves the old behavior:

Note: this means the tap bounds will be exact to the card, not floating. This means you can set top, left, bottom, right on an image inside the Card and flex the heck out of it and have the entire card actually match the tap bounds.


  initializeCardStyle = () => {
  //cardInputWidth: PropTypes.number,
  //cardInputHeight: PropTypes.number,
    const {
      cardVerticalMargin,
      cardHorizontalMargin,
      cardInputWidth,
      cardInputHeight,
      heightDivisor,
      widthDivisor,
      marginTop,
      marginBottom
    } = this.props

    var cardWidthTemp = 0;
    var cardHeightTemp = 0;
    if (!cardInputWidth) {
     cardWidthTemp = (width - cardHorizontalMargin * 2) / widthDivisor;
    } else {
      cardWidthTemp = cardInputWidth;
    }
    if (!cardInputHeight) {
      cardHeightTemp = (height - cardVerticalMargin * 2 - marginTop - marginBottom) / heightDivisor; //1.3
    } else {
      cardHeightTemp = cardInputHeight;
    }

    const cardWidth = cardWidthTemp;
    const cardHeight = cardHeightTemp;

    this.cardStyle = {
      top: cardVerticalMargin,
      alignSelf: 'center',
      width: cardWidth,
      height: cardHeight
    }

    this.customCardStyle = this.props.cardStyle
  }

Example usage:

  renderCard(cardObject) {
    return (
      <View style={styles.cardWrapper}>
        <View style={{flex: 2}}>
          <Image source={{ uri: cardObject.ImageThatFillsView }} style={styles.cardImage} />
        </View>
        <View style={styles.cardSpacer}>
          <Text style={styles.profileName}>{cardObject.name}</Text>
          <View style={styles.descriptionContainer}>
            <Text style={styles.sectionHeader}>Some text</Text>
            <Text style={styles.profileText}>{cardObject.someText}</Text>
          </View>
        </View>
      </View>
    )
  }

  render() {
    return (
      <View style={styles.container}>
          <Swiper
              cards={this.state.data}
              renderCard={this.renderCard}
              cardStyle={styles.swiperStyle}
              onSwiped={(cardIndex) => {console.log(cardIndex)}}
              onSwipedAll={() => {console.log('onSwipedAll')}}
              cardIndex={0}
              cardInputWidth={300}
              onTapCard={(cardIndex) => {alert("Tapped card.")}}
              onTapCardDeadZone={3}
              backgroundColor={GLOBAL.COLOR.LIGHT_PURPLE}>
              <Text style={styles.helpText}> Swipe up or down to continue to the next card. </Text>
              <Text style={styles.helpText}> Tap the picture to view. </Text>
          </Swiper>
      </View>
    )

... style
  swiperStyle: {
    flex: 1, 
    borderWidth: 5, 
    borderRadius: 10
  },
  cardImage: {
    position: 'absolute',
    alignSelf: 'center',
    top: 0,
    left: 0,
    bottom: 0,
    right: 0,
    resizeMode: 'cover',
  },

Looks like:

screen shot 2018-01-01 at 4 56 58 am

tl;dr It's not quite what the original ask was for, but I believe it is a good compromise.

Edit edit edit edit: I may be able to utilize this to figure out the "stack" offset as well thats been requested.

felipedeboni commented 6 years ago

Why not using just CSS? Am I missing something that will be broken?

I am using the following, seems to be working just fine.

<Swiper
  ref={swiper => { this.swiper = swiper }}
  cards={questions}
  verticalSwipe={false}
  onSwipedLeft={this.handleSwipedLeft}
  onSwipedRight={this.handleSwipedRight}
  onSwipedAll={this.handleFinish}
  showSecondCard={questions.length > 1}
  backgroundColor={'transparent'}
  cardStyle={{
    top: 0,
    left: 0,
    bottom: 0,
    right: 0,
    width: 'auto',
    height: 'auto'
  }}
  renderCard={card => (
    <CardDetails
      card={card}
      handleAnswerToggle={this.handleAnswerToggle}
    />
  )}
/>

image image

bernhardt1 commented 5 years ago

A had an issue similar to this one. In my case I wanted the card to render above all the other content on the screen when dragged around. I was able make the parent Views width and height overlap the area I wanted the card to overlap. From there I used @felipedeboni approach to adjust the cardStyle.

There was a hidden view component in the swiper that was stopping my click events from getting through to content underneath. To fix it, I did have to go inside the Swiper code and change the render method to include pointerEvents="box-none".

render = () => {
    return (
      <View
        pointerEvents="box-none"
        style={[
          styles.container,
          {
            backgroundColor: this.props.backgroundColor,
            marginTop: this.props.marginTop,
            marginBottom: this.props.marginBottom
          }
        ]}
      >
        {this.renderChildren()}
        {this.renderStack()}
        {this.props.swipeBackCard ? this.renderSwipeBackCard() : null}
      </View>
    );
  };

Perhaps this can be changed in the repository? It is one line pointerEvents="box-none" for the view being rendered. @alexbrillant what do you think?

webraptor commented 5 years ago

@bernhardt1 @alexbrillant simply adding the prop wouldn't be good as it wouldn't be configurable. Having it as a swiper prop with a default value could actually work.