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)
}
}
CodePudding user response:
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 theLoadingView
can be conditionally displayed and not displayed all the time and just default to anEmptyView
- 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()
}
}
}