Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,12 @@ class ReactNativeBlurView : BlurViewGroup {

if (isBlurInitialized) return

// Defer the blur root swap to next frame so the view tree is fully mounted
val runnable = Runnable {
initRunnable = null
if (isBlurInitialized) return@Runnable
swapBlurRootToScreenAncestor()
initializeBlur()
}
initRunnable = runnable
post(runnable)
// Immediately try to swap blur root and initialize.
// We avoid posting a runnable to prevent the 1-second delay artifact.
// If the parent hierarchy is not ready yet (unlikely in onAttachedToWindow),
// we could fall back to post, but for now we prioritize immediate execution.
swapBlurRootToScreenAncestor()
initializeBlur()
}

/**
Expand Down
3 changes: 2 additions & 1 deletion example/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"scheme": "exampleapp",
"userInterfaceStyle": "automatic",
"ios": {
"icon": "./assets/expo.icon"
"icon": "./assets/expo.icon",
"bundleIdentifier": "com.sbaiahmed1.reactnativeblurexampleapp"
},
"android": {
"adaptiveIcon": {
Expand Down
3 changes: 2 additions & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"scripts": {
"start": "expo start",
"android": "expo run:android",
"prebuild:ios": "expo prebuild --platform ios --clean",
"ios": "expo run:ios",
"web": "expo start --web",
"lint": "expo lint",
Expand Down Expand Up @@ -47,4 +48,4 @@
"react-native-monorepo-config": "*",
"typescript": "~5.9.2"
}
}
}
25 changes: 22 additions & 3 deletions ios/Views/AdvancedBlurView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,14 @@ import UIKit
hosting.view.backgroundColor = .clear
hosting.view.translatesAutoresizingMaskIntoConstraints = false

addSubview(hosting.view)
// Insert at index 0 to ensure it stays behind any potential subviews (though usually this view has no children)
// This fixes the z-ordering bug where blur covers content
if !subviews.isEmpty {
insertSubview(hosting.view, at: 0)
} else {
addSubview(hosting.view)
}

NSLayoutConstraint.activate([
hosting.view.topAnchor.constraint(equalTo: topAnchor),
hosting.view.leadingAnchor.constraint(equalTo: leadingAnchor),
Expand All @@ -80,8 +87,20 @@ import UIKit
}

private func updateView() {
if hostingController != nil {
setupHostingController()
if let hosting = hostingController {
// Update the existing controller's root view to avoid expensive recreation
// This fixes performance bottlenecks and state synchronization issues
let blurStyle = blurStyleFromString(blurTypeString)
let swiftUIView = BasicColoredView(
blurAmount: blurAmount,
blurStyle: blurStyle,
ignoreSafeArea: ignoreSafeArea,
reducedTransparencyFallbackColor: reducedTransparencyFallbackColor
)
hosting.rootView = swiftUIView
hosting.view.setNeedsLayout()
} else {
setupHostingController()
Comment on lines +102 to +103
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep the initial hosting-controller creation gated on non-zero bounds.

layoutSubviews() explicitly defers setupHostingController() until the view has a real frame, but this new else path bypasses that guard. Any prop update before first layout will now recreate the original “initialize too early” behavior the file is trying to avoid.

Suggested fix
   private func updateView() {
     if let hosting = hostingController {
         // Update the existing controller's root view to avoid expensive recreation
         // This fixes performance bottlenecks and state synchronization issues
         let blurStyle = blurStyleFromString(blurTypeString)
         let swiftUIView = BasicColoredView(
           blurAmount: blurAmount,
           blurStyle: blurStyle,
           ignoreSafeArea: ignoreSafeArea,
           reducedTransparencyFallbackColor: reducedTransparencyFallbackColor
         )
         hosting.rootView = swiftUIView
         hosting.view.setNeedsLayout()
     } else {
-        setupHostingController()
+        guard bounds.width > 0 && bounds.height > 0 else { return }
+        setupHostingController()
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/Views/AdvancedBlurView.swift` around lines 102 - 103, The new else path
calls setupHostingController() too early and bypasses the existing
layoutSubviews() guard; instead of calling setupHostingController()
unconditionally in that branch, only initialize the hosting controller when the
view has non-zero bounds (e.g. check bounds.isEmpty or width/height > 0) or
defer by returning and letting layoutSubviews() call setupHostingController();
update the branch around setupHostingController() so it mirrors the
layoutSubviews() gating to avoid initializing before first layout.

}
}

Expand Down
20 changes: 14 additions & 6 deletions ios/Views/VibrancyEffectView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,24 @@ import UIKit
let blurEffect = UIBlurEffect(style: style)
let vibrancyEffect = UIVibrancyEffect(blurEffect: blurEffect)

// Use animator to control blur intensity
blurAnimator = UIViewPropertyAnimator(duration: 1, curve: .linear)
blurAnimator?.addAnimations { [weak self] in
self?.blurEffectView.effect = blurEffect
self?.vibrancyEffectView.effect = vibrancyEffect
// Set effects directly first to ensure they are visible
// Animating them from nil often causes issues with UIVibrancyEffect
blurEffectView.effect = blurEffect
vibrancyEffectView.effect = vibrancyEffect

// Create animator to adjust intensity
blurAnimator = UIViewPropertyAnimator(duration: 1, curve: .linear) { [weak self] in
self?.blurEffectView.effect = nil
self?.vibrancyEffectView.effect = nil
}

// Convert blurAmount (0-100) to intensity (0.0-1.0)
// We reverse the logic:
// fractionComplete = 0.0 -> effects are fully applied (start state)
// fractionComplete = 1.0 -> effects are removed (end state)
// So to get desired intensity X, we set fractionComplete to (1 - X)
let intensity = min(max(blurAmount / 100.0, 0.0), 1.0)
blurAnimator?.fractionComplete = intensity
blurAnimator?.fractionComplete = 1.0 - intensity

// Stop the animation at the current state
DispatchQueue.main.async { [weak self, weak blurAnimator] in
Expand Down
Loading