react-native-view-shot
react-native-view-shot copied to clipboard
blank image on android, iOS works well
Getting blank image on android, iOS works well , Below is the code :-
"react-native-view-shot": "^3.1.2",
<View style={styles(theme).container}>
<View collapsable={false} style={[styles(theme).digitalView]} ref={viewRef}>
<DigitalFlyar barberUser={barberUser} isTheme={isTheme} />
</View>
<Button text={strings.download} onPress={downloadImage} style={styles(theme).buttonView} />
const downloadImage = async () => {
console.warn("Download");
try {
// react-native-view-shot caputures component
const format = Platform.OS === "android" ? "raw" : "png";
const result = Platform.OS === "android" ? "zip-base64" : "base64";
captureRef(viewRef, {
format: 'jpg',
quality: 0.8,
result,
}).then(data => {
// expected pattern 'width:height|', example: '1080:1731|'
const resolution = /^(\d+):(\d+)\|/g.exec(data);
const base64 = data.substr((resolution || [''])[0].length || 0);
// console.warn("IMAGE DOWNLOAD",base64);
onShareImage('data:image/jpeg;base64,' + base64);
});
} catch (error) {
console.warn(error);
}
};
const onShareImage = async (url: string) => {
try {
await Share.open({
title: 'Barbr',
url: url,
failOnCancel: false,
type: 'image/jpeg',
})
.then(res => {
console.log(res);
navigation.goBack();
})
.catch(err => {
err && console.log('err', err);
});
} catch (error) {
//@ts-ignore
console.warn(error);
showAlert('', error.message);
}
};
we will need more information to pin point the actual problem.
what is DigitalFlyar made of? is it only text and layouts? is it using images? is it using some other native components like opengl stuff?
@gre Below is DigitalFlyar
import { useFocusEffect } from '@react-navigation/native';
import {selectBarber} from 'appRedux/store/auth/selector';
import {Calendar, IconPosterNext, Link} from 'assets';
import Struck from 'assets/Struck';
import {Avatar, CardView, TextView} from 'atoms';
import React from 'react';
import {ImageBackground, View, Image} from 'react-native';
import {useSelector} from 'react-redux';
import {strings, withTheme, baseWebsite, ThemeType, showAlert} from 'utils';
import {getTheme} from 'utils/theme/thememode';
import styles from './styles';
interface DigitalFlyarProps {
isTheme: Boolean;
}
const DigitalFlyar: React.FC<DigitalFlyarProps> = (props: DigitalFlyarProps) => {
const {isTheme} = props;
let url = baseWebsite;
let barberUser = useSelector(selectBarber);
let theme = isTheme ? getTheme(ThemeType.LIGHT) : getTheme(ThemeType.DARK);
let imageLogo = isTheme ? require('../../../assets/BlackGreenLogo.png') : require('../../../assets/WhiteGreenLogo.png');
// console.warn(barberUser?.image);
// useFocusEffect(
// React.useCallback(() => {
// }, []),
// );
return (
<View style={styles(theme).main}>
<ImageBackground
style={styles(theme).container}
borderRadius={10}
source={isTheme ? require('../../../assets/FlayerWhiteBgold.png') : require('../../../assets/FlayerBg.png')}>
<View style={styles(theme).profileContainer}>
{barberUser?.image ? (
// <Image uri={barberUser.image} width={70} />
<Image
source={{
uri: barberUser.image
}}
style={{width: 70, height: 70, borderRadius: 400/ 2,shadowColor: 'white',
shadowOffset: {width: 1, height: 1},
shadowOpacity: 0.6,}}
/>
// <Avatar imageUrl={barberUser.image} size={70} style={styles(theme).profileiImage}
// avatarStyle={{ borderWidth: 1, borderColor: 'black', }}
// containerStyle={{ borderColor:'black', borderWidth:0 }}
// />
):<></>}
</View>
<TextView text={barberUser?.display_name} style={[styles(theme).activeLinkText]} />
<TextView text={strings.is_now_available_on} style={styles(theme).avaliableText} />
<View style={styles(theme).imageView}>
<Image source={imageLogo} resizeMode="contain" style={styles(theme).logoImage} />
{/* <Image
source={isTheme ? require('../../../assets/BlackLogo.png') : require('../../../assets/WhiteLogo.png')}
resizeMode="contain"
style={styles(theme).logoImage}
/>
<View style={styles(theme).greenLogoView}>
<Image source={require('../../../assets/GreenLogo.png')} resizeMode="contain" style={styles(theme).logoImage} />
</View> */}
</View>
<View style={styles(theme).bookView}>
<TextView text={strings.book_your_next_trim} style={styles(theme).bookText} />
<CardView style={isTheme ? styles(theme).lightlinkView : styles(theme).linkView}>
<TextView text={`${url}`} style={[styles(theme).linkText]} />
<TextView text={barberUser?.username} style={styles(theme).linkUserName} />
</CardView>
</View>
<View style={styles(theme).footerView}>
<View style={styles(theme).calendarContainer}>
<Link />
{/* <View style={styles(theme).roundView}>
<TextView text="1" style={styles(theme).digitText} />
</View> */}
<TextView text={strings.tap_link.toLocaleUpperCase()} style={styles(theme).tapLinkText} />
</View>
<View style={styles(theme).calendarContainer}>
<IconPosterNext color={isTheme ? 'rgba(239, 239, 239, 1)' : 'white'} />
</View>
<View style={styles(theme).calendarContainer}>
<Calendar />
{/* <View style={styles(theme).roundView}>
<TextView text="2" style={styles(theme).digitText} />
</View> */}
<TextView text={strings.book_in.toLocaleUpperCase()} style={styles(theme).tapLinkText} />
</View>
<View style={styles(theme).calendarContainer}>
<IconPosterNext color={isTheme ? 'rgba(239, 239, 239, 1)' : 'white'} />
</View>
<View style={styles(theme).struckContainer}>
<Struck />
{/* <View style={styles(theme).roundView}>
<TextView text="3" style={styles(theme).digitText} />
</View> */}
<TextView text={strings.look_fresh.toLocaleUpperCase()} style={styles(theme).tapLinkText} />
</View>
</View>
</ImageBackground>
</View>
);
};
export default withTheme(DigitalFlyar);
@gre any update on this?
it's very hard for me to dig into each single usecase, it would be good to pinpoint to the part of your component that don't capture, like having a minimal reproductible example would help. there are many limitations on cases where the native side can't properly capture views.
i have the opposite result, is completely blank on iOS while not on android, and i got the same configuration and view for both
Hello, same issue with android here as well.
I have the following component:
<>
<ViewShot ref={viewShotRef} style={{flex: 1}}>
<ViroARSceneNavigator
ref={ref}
autofocus={autofocus}
initialScene={{
scene: ARScene,
}}
style={{flex: 1}}
/>
</ViewShot>
<ARGui
ref={viewShotRef}
isLoading={isRealLoading}
isProduct={!!productId}
/>
</>
The ARGui is passing the viewShotRef to the "ARScreenshotButton" component when the capture method is called on button press.
const ARScreenshotButton = (_props: any, ref?: MutableRefObject<any>): JSX.Element => {
const {showModal} = useModal();
async function handleTakeScreenshot(): Promise<void> {
try {
// takeScreenshot from viro-react not working in v 2.23.0
// using react-native-screenshot-capture instead
const imageUrl = await ref.current?.capture();
CameraRoll.save(imageUrl, {type: 'photo'});
showModal({
HeaderText: t`Info`,
BodyText: t`Picture has been successfully stored to camera roll`,
SuccessButtonText: t`Ok`,
});
} catch (err) {
console.log(err);
}
}
return (
<Pressable onPress={handleTakeScreenshot}>
<Camera />
</Pressable>
);
}
export default forwardRef(ARScreenshotButton);
On iOS picture is created perfectly, on android its just (blank) kind of white image.
I've didn't find any info that we should somehow change the Manifest xml or anything else. Is it necessary?
Thanks in advance for any help.
EDIT: According to the Interoperability Table react-gl wont work on android.
@ViktorVojtek same issue here.. Did you find anything? (There is a prop that maybe helps but I am not sure on where to use it.. *handleGLSurfaceViewOnAndroid)
same issue with imagebackground not showing the image in the screenshot.
I solved this issue by using imageRef on ImageBackground. Code is imcomplete as I tried to remove unneeded portions:
const imageRef = useRef(null);
const {captureScreenshot, moveFileToStorage} = useScreenshot({
viewRef: imageRef,
});
const onScreenShotPress = async () => {
const uri = await captureScreenshot(); // Capture the screenshot
};
return (
<View style={styles.container}>
<ImageBackground
ref={imageRef}
source={{uri: imageURL}}
style={styles.imageBackground}>
</ImageBackground>
import {captureRef} from 'react-native-view-shot';
export default function useScreenshot({viewRef}) {
const captureScreenshot = async () => {
if (!viewRef.current) {
console.warn('viewRef.current is null. Skipping screenshot capture.');
return null;
}
try {
const uri = await captureRef(viewRef.current, {
format: 'png',
quality: 1,
});
console.log(`Screenshot captured: ${uri}`);
return uri;
} catch (error) {
console.error('Failed to capture screenshot:', error);
return null;
}
};
just try to go with png format for both ios and android it will work <ViewShot captureMode="mount" options={{ fileName: 'file-name', format:"png", quality: 0.9, }} ref={ref}> <View style={[styles.shadow,{ width: 180, height: 50,
alignItems: 'center',
}]}>
<Text style={styles.tittle}>{signatoryName}</Text>
</View>
</ViewShot>
In my case, The boxShadow on the view (or child of the view) I was capturing was causing this issue on Android. With the elevation set to any non-zero value, captureRef was capturing a blank image, probably because underneath there some issue while capturing components having box shadow or something.
I solved this by creating a state like:
const [capturingScreenshot, setCapturingScreenshot] = useState(false);
Now when you capture the view shot, set to true, then back to false once finished:
const handleDownloadInvoice = async () => {
setCapturingScreenshot(true); // <-- HERE
setTimeout(async () => {
const localImageUri = await captureViewShot(contentRef, 'png');
setCapturingScreenshot(false); // <-- HERE
CameraRoll.save(localImageUri)
.then(res => {
if (res) {
showToast('Invoice saved!');
}
})
.catch(err => {
showToast('Could not download invoice.');
console.log('SAVING TO CAMERAROLL: ', err);
});
}, 500);
};
And adding condition while adding the boxShadow on Android using elevation:
<View style={[ styles.receiptBackground, { elevation: capturingScreenshot ? 0 : 20 } ]}>