Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Picture not rerendering on iOS #2732

Closed
mbpictures opened this issue Nov 12, 2024 · 10 comments · Fixed by #2793
Closed

Picture not rerendering on iOS #2732

mbpictures opened this issue Nov 12, 2024 · 10 comments · Fixed by #2793
Labels

Comments

@mbpictures
Copy link

Hi!

I'm creating a drawing-like component using skia and gesture handler. My code looks like this (simplified):

const DrawBoard = forwardRef<DrawBoardType, Props>(({style}, ref) => {
    const path = useSharedValue<Element>({path: Skia.Path.Make(), paint: Skia.Paint(), type: "draw"});
    const lastTouch = useSharedValue<{x: number; y: number}>({x: 0, y: 0});
    const {toolbar} = useToolbar();
    const currentPicture = useSharedValue(emptyPicture);

    const panGesture = Gesture.Pan()
        .maxPointers(1)
        .minDistance(0)
        .averageTouches(true)
        .enabled(toolbar.activeTool !== Tools.None)
        .onStart(e => {
            const newPath = Skia.Path.Make();
            newPath.moveTo(e.x, e.y);
            if (toolbar.activeTool === Tools.Pencil || toolbar.activeTool === Tools.Eraser) {
                newPath.lineTo(e.x, e.y);
            }
            const newPaint = Skia.Paint();
            newPaint.setStyle(PaintStyle.Stroke);
            newPaint.setColor(Skia.Color(toolbar.color));
            newPaint.setStrokeWidth(toolbar.strokeWidth);
            if (toolbar.activeTool === Tools.Eraser) {
                newPaint.setBlendMode(BlendMode.Clear);
            } else {
                newPaint.setBlendMode(BlendMode.Src);
            }
            path.value = {
                path: newPath,
                paint: newPaint,
                type: toolbar.activeTool === Tools.Eraser ? "erase" : "draw"
            };
            lastTouch.value = {x: e.x, y: e.y};
        })
        .onUpdate(e => {
            if (toolbar.activeTool === Tools.Rectangle) {
                path.value.path.reset();
                path.value.path.addRect({
                    x: lastTouch.value.x,
                    y: lastTouch.value.y,
                    width: e.x - lastTouch.value.x,
                    height: e.y -lastTouch.value.y
                });
            } else if (toolbar.activeTool === Tools.Circle) {
                path.value.path.reset();
                path.value.path.addCircle(lastTouch.value.x, lastTouch.value.y, Math.sqrt(Math.pow(e.x - lastTouch.value.x, 2) + Math.pow(e.y - lastTouch.value.y, 2)));
            } else {
                path.value.path.lineTo(e.x, e.y);
            }
            // update the picture
            currentPicture.value = createPicture(
                canvas => {
                    canvas.drawPath(path.value.path, path.value.paint);
                }
            )
        })
        .onEnd(() => {
            path.value = {path: Skia.Path.Make(), paint: Skia.Paint(), type: "draw"};
        });

    return (
        <View style={[style, {pointerEvents: toolbar.activeTool === Tools.None ? "none" : "auto"}]}>
            <View style={styles.container}>
                <GestureDetector gesture={panGesture}>
                    <Canvas style={styles.canvas}>
                        <Picture picture={currentPicture} />
                    </Canvas>
                </GestureDetector>
            </View>
        </View>
    )
});

This works fine on Android, but on iOS the Picture doesn't rerender. I already added logs inside of the createPicture function and saw, that it is called while painting and also the value of the path looks good, so I have no idea why the picture doesn't rerender.

@wcandillon
Copy link
Contributor

Can you provide a standalone reproducible example? I would be happy to take a look

@mbpictures
Copy link
Author

Repro is available here: https://github.com/mbpictures/react-native-skia-sketch-repro

@mbpictures
Copy link
Author

Any updates?

@wcandillon
Copy link
Contributor

Yes thank you for the reproduction. The layout seems to not be accepted by the canvas on iOS (or at least on iOS simulator with new arch). I was able to rewrite your example to make it work perfectly. You were using lots of layout properties I'm not familar with. I will try to track down what exactly makes the layout fail on iOS. Could you do the same on your side? See if we can track down exactly which property "fails".

This is not related to the pictures, it's just that on iOS the Canvas appear to not have an ok layout (might be related to gesture handler too).

@wcandillon
Copy link
Contributor

This is how you can rewrite you example to work:

        <View style={[style, {pointerEvents: toolbar.activeTool === Tools.None ? 'none' : 'auto'}]}>
            <View style={styles.container}>
                <GestureDetector gesture={panGesture}>
                    <View style={styles.canvas}>
                    <Canvas style={{ flex: 1}}>
                        <Picture picture={allPicture} />
                        <Picture picture={currentPicture} />
                    </Canvas>
                    </View>
                </GestureDetector>
            </View>
        </View>

But still I would like to get a sense of what is going on.

@wcandillon
Copy link
Contributor

I've noticed that on iOS, removing the following: {pointerEvents: toolbar.activeTool === Tools.None ? 'none' : 'auto'} make the example work immediately, no idea why

@mbpictures
Copy link
Author

Thanks for the investigation! Removing {pointerEvents: toolbar.activeTool === Tools.None ? 'none' : 'auto'} didn't work for me. Also interesting: I use the exact same code in my production app, but with additional saving/restoring of the canvas/the drawing. When I close the app and open it again, so the drawings get reloaded, they show up on the screen. That's the reason I thought it is not a layout related bug. However updating the code as you did (giving the canvas the style of flex: 1, instead of using absolut positioning) worked, which is kinda weird for me!

@JonathanLab
Copy link

JonathanLab commented Dec 4, 2024

Having the exact same issue as you, on new architecture. Initially the path will render properly, but might randomly disappear. Only fully closing and opening the app resolves the issue.

  • New architecture
  • @shopify/react-native-skia 1.5.0
  • Expo SDK 52
  • RN 0.76.3
  • iPhone 15 Pro Max simulator (Also able to replicate on iPhone 13 Pro)

@wcandillon
Copy link
Contributor

@mbpictures I was able to reproduce the issue in a minimal example. Hopefully we can find the root cause issue.
@JonathanLab Would you mind share your code with us? That would be interesting data.

Copy link
Contributor

github-actions bot commented Dec 7, 2024

🎉 This issue has been resolved in version 1.7.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants