react-native-smooth-clip-view 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.1](https://github.com/kbrattli/react-native-smooth-clip-view/releases/tag/v0.5.1) — 2026-09-28
4
+
5
+ - **Fix: iOS baked shadows were invisible.** `shadowRendering="baked"` baked
6
+ its tile by rendering a shadowPath-only layer through `renderInContext:`,
7
+ which draws a layer's shadow only where the layer has content of its own,
8
+ so every tile was fully transparent and a baked host drew no shadow at all.
9
+ The tile is now drawn as a Core Graphics shadow inside a UIKit image
10
+ renderer, whose base transform also scales the blur with the tile's scale
11
+ (a hand-scaled bitmap context left the shadow in pixel space). A Core
12
+ Graphics shadow of `blur` has the same sigma as Core Animation's
13
+ `shadowRadius = blur / 2`, so the tile matches the blur path's shadow
14
+ sample for sample; the iOS and Android suites now pin the tile's alpha
15
+ profile. Android tiles were unaffected.
16
+
3
17
  ## [0.5.0](https://github.com/kbrattli/react-native-smooth-clip-view/releases/tag/v0.5.0) — 2026-09-28
4
18
 
5
19
  - **Backdrop channel.** A presentation gains `backdrop: { translateX,
@@ -445,9 +445,16 @@ static CGFloat SmoothClipBakedShadowMargin(const smoothclip::Shadow &shadow) {
445
445
  }
446
446
 
447
447
  // Baked once per (radius step, curve, blur, colour, scale) and cached for the
448
- // process. Rendered through Core Animation's own shadow path so the tile is
449
- // the pure blurred shape the compositor draws for a `shadowPath` layer, with
450
- // nothing filled in on top of it; colour alpha is applied as layer opacity.
448
+ // process. The tile is the pure blurred shape the compositor draws for a
449
+ // `shadowPath` layer, with nothing filled in on top of it; colour alpha is
450
+ // applied as layer opacity. It is drawn with a Core Graphics shadow, not by
451
+ // rendering a layer: `renderInContext:` draws a layer's shadow only where the
452
+ // layer has content of its own, so a shadowPath-only layer renders nothing.
453
+ // A Core Graphics shadow of `b` has sigma b / 2, the same sigma Core
454
+ // Animation gives `shadowRadius` b / 2 (the blur path's setting), so the two
455
+ // tails match sample for sample. The context comes from a UIKit image
456
+ // renderer, whose base transform scales the blur with the tile's scale; a
457
+ // hand-scaled CGBitmapContext leaves the shadow in pixel space.
451
458
  static NSCache<NSString *, SmoothClipShadowTile *> *SmoothClipShadowTileCache(void) {
452
459
  static NSCache<NSString *, SmoothClipShadowTile *> *cache;
453
460
  static dispatch_once_t once;
@@ -523,41 +530,44 @@ static SmoothClipShadowTile *SmoothClipBakeShadowTile(
523
530
  const CGFloat margin = SmoothClipBakedShadowMargin(shadow);
524
531
 
525
532
  const CGFloat side = 2 * (2 * margin + tileRadius) + kSmoothClipShadowTileBand;
526
- const size_t pixels = (size_t)MAX(1, ceil(side * scale));
527
- CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
528
- CGContextRef context = CGBitmapContextCreate(
529
- NULL, pixels, pixels, 8, 0, space,
530
- kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
531
- CGColorSpaceRelease(space);
532
- if (context == NULL) return nil;
533
- CGContextScaleCTM(context, scale, scale);
534
- CALayer *layer = [CALayer layer];
535
- layer.frame = CGRectMake(0, 0, side, side);
536
- const CGRect shape = CGRectInset(layer.bounds, margin, margin);
533
+ const CGRect shape = CGRectInset(CGRectMake(0, 0, side, side), margin, margin);
537
534
  const SmoothClipCornerRadii radii = {
538
535
  tileRadius, tileRadius, tileRadius, tileRadius};
539
536
  CGPathRef path = SmoothClipCreateRoundedRectPath(shape, radii, curve);
540
- layer.shadowPath = path;
537
+ CGColorRef color = [UIColor colorWithRed:smoothclip::clamp01(shadow.red)
538
+ green:smoothclip::clamp01(shadow.green)
539
+ blue:smoothclip::clamp01(shadow.blue)
540
+ alpha:1].CGColor;
541
+ const CGFloat blur = MAX(0, shadow.blurRadius);
542
+ UIGraphicsImageRendererFormat *format =
543
+ [UIGraphicsImageRendererFormat defaultFormat];
544
+ format.scale = scale;
545
+ format.opaque = NO;
546
+ format.preferredRange = UIGraphicsImageRendererFormatRangeStandard;
547
+ UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc]
548
+ initWithSize:CGSizeMake(side, side) format:format];
549
+ UIImage *rendered = [renderer imageWithActions:^(
550
+ UIGraphicsImageRendererContext *rendererContext) {
551
+ CGContextRef context = rendererContext.CGContext;
552
+ // Only the shadow lands on the tile: the shape itself is filled far
553
+ // outside the bitmap and its shadow offset back by the same distance,
554
+ // so the interior keeps the blurred shape's own profile (0.5 at the
555
+ // edge) instead of a hard fill.
556
+ const CGFloat away = 4 * side;
557
+ CGContextSetShadowWithColor(context, CGSizeMake(0, -away), blur, color);
558
+ CGContextTranslateCTM(context, 0, away);
559
+ CGContextAddPath(context, path);
560
+ CGContextSetFillColorWithColor(context, color);
561
+ CGContextFillPath(context);
562
+ }];
541
563
  CGPathRelease(path);
542
- layer.shadowColor = [UIColor colorWithRed:smoothclip::clamp01(shadow.red)
543
- green:smoothclip::clamp01(shadow.green)
544
- blue:smoothclip::clamp01(shadow.blue)
545
- alpha:1].CGColor;
546
- layer.shadowOpacity = 1;
547
- layer.shadowOffset = CGSizeZero;
548
- layer.shadowRadius = MAX(0, shadow.blurRadius) / 2.0;
549
- // The bitmap's y axis is flipped relative to the layer; the tile is
550
- // symmetric (uniform radii, no offset), so that is harmless.
551
- [layer renderInContext:context];
552
- CGImageRef image = CGBitmapContextCreateImage(context);
553
- CGContextRelease(context);
564
+ CGImageRef image = rendered.CGImage;
554
565
  if (image == NULL) return nil;
555
566
  tile = [[SmoothClipShadowTile alloc] initWithImage:image
556
567
  radius:tileRadius
557
568
  margin:margin
558
569
  side:side
559
570
  scale:scale];
560
- CGImageRelease(image);
561
571
  [SmoothClipShadowTileCache() setObject:tile forKey:key];
562
572
  return tile;
563
573
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-smooth-clip-view",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Layout-free animated rounded clipping for React Native Fabric",
5
5
  "main": "./lib/module/index.js",
6
6
  "module": "./lib/module/index.js",