What exactly is it you're trying to do? If you want to replace what's on screen entirely, then you can ultimately do that from the SceneDelegate, just swapping out the UIHostingController containing the ContentView with one containing your LoginView.
Alternatively (and possibly a bit more SwiftUI-ish) you could have a special 'switcher' view that exists only to return either the ContentView or the LoginView, based on some value. That value can be an @State variable, and you can drop a binding to it into the environment, allowing others to modify it from inside button actions and the like. You could use an @EnvironmentObject, but that requires a class, etc., so unless you have something like that floating around already, it seems like a plain @Environment item would work.
You'd have to define your own EnvironmentKey and extend EnvironmentValues to provide a key-path though. Here's a very simple example, which hasn't been tested at all:
enum RootViewOption: Equatable {
case content
case login
}
struct RootViewOptionEnvironmentKey: EnvironmentKey {
typealias Value = Binding
static var defaultValue: Binding = .constant(.content)
}
extension EnvironmentValues {
var rootView: RootViewOption {
get { self[RootViewOptionEnvironmentKey.self] }
set { self[RootViewOptionEnvironmentKey.self] = newValue }
}
}
struct SwitchRootView: View {
@State var whichView: RootViewOption = .content
var body: some View {
if whichView == .content {
ContentView()
} else {
LoginView()
}
}
}
struct MySubView: View {
@Environment(\.rootView) var rootView
var body: some View {
VStack(spacing: 20) {
Text("Press button to show login screen")
Button(action: { rootView = .login }, content: { Text("Press Me") })
}
}
}