0

A similar question is asked here: SwiftUI: Global Overlay That Can Be Triggered From Any View. The examples however are shown a Toast, rather than a 'blocking' view. The accepted answer changes the presenting view (the toolbar is moving up after the toast is shown). Something is wrong with the code, but I don't know what (I just started learning SwiftUI). The code below is a combined effort from some other answers.

Alright, ideally I want to call a function on e.g. an EnvironmentObject which will automatically present an overlaying View with a ProgressView, to show that something is being loaded. The user should not be able to interact with the application while it is loading. The loading screen should be a bit blurry, overlapping with the presenting view.

Below shows what I have, the Text is never shown, but my breakpoint is hit when I click on the Load button. Any ideas why the LoadingView is never shown?

import SwiftUI
import UIKit

@main
struct TextLeadingApp: App {
    var body: some Scene {
        WindowGroup {
            ZStack {
                LoadingView() // This view should always be hidden, unless it is loading
                ContentView()
            }
            .environmentObject(Load())
        }
    }
}

class Load: ObservableObject {
    @Published var loader = 0

    func load() {
        loader += 1
    }
}

struct LoadingView: View {
    @EnvironmentObject var load: Load

    var body: some View {
        if load.loader > 0 {
            GeometryReader { geometry in
                Text("LOADING")
                    .frame(
                        width: geometry.size.width,
                        height: geometry.size.height
                    )
                    .ignoresSafeArea(.all, edges: .all)
            }
        } else {
            EmptyView().frame(width: 0, height: 0)
        }
    }
}

struct ContentView: View {
    @EnvironmentObject var load: Load

    var body: some View {
        NavigationView {
            Text("hi")
                .toolbar {
                    Button("Load") {
                        load.load()
                    }
                }

                .navigationBarTitle(Text("A List"), displayMode: .large)
        }
        .navigationViewStyle(.stack)
    }
}
J. Doe
  • 12,159
  • 9
  • 60
  • 114

1 Answers1

1

There are a couple of things that could be adjusted with your code.

  • I'd make Load a @StateObject on a parent view so that the LoadingView can be conditionally displayed and not displayed all the time and just default to an EmptyView
  • In a ZStack, the topmost view should be last -- you have it first.
  • You can use .background(.ultraThinMaterial)
@main
struct TextLeadingApp: App {
    var body: some Scene {
        WindowGroup {
            ParentView()
        }
    }
}

struct ParentView : View {
    @StateObject private var load = Load()
    
    var body: some View {
        ZStack {
            ContentView()
            if load.loader > 0 {
                LoadingView()
            }
        }.environmentObject(load)
    }
}

class Load: ObservableObject {
    @Published var loader = 0

    func load() {
        loader += 1
    }
}

struct LoadingView: View {
    @EnvironmentObject var load: Load

    var body: some View {
        VStack {
            Text("LOADING")
        }
        .ignoresSafeArea()
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(.ultraThinMaterial)
    }
}

struct ContentView: View {
    @EnvironmentObject var load: Load

    var body: some View {
        NavigationView {
            Text("hi")
                .onTapGesture {
                    print("tapped")
                }
                .font(.system(size: 100, weight: .bold, design: .default))
                .foregroundColor(.orange)
                .toolbar {
                    Button("Load") {
                        load.load()
                    }
                }
                .navigationBarTitle(Text("A List"), displayMode: .large)
        }
        .navigationViewStyle(.stack)
    }
}

I've adjusted ContentView a bit, just to make the blur effect more obvious.


Update, with OP's request that only LoadingView responds to a change in the state, and not the parent view:

@main
struct TextLeadingApp: App {
    var body: some Scene {
        WindowGroup {
            ZStack {
                ContentView()
                LoadingView()
            }
            .environmentObject(Load())
        }
    }
}


class Load: ObservableObject {
    @Published var loader = 0
    
    func load() {
        loader += 1
    }
}

struct LoadingView: View {
    @EnvironmentObject var load: Load
    
    var body: some View {
        if load.loader > 0 {
            VStack {
                Text("LOADING")
            }
            .ignoresSafeArea()
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(.ultraThinMaterial)
        } else {
            EmptyView()
        }
    }
}
jnpdx
  • 45,847
  • 6
  • 64
  • 94
  • Thanks, it looks like it working, but the ParentView's body is recalculated every time the `load` method is called. That is the reason why I only did state changes in the LoadingView itself. Will this way not 'forget' state across Views since everything is recalculated? – J. Doe Feb 09 '22 at 18:25
  • That's not how state works in SwiftUI -- state is not "forgotten" when a view is recalculated. In fact, the opposite is true -- state is retained across updates. The view tree is diffed across updates and updates are only applied where necessary. If parameters/state haven't changed, you won't see them recalculated (although their inits, which should be *extremely* light weight are called). – jnpdx Feb 09 '22 at 18:27
  • I can rewrite using your original approach of using an `EmptyView`, but I don't think you'll see any tangible benefit from it. You may want to check out https://developer.apple.com/videos/play/wwdc2021/10022/ – jnpdx Feb 09 '22 at 18:28
  • Thanks, I will check out that video. I am still curious if it is possible while only refreshing `LoadingView` and leave the other views for what it is. Is that possible? – J. Doe Feb 09 '22 at 18:32
  • Yes, it's possible. See my update. – jnpdx Feb 09 '22 at 18:36
  • Thanks for our help and edit, this was what I was looking for! – J. Doe Feb 09 '22 at 19:12