skip to Main Content

I encountered a strange problem since iOS 15: I have a Blur Effect on the App's Root View, which changes depending on the scenePhase.
This was working perfectly until iOS 15 got released. Now, whenever the Blur Effect is 0, the Status Bar of the App collapses and the Navigation Bar moves up and is no more interactable.

struct RootView: View {
    @Environment(.scenePhase) var scenePhase
    @State private var blurRadius: CGFloat = 0

    var body: some View {
        Group {
            OtherViews()
        }
        .blur(radius: blurRadius)
        .onChange(of: scenePhase) { newValue in updateBlurRadius(newValue) }
    }

     private func updateBlurRadius(_ scenePhase: ScenePhase) {
         switch scenePhase {
             case .active : withAnimation { blurRadius = 0 }
             case .inactive: withAnimation { blurRadius = 16 }
             case .background: withAnimation { blurRadius = 16 }
             @unknown default: print("Unknown Case")
        }
    }
}

This code worked fine for iOS 14 and before. However, since iOS 15, the following bug appears:

Bug Preview

  • The curious thing is, that when the scenePhase becomes inactive, the Navigation Bar instantly jumps into its proper spot. And as soon as the scenePhase becomes active again, it jumps back to the top behind the Status Bar.
  • Also, when changing the Blur Radius for the active scenePhase to 0.001 instead of 0, everything works perfectly fine and the Navigation Bar does not jump behind the Status Bar.

Does anyone have an idea what could cause this strange behavior when working with Blur Effects?

Thanks a lot for your help in advance.

2

Answers


  1. I had this exact issue and was not able to find a fix, so I am now using this alternative implementation, which does pretty much the same thing:

    ZStack {
        // View to be blurred goes here
        Rectangle()
            .ignoresSafeArea()
            .foregroundStyle(.ultraThinMaterial)
            .opacity(/* Your condition */ ? 1 : 0)
            .animation(.easeInOut(duration: 0.2))
    }
    

    This will overlay a blurry rectangle over your view.
    So in your case:

    struct RootView: View {
        @Environment(.scenePhase) var scenePhase
    
        var body: some View {
            ZStack {
                OtherViews()
                Rectangle()
                    .ignoresSafeArea()
                    .foregroundStyle(.ultraThinMaterial)
                    .opacity(scenePhase != .active ? 1 : 0)
                    .animation(.easeInOut)
            }
        }
    }
    

    Because this solution uses the new materials it only works on iOS 15. You can use if #available(iOS 15, *) to provide two different implementations, on for iOS 15+ and one for iOS 14 and earlier.

    Login or Signup to reply.
  2. Although I’m not confident that this solution is markedly better than others, I’ll suggest it for consideration due to its simplicity.

    Previous

    .blur(someCondition ? 2.0 : 0.0)
    

    Hacky "fix"

    .blur(someCondition ? 2.0 : 0.0001)
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search