gre / react-native-view-shot

Snapshot a React Native view and save it to an image
https://github.com/gre/react-native-view-shot-example
MIT License
2.63k stars 343 forks source link

View nested inside Camera component doesn't show up in view shot #492

Open JordanHe opened 12 months ago

JordanHe commented 12 months ago

I want to put some overlays ontop of my camera view so that when the shot is taken by view shot it will be a picture with the overlays ontop. My issue is that when I put my component nested inside the component it doesn't show up in the result of the view-shot.

If I just do the Overlay then it works but once I nest it inside the Camera component ( using expo-camera import { Camera, CameraType } from "expo-camera"; ) only the camera shows up in the view-shot when I take it.

I have tried adding collapsable={false} to all my views inside the component but that doesn't fix it. Is there something I am doing wrong?

import { Camera, CameraType } from "expo-camera";
import { useEffect, useRef, useState } from "react";
import {
  Button,
  Dimensions,
  Platform,
  StyleSheet,
  Text,
  TouchableOpacity,
  View,
  Image,
} from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { Stack, useRouter } from "expo-router";
import Overlay from "./components/overlay";

import ViewShot from "react-native-view-shot";
  const MyComponent: React.FC = () => {
  const viewShotRef = useRef<ViewShot | null>(null);
  const [screenshotUri, setScreenshotUri] = useState<string>("");

  const handleRetake = () => {
    setScreenshotUri("");
  };

  const handleCapture = async () => {
    console.log("viewShotRef.current=", viewShotRef.current);
    if (viewShotRef.current) {
      try {
        const uri = await viewShotRef.current.capture!();
        console.log("Captured and do something with ", uri);
        setScreenshotUri(uri);
      } catch (error) {
        console.error("Capture failed:", error);
      }
    }
  };

  const [type, setType] = useState(CameraType.front);
  const [permission, requestPermission] = Camera.useCameraPermissions();

  const [camera, setCamera] = useState<Camera | null>(null);

  const toggleCameraType = () => {
    setType((current) =>
      current === CameraType.back ? CameraType.front : CameraType.back,
    );
  };

  const [hasCameraPermission, setHasCameraPermission] = useState<boolean>();
  // Screen Ratio and image padding
  const [imagePadding, setImagePadding] = useState(0);
  const [ratio, setRatio] = useState("4:3"); // default is 4:3
  const { height, width } = Dimensions.get("window");
  const screenRatio = height / width;
  const [isRatioSet, setIsRatioSet] = useState(false);

  // on screen  load, ask for permission to use the camera
  useEffect(() => {
    const getCameraStatus = async () => {
      const request = await requestPermission();
      setHasCameraPermission(request.granted);
    };
    getCameraStatus();
  }, []);

  const prepareRatio = async () => {
    let desiredRatio = "4:3";
    if (Platform.OS === "android" && camera) {
      const ratios = await camera.getSupportedRatiosAsync();

      // .....  ratio calculation
      setImagePadding(remainder);
      setRatio(desiredRatio);
      setIsRatioSet(true);
    }
  };

  const setCameraReady = async () => {
    if (!isRatioSet) {
      await prepareRatio();
    }
  };

  if (hasCameraPermission === null) {
    return (
      <View className="flex-1 items-center justify-center self-center">
        <Text>Waiting for camera permissions</Text>
      </View>
    );
  } else if (hasCameraPermission === false) {
    return (
      <View className="flex-1 items-center justify-center self-center">
        <Text>No access to camera</Text>
      </View>
    );
  } else {
    return (
      <SafeAreaView className="flex-1">
        <Stack.Screen options={{ title: "Live" }} />
        <View className="flex-1 justify-center bg-neutral-800">
          {/* 
        We created a Camera height by adding margins to the top and bottom, 
        but we could set the width/height instead 
        since we know the screen dimensions
        */}
          {screenshotUri == "" ? (
            <ViewShot ref={viewShotRef} style={{ flex: 1 }}>
              <Camera
                style={{
                  flex: 1,
                  //   marginTop: imagePadding,
                  marginBottom: imagePadding,
                }}
                onCameraReady={setCameraReady}
                ratio={ratio}
                type={type}
                ref={(ref) => {
                  setCamera(ref);
                }}
              >
              <Overlay
                toggleCameraType={toggleCameraType}
                handleCapture={handleCapture}
                distance={1.32}
                duration={4.55}
              />
              </Camera>
            </ViewShot>
          ) : (
            <View>
              <TouchableOpacity
                onPress={handleRetake}
                className="absolute left-2 top-2 z-10"
              >
                <Text>Retake</Text>
              </TouchableOpacity>
              <Image
                source={{ uri: screenshotUri }}
                style={{
                  height: undefined,
                  width: "100%",
                  aspectRatio: 9 / 16,
                  alignSelf: "center",
                }}
              />
            </View>
          )}
        </View>
      </SafeAreaView>
    );
  }

Version & Platform

using react native expo (managed workflow, not expo go) version number of react-native-view-shot is "react-native-view-shot": "3.5.0",

**Platform: Android

component below

import { TouchableOpacity, View, Text, Image } from "react-native";
import ff from "../../utils/globalStyles";
import run from "../../workout/run";
import { Ionicons } from "@expo/vector-icons";
import { Entypo } from "@expo/vector-icons";
import { useRouter } from "expo-router";

type OverlayProps = {
  toggleCameraType: () => void;
  handleCapture: () => Promise<void>;
  distance: number;
  duration: number;
};

const Overlay: React.FC<OverlayProps> = ({
  toggleCameraType,
  handleCapture,
  distance,
  duration,
}) => {
  const router = useRouter();

  return (
    <View className="z-10 h-full justify-between" collapsable={false}>
      <View className="flex-row justify-between px-2 pt-4" collapsable={false}>
        <TouchableOpacity onPress={() => router.back()}>
          <Entypo name="cross" size={36} color="white" />
        </TouchableOpacity>
        <Image
          source={require("../../workout/img/myImage.png")}
          style={{
            height: undefined,
            width: 140,
            aspectRatio: 2.7,
            alignSelf: "center",
          }}
        />
        <View className="w-9" />
      </View>

      <View className="flex-row justify-between px-10 pb-4" collapsable={false}>
        <View className="w-12" />
        <TouchableOpacity onPress={handleCapture}>
          <View
            className="h-20 w-20 justify-center rounded-full bg-white"
            style={{ opacity: 0.5 }}
          >
            <View className="h-16 w-16 self-center rounded-full bg-white"></View>
          </View>
        </TouchableOpacity>
        <TouchableOpacity onPress={toggleCameraType} className="justify-center">
          <Ionicons name="ios-camera-reverse-outline" size={48} color="white" />
        </TouchableOpacity>
      </View>
    </View>
  );
};

export default Overlay;
aindong commented 8 months ago

Hi @JordanHe were you able to solve this?

tructtyoong commented 7 months ago

same issue