Activity Indicator in SwiftUI cannot cover navigation bar

75 Views Asked by At

I have 2 views, lets name it view A and view B. View B will be shown by pushed from view A.

In view B, I want to show Activity Indicator that cover the whole screen of the device. But in my approach, it turns out that it only cover the view B content. The navigation bar is not covered with the Activity Indicator.

I used view modifier to show Activity Indicator. Here's the code:

struct ActivityIndicator: UIViewRepresentable {
    @Binding var isAnimating: Bool
    let style: UIActivityIndicatorView.Style

    func makeUIView(context: UIViewRepresentableContext<ActivityIndicator>) -> UIActivityIndicatorView {
        return UIActivityIndicatorView(style: style)
    }

    func updateUIView(_ uiView: UIActivityIndicatorView, context: UIViewRepresentableContext<ActivityIndicator>) {
        isAnimating ? uiView.startAnimating() : uiView.stopAnimating()
    }
}

struct ActivityIndicatorModifier: AnimatableModifier {
    var isLoading: Bool

    init(isLoading: Bool, color: Color = .primary, lineWidth: CGFloat = 3) {
        self.isLoading = isLoading
    }

    var animatableData: Bool {
        get { isLoading }
        set { isLoading = newValue }
    }

    func body(content: Content) -> some View {
        ZStack {
            if isLoading {
                GeometryReader { geometry in
                    ZStack(alignment: .center) {
                        content
                            .disabled(self.isLoading)
                            .blur(radius: self.isLoading ? 3 : 0)

                        VStack {
                            ActivityIndicator(isAnimating: .constant(true), style: .large)
                        }
                        .frame(width: geometry.size.width / 2,
                               height: geometry.size.height / 5)
                        .background(Color.secondary.colorInvert())
                        .foregroundColor(Color.primary)
                        .cornerRadius(20)
                        .opacity(self.isLoading ? 1 : 0)
                        .position(x: geometry.frame(in: .local).midX, y: geometry.frame(in: .local).midY)
                    }
                }
            } else {
                content
            }
        }
    }
}

And this is the code of view B:

    var body: some View {
        VStack {
            ** View Content **
        }
        .padding(.top)
        .toolbar(content: {
            ToolbarItemGroup(placement: .navigationBarTrailing) {
                Button {

                } label: {
                    Text("Save")
                        .font(.bold(.body)())
                }
                .padding(.trailing)
            }
        })
        .navigationTitle("View B")
        .background(Color.themeColor)
        .modifier(ActivityIndicatorModifier(isLoading: loadingState))
    }

This is the result when Activity Indicator is shown, the 'Back' button is still tappable: ViewB

How to show Activity Indicator in correct way to cover the whole screen including the navigation bar?

Thank you in advance.

2

There are 2 best solutions below

2
MatBuompy On

I think that modifying it like this should solve your problem:

func body(content: Content) -> some View {
    ZStack {
        if isLoading {
            GeometryReader { geometry in
                ZStack(alignment: .center) {
                    content
                        .disabled(self.isLoading)
                        .blur(radius: self.isLoading ? 3 : 0)

                    VStack {
                        ActivityIndicator(isAnimating: .constant(true), style: .large)
                    }
                    .frame(width: geometry.size.width / 2,
                           height: geometry.size.height / 5)
                    .background(Color.secondary.colorInvert()) // <-- Comment this if you want
                    .foregroundColor(Color.primary)
                    .cornerRadius(20)
                    .opacity(self.isLoading ? 1 : 0)
                    .position(x: geometry.frame(in: .local).midX, y: geometry.frame(in: .local).midY)
                    /// Use background here
                    .background(Color.secondary.colorInvert().ignoresSafeArea())
                }
            }
        } else {
            content
        }
    }
}

Comment the first background modifier if you'd like to loose the rounded rectangle arounf the ActivityIndicator. The second one covers the whole screen area. Let me know if that works for you!

0
Jayant Badlani On

It is because you are using the iOS default NavigationView, which appears at the top of all your view content. One way to create a custom NavbarView to gain control over the UI.

Alternatively, this link may help you achieve the same result with the default one by wrapping NavigationView inside a ZStack.

https://stackoverflow.com/a/67530774/22695708