Expo showing weird additional color upon rendering on IOS devices


#1

IMG_0216

As you can see in the pictures, there are slight discoloration on the back button, torch button, as well as the carousel headers I’m using (the blue part).

They’re not supposed to be there and there isn’t supposed to be another View or component rendered onto the screen code wise.

And on the Scanner screen, you can also see that there’s a 1px gap between the top layer of the translucent black component and the second one. All are coded using flex so I don’t know what could’ve been the problem.

This happens on iphoneX simulator and iphone 5s device (at least so far) and none of the android sim/devices showing the same issue.

Here’s the code and styling:

Scanner page:

import React from 'react';
import {
    Alert,
    Linking,
    Text,
    View,
    StyleSheet
} from 'react-native';
import { BarCodeScanner, Permissions, Camera } from 'expo';

import { windowWidth, windowHeight, normalize } from '../theme/baseTheme';

const opacity = 'rgba(0, 0, 0, .6)';
const styles = StyleSheet.create({
    container: {
        flex: 1,
    },
    scanArea: {
        height: windowHeight - normalize(65),
        width: windowWidth
    },
    layerTop: {
        flex: 1,
        backgroundColor: opacity
    },
    layerCenter: {
        flex: 2,
        flexDirection: 'row'
    },
    layerLeft: {
        flex: 1,
        backgroundColor: opacity
    },
    focused: {
        flex: 10
    },
    layerRight: {
        flex: 1,
        backgroundColor: opacity
    },
    layerBottom: {
        flex: 1,
        backgroundColor: opacity
    },
});

export default class Scanner extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            hasCameraPermission: false,
            lastScan: null
        };
    }
    componentDidMount() {
        this._requestCameraPermission();
    }

    _requestCameraPermission = async () => {
        const { status } = await Permissions.askAsync(Permissions.CAMERA);
        this.setState({
            hasCameraPermission: status === 'granted',
        });
    };

    /**
     * Callback to be called by BarcodeScanner once it reads a compatible barcode, with param destructured
     * @param {Object} result: The result will be in the form of {type:String, data:String}
     */
    onSuccessRead = ({ type, data }) => {
        if (data !== this.state.lastScan) {
            this.setState({ lastScan: data })

            //Get a displayable version of the passed barcode type
            switch (type) {
                case BarCodeScanner.Constants.BarCodeType.aztec: type = "Aztec"; break;
                case BarCodeScanner.Constants.BarCodeType.codabar: type = "Codabar"; break;
                case BarCodeScanner.Constants.BarCodeType.code128: type = "Code128"; break;
                case BarCodeScanner.Constants.BarCodeType.code138: type = "Code138"; break;
                case BarCodeScanner.Constants.BarCodeType.code39: type = "Code39"; break;
                case BarCodeScanner.Constants.BarCodeType.code39mod43: type = "Code39mod43"; break;
                case BarCodeScanner.Constants.BarCodeType.code93: type = "Code93"; break;
                case BarCodeScanner.Constants.BarCodeType.datamatrix: type = "Datamatrix"; break;
                case BarCodeScanner.Constants.BarCodeType.ean13: type = "Ean13"; break;
                case BarCodeScanner.Constants.BarCodeType.ean8: type = "Ean8"; break;
                case BarCodeScanner.Constants.BarCodeType.interleaved2of5: type = "Interleaved2of5"; break;
                case BarCodeScanner.Constants.BarCodeType.itf14: type = "Itf14"; break;
                case BarCodeScanner.Constants.BarCodeType.maxicode: type = "Maxicode"; break;
                case BarCodeScanner.Constants.BarCodeType.pdf417: type = "Pdf417"; break;
                case BarCodeScanner.Constants.BarCodeType.qr: type = "QRCode"; break;
                case BarCodeScanner.Constants.BarCodeType.rss14: type = "Rss14"; break;
                case BarCodeScanner.Constants.BarCodeType.rssexpanded: type = "Rssexpanded"; break;
                case BarCodeScanner.Constants.BarCodeType.upc_a: type = "Upc_a"; break;
                case BarCodeScanner.Constants.BarCodeType.upc_e: type = "Upc_e"; break;
                case BarCodeScanner.Constants.BarCodeType.upc_ean: type = "Upc_ean"; break;
            }

            this.props.onRead(type, data, () => { this.setState({ lastScan: null }) }); //callback, what to do with the data on success read
        }
    }

    render() {
        return (
            <View style={styles.container}>
                {
                    this.state.hasCameraPermission === null ?
                        <Text>Requesting for camera permission</Text> :
                        this.state.hasCameraPermission === false ?
                            <Text style={{ color: '#fff' }}> Camera permission is not granted </Text> :

                            <BarCodeScanner
                                onBarCodeRead={this.onSuccessRead.bind(this)}
                                style={styles.scanArea}
                                autoFocus={Camera.Constants.AutoFocus.on}
                                focusDepth={1}
                                torchMode={this.props.torch}>

                                <View style={styles.layerTop} />
                                <View style={styles.layerCenter}>
                                    <View style={styles.layerLeft} />
                                    <View style={styles.focused} />
                                    <View style={styles.layerRight} />
                                </View>
                                <View style={styles.layerBottom} />
                            </BarCodeScanner>
                }
            </View>
        );
    }
}

User Manual page:

styles.js

import { StyleSheet } from 'react-native';
import { padding, color, fontSize, fontFamily, windowWidth, normalize } from '../../../theme/baseTheme';

const styles = StyleSheet.create({
  container: {
    flex: 1
  },

  headerContainer: {
    borderBottomWidth: 2,
    borderColor: color.blue,
    marginHorizontal: normalize(10),
    marginVertical: normalize(15),
  },

  headerText: {
    fontFamily: fontFamily.bold,
    fontSize: fontSize.large
  },

  accordionContainer: {
    marginVertical: normalize(10),
    marginHorizontal: normalize(10)
  },

  manualContainer: {
    flex: 1,
    marginVertical: normalize(10),
  },

  manualHeader: {
    height: normalize(110),
    backgroundColor: color.light_blue,
    borderTopRightRadius: normalize(20),
    borderTopLeftRadius: normalize(20),
    alignItems: 'center'
  },

  manualImage: {
    height: normalize(80),
    width: normalize(80),
    resizeMode: 'contain',
    marginVertical: normalize(15)
  },

  manualBody: {
    flex: 3,
    borderBottomRightRadius: normalize(20),
    borderBottomLeftRadius: normalize(20),
    backgroundColor: color.white
  },

  manualTitle: {
    fontFamily: fontFamily.bold,
    fontSize: fontSize.large,
    alignSelf: 'center',
    marginVertical: normalize(15)
  },

  manualContent: {
    fontFamily: fontFamily.regular,
    fontSize: fontSize.regular,
    textAlign: 'justify',
    marginBottom: normalize(15),
    marginHorizontal: normalize(15)
  },

  buttonContainer: {
    position: 'absolute',
    bottom: 0,    
    height: normalize(60),
    width: normalize(60),
    backgroundColor: 'rgba(0, 0, 0, .4)',
    borderTopEndRadius: normalize(10)
  },

  backButton: {
    position: 'absolute',
    bottom: 0, 
    height: normalize(57),
    width: normalize(57),
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: 'rgba(0, 0, 0, .5)',
    borderTopEndRadius: normalize(7)
  }
});

export default styles;

UserManual.js

class UserManual extends React.Component {
  constructor(props) {
    super(props);
  }

  /**
   * Map list of tabs into list of Objects containing a full card information of manual
   * @param {Array} list: Tab titles 
   */
  mapTabsToManuals(list) {
    return list.map(item => {
      let text = "Lorem ipsum dolor sit amet, cu verear facilisi vel, soleat menandri mnesarchum in sed. Ex nullam blandit sententiae vix. Cum simul euripidis eu, ea malis oporteat delicatissimi quo, eam in menandri consequat philosophia. Fuisset oporteat pri at, tollit nostrum fierent nec id, eum ad solum detracto. Ex vel verear quaerendum.\n\nMalorum quaerendum ea vim, mel movet partem persecuti et, cu iudico impetus persius sea. Eos falli suscipit accommodare cu. Eam id sanctus albucius, agam facilisi interpretaris ei qui. Usu tamquam maiestatis delicatissimi et. Quo id detraxit reprehendunt, ex his accommodare complectitur.\n\nAd his ferri utroque accusata. Id nam elit decore vivendum. Per solet iuvaret fierent et, diam idque at mea. Vix cu saepe vituperatoribus. Mea nostrud deleniti in. Prompta consequat voluptaria ne eam, no wisi augue vis.\n\nNo qui mollis singulis partiendo. Odio dolorum splendide ut sit, sea in vivendum mediocrem voluptatum, in vim ferri nostro discere. Vim soleat doctus mentitum ut, ut eos decore reprimique consectetuer. Idque constituto mei an, per ei debitis eligendi. Eum ea omnis atomorum aliquando, nam te offendit ocurreret, at mea nusquam docendi.\n\nPro ad probo corpora, vocent dolores mel id, pri assum erroribus no. Oratio tantas ne quo. Cu purto duis nam, ei vix facer moderatius, at sumo neglegentur per. Ea recteque electram eos, sea dicant nullam ex, eu indoctum referrentur eam.";
      let source = links.IDtoIcon(item['MenuID'], 'white');

      return {
        image: source,
        title: links.IDtoName(item['MenuID']),
        content: text
      }
    });
  }

  /**
   * Get a carousel of cards containing tabs of menu based on title
   * @param {Object} menu: Level 1 Menu to be created a carousel from 
   */
  getCarousel(menu) {
    let sliderWidth = windowWidth - 30;
    let itemWidth = sliderWidth - 60;

    return <Carousel
      data={this.mapTabsToManuals(menu['Children'])}
      renderItem={({ item, index }) => {
        return (
          <View style={styles.manualContainer}>
            <View style={styles.manualHeader}>
              <Image style={styles.manualImage} source={item.image} />
            </View>
            <View style={styles.manualBody}>
              <Text style={styles.manualTitle}>{item.title}</Text>
              <Text style={styles.manualContent}>{item.content}</Text>
            </View>
          </View>
        );
      }}
      sliderWidth={sliderWidth}
      itemWidth={itemWidth}
    />
  }

  render() {
    return (
      <View style={styles.container}>
        <ScrollView>
          <View style={styles.headerContainer}>
            <Text style={styles.headerText}>User Manual</Text>
          </View>
          {
            this.props.menuReceived ? this.props.menuList.map((item, key) => {
              if (item['MenuID'] !== links.ID.HELP)
                return (
                  <View style={styles.accordionContainer} key={key}>
                    <Accordion title={links.IDtoName(item['MenuID'])} body={this.getCarousel(item)} />
                  </View>)
            }) : null
          }
        </ScrollView>
        <View style={styles.buttonContainer}>
          <TouchableOpacity style={{ flex: 1 }} onPress={() => Actions.pop()}>
            <View style={styles.backButton}>
              <Icon name='action-undo' type='simple-line-icon' size={40} color='white' />
            </View>
          </TouchableOpacity>
        </View>
      </View>
    );
  }
}

Does anyone have any idea of what might have happened here?

Thanks in advance for any suggestions :slight_smile:


#2

Hi-

Can you put your code in a Snack or share your whole repo?
It’s really hard to figure out what’s wrong with code that isn’t easy to run.


#3

Yes right sorry i forgot.

It would be hard to recreate using snack since the component relies on other parts of the code (component and data wise), so i hope repo is fine:


#4

@ccheever I have managed to learn how to use snack properly on my days off so here it is, a representation of the ios problem:

https://snack.expo.io/SJ2hPGnQ7

This is a screenshot from snack.
And if you try loading it on snack’s Android, you wouldn’t see the weird discoloration.

Hope this helps give you more insight as to what is the problem here. For all I know it might just be a miscalculation on my end but I’m really stuck here so any pointers would be awesome!

Thanks in advance :slight_smile:


#5

Hi @raywinarto - when I replaced your normalize function with a stub (const normalize = val => val;) all of the weird translucent bits in your UI went away. (I’m assuming that’s the problem you were referring to.) I’m not familiar with the react-native-size-matters library but it looks like that’s the culprit here, so I’d suggest reading its source and making sure it’s really something that you want/need.

Hope that helps :slight_smile:


#6

@esamelson Thanks for the generous help, I never would’ve thought of that!
lots to learn…
sorry for wasting ur time

UPDATE: this does fix the main color issue, but the 1px gap apparently has nothing to do with normalize method:

import React from 'react';
import {
    Alert,
    Linking,
    Text,
    View,
    StyleSheet
} from 'react-native';
import { BarCodeScanner, Permissions, Camera } from 'expo';

import { windowWidth, windowHeight} from '../theme/baseTheme';

const opacity = 'rgba(0, 0, 0, .6)';
const styles = StyleSheet.create({
    container: {
        flex: 1,
    },
    scanArea: {
        height: windowHeight,
        width: windowWidth
    },
    layerTop: {
        flex: 1,
        backgroundColor: opacity
    },
    layerCenter: {
        flex: 2,
        flexDirection: 'row'
    },
    layerLeft: {
        flex: 1,
        backgroundColor: opacity
    },
    focused: {
        flex: 10
    },
    layerRight: {
        flex: 1,
        backgroundColor: opacity
    },
    layerBottom: {
        flex: 1,
        backgroundColor: opacity
    },
});

export default class Scanner extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            hasCameraPermission: false,
            lastScan: null
        };
    }
    componentDidMount() {
        this._requestCameraPermission();
    }
    
    _requestCameraPermission = async () => {
        const { status } = await Permissions.askAsync(Permissions.CAMERA);
        this.setState({
            hasCameraPermission: status === 'granted',
        });
    };

    /**
     * Callback to be called by BarcodeScanner once it reads a compatible barcode, with param destructured
     * @param {Object} result: The result will be in the form of {type:String, data:String}
     */
    onSuccessRead = ({ type, data }) => {
        if (data !== this.state.lastScan) {
            this.setState({ lastScan: data })

            //Get a displayable version of the passed barcode type
            switch (type) {
                case BarCodeScanner.Constants.BarCodeType.aztec: type = "Aztec"; break;
                case BarCodeScanner.Constants.BarCodeType.codabar: type = "Codabar"; break;
                case BarCodeScanner.Constants.BarCodeType.code128: type = "Code128"; break;
                case BarCodeScanner.Constants.BarCodeType.code138: type = "Code138"; break;
                case BarCodeScanner.Constants.BarCodeType.code39: type = "Code39"; break;
                case BarCodeScanner.Constants.BarCodeType.code39mod43: type = "Code39mod43"; break;
                case BarCodeScanner.Constants.BarCodeType.code93: type = "Code93"; break;
                case BarCodeScanner.Constants.BarCodeType.datamatrix: type = "Datamatrix"; break;
                case BarCodeScanner.Constants.BarCodeType.ean13: type = "Ean13"; break;
                case BarCodeScanner.Constants.BarCodeType.ean8: type = "Ean8"; break;
                case BarCodeScanner.Constants.BarCodeType.interleaved2of5: type = "Interleaved2of5"; break;
                case BarCodeScanner.Constants.BarCodeType.itf14: type = "Itf14"; break;
                case BarCodeScanner.Constants.BarCodeType.maxicode: type = "Maxicode"; break;
                case BarCodeScanner.Constants.BarCodeType.pdf417: type = "Pdf417"; break;
                case BarCodeScanner.Constants.BarCodeType.qr: type = "QRCode"; break;
                case BarCodeScanner.Constants.BarCodeType.rss14: type = "Rss14"; break;
                case BarCodeScanner.Constants.BarCodeType.rssexpanded: type = "Rssexpanded"; break;
                case BarCodeScanner.Constants.BarCodeType.upc_a: type = "Upc_a"; break;
                case BarCodeScanner.Constants.BarCodeType.upc_e: type = "Upc_e"; break;
                case BarCodeScanner.Constants.BarCodeType.upc_ean: type = "Upc_ean"; break;
            }
           
            this.props.onRead(type, data, () => { this.setState({ lastScan: null }) }); //callback, what to do with the data on success read
        }
    }

    render() {
        return (
            <View style={styles.container}>
                {
                    this.state.hasCameraPermission === null ?
                        <Text>Requesting for camera permission</Text> :
                        this.state.hasCameraPermission === false ?
                            <Text style={{ color: '#fff' }}> Camera permission is not granted </Text> :

                            <BarCodeScanner
                                onBarCodeRead={this.onSuccessRead.bind(this)}
                                style={styles.scanArea}
                                autoFocus={Camera.Constants.AutoFocus.on}
                                focusDepth={1}
                                torchMode={this.props.torch}>

                                <View style={styles.layerTop} />
                                <View style={styles.layerCenter}>
                                    <View style={styles.layerLeft} />
                                    <View style={styles.focused} />
                                    <View style={styles.layerRight} />
                                </View>
                                <View style={styles.layerBottom} />
                            </BarCodeScanner>
                }
            </View>
        );
    }
}

IMG_0216

Do you have any guess on what I should check here?
It shouldn’t be the styling since it’s only using flex for everything…
Again, thanks for the help :slight_smile:


#7

nevermind! Apparently I just can’t style the scan area height with windowHeight - 65! Or anything that ends with 5 really…
maybe related to https://github.com/facebook/react-native/issues/13305?

Anyway, setting the scan area to windowHeight - <multiple of 10> fixed this for me

Thanks again :slight_smile:


#8

This topic was automatically closed 15 days after the last reply. New replies are no longer allowed.