I'm trying to have a system-wide progress bar in my SwiftUI application, so I defined this view (be aware: I'm targeting iOS 13 ):
import SwiftUI
struct LoadingView<Content>: View where Content: View {
@Binding var isShowing: Bool
var content: () -> Content
var body: some View {
GeometryReader { _ in
ZStack {
self.content()
if self.isShowing {
VStack {
ActivityIndicator()
Text("Loading...")
}
}
}
}
}
}
struct ActivityIndicator: UIViewRepresentable {
typealias UIView = UIActivityIndicatorView
fileprivate var configuration = { (_: UIView) in }
func makeUIView(context: UIViewRepresentableContext<Self>) -> UIView { UIView() }
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<Self>) {
uiView.startAnimating()
configuration(uiView)
}
}
and is used in ContenView.swift like this
import SwiftUI
struct ContentView: View {
@EnvironmentObject var myViewModel: MyViewModel
var body: some View {
let isLoading = Binding<Bool>(
get: { self.myViewModel.isLoading },
set: { _ in }
)
LoadingView(isShowing: isLoading) {
NavigationView {
Home()
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
}
where MyViewModel is a pretty standard ViewModel with a @Published
var for isLoading, and Home()
is the main entry point for the app.
Whenever some action that trigger isLoading
in MyViewModel is done, the progress bar is shown (and hidden) correctly.
But, if I present a .sheet
either by one of the view inside the NavigationView, or using the ContentView itself, no matter what it hides the progress bar.
Even if I use the builtin 14 ProgressView
the problem persists.
.Zindex()
does not help either.
Any way to have that view always on top when showed, no matter what .sheet
, .alert
or any overlay view is available on SwiftUI is present on the screen?
Thanks in advance!
CodePudding user response:
As already written in the comments, a modal view will be shown on top of any other view. A modal view is meant to establish a Computer-Human communication, or dialog (thus modal views frequently will be named "Dialog").
The observation, that a sheet (modal view) covers the loading indicator is expected behaviour.
But, IMO the issue described in the question and refined in the comments, can be solved nicely without breaking the behaviour of the modal views:
When you want to show data, that is not yet complete or even completely absent, you may show a "blank" screen, and in additions to this, let the view model generate a view state that says, that the view should show an "Input Sheet".
So initially, the user sees an input form over a blank screen.
Once the user made the input and submits the form (which will be handled in the View Model) the input sheet disappears (controlled by the View State generated by the View Model), and reveals the "blank" view underneath it.
So, the View Model could now present another sheet, or it realises that the input is complete.
Once the input is complete, the view model loads data and since this may take a while, it reflects this in the View State accordingly, for example using a "loading" state or flag. The view renders this accordingly, which is a loading indicator above the "blank" view.
When the view model receives data, it clears the loading state and sets the view state accordingly, passing through the data as well.
The view now renders the data view.
If the loading task failed, the view model composes a view state where the content is "absent" and with an error info.
Again the view renders this, possibly showing an alert with the message above a "blank" view, since there is still no data.
Ensure, the user can dismiss the error alert and the view model handles it by removing the "modal error" state, but the content is still "absent".
Now, the user is starring at a blank view. You may embed an error message here, or even add a "Retry" button. In any case, ensure the user can navigate away from that screen.
And so on. ;)