Skip to content

Commit eef5827

Browse files
fix: Respect RN view layout and transforms within ForeignObject (#2811)
# Summary Fixes: #2733 (`Bug1`, `Bug3`) Fixes: #1428 Fixes: #2519 ### Problem When a React Native view (e.g., `<View>`, `<Text>`) is nested inside an SVG `<ForeignObject>`, its own style properties like transform (scale, rotate, translate) and its layout position are ignored during rendering. This causes the nested view to be rendered incorrectly at the (0, 0) origin of the` <ForeignObject>` without any of its applied transformations, making it impossible to style or position elements within the foreign object container. ### Solution This PR fixes the issue by manually applying the child view's own position and transformation matrix to the drawing context (the `Canvas` on Android and `CGContext` on iOS) before the view is drawn. On iOS we also reset `hidden` property to `false` for nested RN components inside `ForeignObject` within `prepareForRecycle`. This prevents views from being pooled in an invisible state and then disappearing when they are subsequently reused on different screens. With changes: <img width="797" height="291" alt="image" src="https://github.com/user-attachments/assets/1faf0645-7ee0-432b-99e9-28129ba13c1c" /> Before changes: <img width="793" height="304" alt="image" src="https://github.com/user-attachments/assets/c2ffce3d-ca05-4e76-baa6-75765a754958" /> ## Test Plan Run test case from issue: #2733
1 parent 0e08a39 commit eef5827

File tree

2 files changed

+43
-2
lines changed

2 files changed

+43
-2
lines changed

android/src/main/java/com/horcrux/svg/ForeignObjectView.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import android.annotation.SuppressLint;
1212
import android.graphics.Bitmap;
1313
import android.graphics.Canvas;
14+
import android.graphics.Matrix;
1415
import android.graphics.Paint;
1516
import android.graphics.RectF;
1617
import android.view.View;
@@ -110,7 +111,19 @@ void drawGroup(final Canvas canvas, final Paint paint, final float opacity) {
110111
}
111112
} else {
112113
// Enable rendering other native ancestor views in e.g. masks
114+
final int saveCount = canvas.save();
115+
116+
int left = child.getLeft();
117+
int top = child.getTop();
118+
canvas.translate(left, top);
119+
120+
Matrix childMatrix = child.getMatrix();
121+
if (!childMatrix.isIdentity()) {
122+
canvas.concat(childMatrix);
123+
}
124+
113125
child.draw(canvas);
126+
canvas.restoreToCount(saveCount);
114127
}
115128
}
116129
this.setClientRect(groupRect);

apple/Elements/RNSVGForeignObject.mm

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,28 @@ - (void)renderGroupTo:(CGContextRef)context rect:(CGRect)rect
139139
CGContextClipToRect(context, svgViewRect);
140140
[svgView drawToContext:context withRect:svgViewRect];
141141
} else {
142-
node.hidden = false;
142+
CGContextSaveGState(context);
143+
144+
CGRect bounds = node.layer.bounds;
145+
CGPoint position = node.layer.position;
146+
CATransform3D transform = node.layer.transform;
147+
148+
CGContextTranslateCTM(context, position.x, position.y);
149+
150+
if (!CATransform3DIsIdentity(transform)) {
151+
CGAffineTransform affine = CATransform3DGetAffineTransform(transform);
152+
CGContextConcatCTM(context, affine);
153+
}
154+
155+
// This moves the origin from that center point to the object's top-left corner,
156+
// which is where drawing operations will begin.
157+
CGContextTranslateCTM(context, -bounds.size.width / 2, -bounds.size.height / 2);
158+
159+
node.hidden = NO;
143160
[node.layer renderInContext:context];
144-
node.hidden = true;
161+
node.hidden = YES;
162+
163+
CGContextRestoreGState(context);
145164
}
146165

147166
return YES;
@@ -173,6 +192,15 @@ - (void)renderGroupTo:(CGContextRef)context rect:(CGRect)rect
173192
[self popGlyphContext];
174193
}
175194

195+
#ifdef RCT_NEW_ARCH_ENABLED
196+
- (void) willRemoveSubview:(RNSVGPlatformView *) subview
197+
{
198+
if ([subview isKindOfClass:[RCTViewComponentView class]]) {
199+
subview.hidden = NO;
200+
}
201+
}
202+
#endif
203+
176204
- (void)drawRect:(CGRect)rect
177205
{
178206
[self invalidate];

0 commit comments

Comments
 (0)