Skip to content

BlurView fails to render on first mount when using absoluteFillObject with dynamic content in FlashList #83

@rhnmht30

Description

@rhnmht30

Description

BlurView does not render the blur effect on first mount when:

  1. Using StyleSheet.absoluteFillObject (no explicit dimensions)
  2. Parent dimensions are determined by dynamic content (e.g., Text)
  3. Rendered inside a FlashList (or FlatList with aggressive recycling)

After scrolling away and back (triggering view recycling), the blur renders correctly.

Environment

  • @sbaiahmed1/react-native-blur: 4.2.2
  • React Native: 0.79.2
  • Expo SDK: 54
  • iOS: 18.x
  • Device: iPhone 15 Pro (Simulator and physical)

Reproduction

import { BlurView } from '@sbaiahmed1/react-native-blur';
import { FlashList } from '@shopify/flash-list';
import { StyleSheet, Text, View } from 'react-native';

const PLACEHOLDER_TEXT = "This content is exclusive to pass holders...";

function LockedPostCard() {
  return (
    <View style={{ position: 'relative', marginTop: 12 }}>
      {/* Dynamic text determines parent height */}
      <Text numberOfLines={4}>{PLACEHOLDER_TEXT}</Text>

      {/* BlurView with absoluteFillObject - FAILS on first render */}
      <BlurView
        blurType="light"
        blurAmount={8}
        style={StyleSheet.absoluteFillObject}
      />
    </View>
  );
}

// Rendered in FlashList
<FlashList
  data={posts}
  renderItem={({ item }) => <LockedPostCard post={item} />}
/>

Expected Behavior

Blur should render immediately on first mount.

Actual Behavior

  • First render: No blur visible (content behind is fully visible)
  • After scrolling away and back: Blur renders correctly

Root Cause Analysis

After investigating the iOS native code, the issue appears to be when the blur effect is initialized relative to the view lifecycle.

@sbaiahmed1/react-native-blur Implementation

File: ios/Views/AdvancedBlurView.swift

@objc public class AdvancedBlurView: UIView {
  public override init(frame: CGRect) {
    super.init(frame: frame)
    setupHostingController()  // ← Blur set up immediately in init()
  }

  public override func didMoveToSuperview() {
    super.didMoveToSuperview()
    if superview != nil {
      setupHostingController()  // ← Called again, but still too early
    }
  }
}

The SwiftUI hosting controller (and blur effect) is set up in init()before the view is added to the hierarchy and before sibling views have rendered.

When using absoluteFillObject, the BlurView relies on parent dimensions. If the parent's dimensions come from dynamic content (Text layout), those dimensions may not be finalized when the blur captures its initial state.

Comparison with expo-blur

File: expo-blur/ios/BlurEffectView.swift

final class BlurEffectView: UIVisualEffectView {
  override func draw(_ rect: CGRect) {  // ← Blur applied in draw()
    super.draw(rect)
    effect = nil
    animator = UIViewPropertyAnimator(duration: 1, curve: .linear) { [unowned self] in
      self.effect = visualEffect
    }
    animator?.fractionComplete = CGFloat(intensity)
  }
}

expo-blur applies the blur effect in draw(_:), which is part of UIKit's rendering cycle. This method is called by the system only when the view is ready to be displayed — meaning layout is complete and sibling views have rendered.

Timeline Comparison

┌─────────────────────────────────────────────────────────────────────────┐
│                    @sbaiahmed1/react-native-blur                        │
├─────────────────────────────────────────────────────────────────────────┤
│  init()           → setupHostingController() called                     │
│  addSubview()     → View added to hierarchy                             │
│  layoutSubviews() → Layout calculated                                   │
│  [Sibling content renders]                                              │
│  ❌ Blur already initialized with incorrect/zero dimensions             │
└─────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────┐
│                           expo-blur                                     │
├─────────────────────────────────────────────────────────────────────────┤
│  init()           → View created (no blur yet)                          │
│  addSubview()     → View added to hierarchy                             │
│  layoutSubviews() → Layout calculated                                   │
│  [Sibling content renders]                                              │
│  draw(_:)         → Blur applied NOW                                    │
│  ✅ Layout complete, dimensions known, blur renders correctly           │
└─────────────────────────────────────────────────────────────────────────┘

Why It Works in Other Cases

The issue does not occur when:

Scenario Why it works
Explicit width/height provided Dimensions known at init time
Blurring static bundled images Image already loaded, dimensions fixed
Not in FlashList Less aggressive timing, more layout cycles

Workarounds

1. Delay rendering (not ideal - causes flash of unblurred content)

const [blurReady, setBlurReady] = useState(false);
useEffect(() => {
  const timer = setTimeout(() => setBlurReady(true), 100);
  return () => clearTimeout(timer);
}, []);

{blurReady && <BlurView ... />}

2. Use explicit dimensions (not always possible with dynamic content)

<BlurView
  style={{
    position: 'absolute',
    width: 300,
    height: 100,
  }}
/>

Suggested Fix

Consider deferring the blur effect setup to draw(_:) or layoutSubviews() (after calling super.layoutSubviews()) to ensure parent dimensions and sibling content are finalized before capturing the blur.

Alternatively, the UIViewRepresentable implementation could use updateUIView more effectively to re-apply the blur after the initial layout pass.

Additional Context

  • This issue is reproducible in both Simulator and physical devices
  • The 100ms delay workaround confirms this is a timing issue

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingiosiOS only

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions