swiftui transition between two view - swiftui

I'm implementing a transition between this two view the login view and home view so when I press the login button the var vm.currentUserSignedIn (saved in app storage in the view model) goes to true and I expecting to make a transition on leave of the login view but I don't understand why I don't see it.
Here's the code:
struct LoginView: View {
#State private var emailTextField: String = ""
#State private var passwordTextField: String = ""
#StateObject var vm: LoginViewModel = LoginViewModel()
var body: some View {
ZStack {
ZStack{
NavigationView{
HomeView(vmLogin: vm)
}
ZStack{
if !vm.currentUserSignedIn {
loginView
.transition(.move(edge: .leading))
}
if vm.isLoading {
Loading()
}
}
.zIndex(2.0)
}
}
}
}

Try with animation, like
ZStack{
if !vm.currentUserSignedIn {
loginView
.transition(.move(edge: .leading))
}
if vm.isLoading {
Loading()
}
}
.zIndex(2.0)
.animation(.default, value: vm.currentUserSignedIn) // << here !!

Related

How to get NavigationStack to retain NavigationPath state when switching views?

I modified an app to use the new NavigationSplitView and NavigationStack, but I can't figure out how to have the NavigationPath retain the state when it's not the active view.
Below is some sample code. I run it on an iPad in landscape mode, or on a Mac (Designed for iPad). It starts with View1 selected and displays View1 in the details. I then tap on SubView and it pushes to SubView. If I then tap on View2, View2 is displayed in the details. If I tap on View1 again, View1 is back at it's root, and is no longer pushed to the SubView. How can I fix this so that when I go back to View1 it is still pushed to SubView?
import SwiftUI
struct ViewType: Identifiable, Hashable {
let id: String
}
private var viewTypes = [
ViewType(id: "View1"),
ViewType(id: "View2"),
]
struct ContentView: View {
#StateObject private var navigationModel = NavigationModel()
#State var selection: Set<String> = [viewTypes[0].id]
var body: some View {
NavigationSplitView {
List(viewTypes, selection: $selection) { viewType in
Text("\(viewType.id)")
}
} detail: {
switch selection.first ?? "Unknown" {
case "View1":
View1()
case "View2":
View2()
default:
Text("Unknown")
}
}
.navigationTitle(selection.first ?? "Unknown")
.environmentObject(navigationModel)
}
}
struct View1: View {
#EnvironmentObject var navigationModel: NavigationModel
var body: some View {
NavigationStack(path: $navigationModel.path) {
Text("View1")
NavigationLink("SubView", value: "SubView")
.navigationDestination(for: String.self) { name in
Text(name)
.onAppear {
print((navigationModel.path.count))
}
}
}
}
}
struct View2: View {
var body: some View {
NavigationStack() {
Text("View2")
}
}
}
final class NavigationModel: ObservableObject {
#Published var path = NavigationPath() {
didSet {
print("path.count: \(path.count)")
}
}
}

Swiftui how to update variable once view is closed

I am using SwiftUi 3.0 and I am new to it . I am learning about ObservedObjects . What I am trying to do is update the count of a variable every time that I close a view . This is the entire small app . The screen starts at DataUpdateView view when I click Next View I go to DataUpdateView2 view . Once I close DataUpdateView2 and go back to the original view I want to have the
Text("Score Count \(progress.score)")
score number increase by 1 since in the second view I do a +1 every time that I close that view . Any suggestions would be great
import SwiftUI
class UserProgress: ObservableObject {
#Published var score = 0
}
struct DataUpdateView: View {
#State var nextView = false
#StateObject var progress = UserProgress()
var body: some View {
VStack {
Text("Score Count \(progress.score)")
Text("Next View")
.onTapGesture {
nextView = true
}.fullScreenCover(isPresented: $nextView, content: {
DataUpdateView2()
})
}
}
}
struct DataUpdateView_Previews: PreviewProvider {
static var previews: some View {
DataUpdateView()
}
}
struct DataUpdateView2: View {
#ObservedObject var progress = UserProgress()
#Environment(\.presentationMode) var presentationMode
var body: some View {
Text("Back")
.onTapGesture {
progress.score += 1
self.presentationMode.wrappedValue.dismiss()
}
}
}
struct DataUpdateView2_Previews: PreviewProvider {
static var previews: some View {
DataUpdateView2()
}
}
You're probably not seeing the first view update since both views are instantiating their own UserProgress(). You need to pass the object you already created in the first view along to the second in the initializer
So In DataUpdateView:
.fullScreenCover(isPresented: $nextView, content: {
DataUpdateView2(progress: progress)
})
}
And then in DataUpdateView2:
struct DataUpdateView2: View {
#ObservedObject var progress: UserProgress
#Environment(\.presentationMode) var presentationMode
// ...
}
So now the second view is receiving the object from the first rather than creating its own.
Note: If you are not using an ObservableObject, then take a look at the second part.
In this specific situation, you don't even need a Binding variable, you can just use the .onDisappear method. .onDisappear Documentation.
import SwiftUI
class UserProgress: ObservableObject {
#Published var score = 0
}
struct DataUpdateView: View {
#State var nextView = false
#StateObject var progress = UserProgress()
var body: some View {
VStack {
Text("Score Count \(progress.score)")
Text("Next View")
.onTapGesture {
nextView = true
}.fullScreenCover(isPresented: $nextView, content: {
DataUpdateView2()
})
}
}
}
struct DataUpdateView_Previews: PreviewProvider {
static var previews: some View {
DataUpdateView()
}
}
struct DataUpdateView2: View {
#ObservedObject var progress = UserProgress()
#Environment(\.presentationMode) var presentationMode
var body: some View {
Text("Back")
.onTapGesture{
presentationMode.wrappedValue.dismiss()
print("Dismissed!")
}
.onDisappear{
//This is called when the view disappears.
progress.score += 1
}
}
}
struct DataUpdateView2_Previews: PreviewProvider {
static var previews: some View {
DataUpdateView2()
}
}
Second Part
If you want the variable to update when the view closes, you could use the .onDisappear method and a Binding value. An example implementation of this is below:
struct ViewOne: View{
#State var number = 0
var body: some View{
VStack{
Text("Number: \(number)")
NavigationLink(destination: ViewTwo(variable: $number)){
Text("Go To View Two")
}
}
}
}
struct ViewTwo: View{
#Binding var variable: Int
var body: some View{
//Content of view 2 here
Text("View Two")
.onDisappear{
//This is called when the view disappears
variable += 1
}
}
}
In short you need to use same view model in both views. A possible and seems simplest approach in your code is to inject view model from first view to second via environment object, like
#StateObject var progress = UserProgress()
var body: some View {
VStack {
Text("Score Count \(progress.score)")
Text("Next View")
.onTapGesture {
nextView = true
}.fullScreenCover(isPresented: $nextView, content: {
DataUpdateView2()
.environmentObject(progress) // << here !!
})
and use it internally, like
struct DataUpdateView2: View {
#EnvironmentObject var progress: UserProgress // << injected automatically !!

SwiftUI incorrect navigation behavior in iOS 14.2

The view navigation hierarchy of my code is as follows:
ColorsView
WarmColorsView
RedView
CoolColorsView
Which is ColorsView can navigate directly to WarmColorsView and CoolColorView, and WarmColorsView can navigate directly to RedView.
Here is code (very simple):
import SwiftUI
class Model: ObservableObject {
#Published var tagNavToWarmOrCool: String?
#Published var isNavToRed = false
}
struct RedView: View {
#EnvironmentObject var model: Model
var body: some View {
VStack {
Button("to cool colors"){
model.isNavToRed = false
model.tagNavToWarmOrCool = "cool"
}
}
}
}
struct CoolColorsView: View {
var body: some View {
VStack {
}
.navigationTitle("Cool Colors")
}
}
struct WarmColorsView: View {
#EnvironmentObject var model: Model
var body: some View {
NavigationLink("red", destination: RedView(), isActive: $model.isNavToRed)
.navigationTitle("Warm Colors")
}
}
struct ColorsView: View {
#EnvironmentObject var model: Model
var body: some View {
VStack {
NavigationLink("to warm colors", destination: WarmColorsView(), tag: "warm", selection: $model.tagNavToWarmOrCool)
NavigationLink("to cool colors", destination: CoolColorsView(), tag: "cool", selection: $model.tagNavToWarmOrCool)
}
.navigationTitle("Colors")
}
}
struct ContentView: View {
#StateObject var model = Model()
var body: some View {
NavigationView {
ColorsView()
}
.navigationViewStyle(StackNavigationViewStyle())
.environmentObject(model)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(Model())
}
}
My intention is to go to the RedView, and then click the button to navigate to the CoolColorsView.
Running in iOS 14.2, however it ends up navigating to the ColorsView, I tried to change NavigationView's style to default, but it didn't work.
There is no such problem in iOS 15.4.1!
So how to navigate from RedView to CoolColorsView by click button in RedView in iOS 14.2? Thanks a lot! :)

How to link my Login screen to my Home screen SwiftUI

I am stuck on linking my login screen to my Main screen. Both have been created separately and I have used the Button function to create the login button and it goes to another screen with the email I have logged in with but that's not what I want as I want my home screen to open up instead.
Button(action: model.login) {
Text("LOGIN")
.fontWeight(.bold)
.foregroundColor(Color("Color1"))
.padding(.vertical)
.frame(width: UIScreen.main.bounds.width - 30)
.background(Color.white)
.clipShape(Capsule())
}
.padding(.top, 22)
My MainView code starting
struct MainView: View {
#State private var isShowing = false
var body: some View {
NavigationView {
ZStack {
In your App struct add a State var isLoggedin passing it to the loginView and toggling it from there.
This is how I implemented.
struct TestApp: App {
#State var isLoggedin: Bool = false
var body: some Scene {
WindowGroup {
if isLoggedin {
ContentView()
} else {
LoginView(isLoggedin: $isLoggedin)
}
}
}
}
struct LoginView: View {
#Binding var isLoggedin: Bool
var body: some View {
Button(action: {
isLoggedin = true
}, label: {
Text("Login")
})
}
}
This way you can pick which view to show. ContentView to LoginView.

NavigationView usage in swiftUI

Coming from Android and working on a very complex application , i would like to use NavigationView as much as possible. Having one view and make all elements appear and disappear on this view seems impossible to handle for me .
I was using navigationView to navigate bewteen views with navigationBar hidden .
This way navigating or making view appear is transparent for the user
After some tests , i encounter limitations : at the 13th or 14 th level of navigation everything disappear and app basically crashes .
Once more , this is a direct navigation between 2 content views , no HOMESCREEN
import SwiftUI
struct test4: View {
#State private var intent3: Bool = false
var body: some View {
NavigationView{
VStack{
NavigationLink(destination : test3() , isActive : $intent3) { }
Text("ver 4")
.onTapGesture {
intent3 = true }
Spacer()
}
}
.navigationBarHidden(true)
}
}
import SwiftUI
struct test3: View {
#State private var intent4: Bool = false
var body: some View {
NavigationView{
VStack{
NavigationLink(destination : test4() , isActive : $intent4) { }
Text("ver 3")
.onTapGesture {
intent4 = true }
Spacer()
}
}.navigationBarHidden(true) }
}
Here a basic example of navigation directly between 2 contents views . Crashes after 14/15 clicks. I encounter the same issue with about any navigation link.
Update:
With your added code, I can see the initial crash was a result of adding a new NavigationView each time. This solves it:
struct ContentView: View {
var body: some View {
NavigationView {
Test3()
}
}
}
struct Test4: View {
#State private var intent3: Bool = false
var body: some View {
VStack{
NavigationLink(destination : Test3() , isActive : $intent3) { }
Text("ver 4")
.onTapGesture {
intent3 = true
}
Spacer()
}
.navigationBarHidden(true)
}
}
struct Test3: View {
#State private var intent4: Bool = false
var body: some View {
VStack{
NavigationLink(destination : Test4() , isActive : $intent4) { }
Text("ver 3")
.onTapGesture {
intent4 = true }
Spacer()
}
.navigationBarHidden(true)
}
}
Original answer:
However, there are solutions to pop to the top of a navigation hierarchy.
One way is to use isActive to manage whether or not a given NavigationLink is presenting its view. That might look like this:
class NavigationReset : ObservableObject {
#Published var rootIsActive = false
func popToTop() {
rootIsActive = false
}
}
struct ContentView: View {
#StateObject private var navReset = NavigationReset()
var body: some View {
NavigationView {
NavigationLink(destination: DetailView(title: "First"), isActive: $navReset.rootIsActive) {
Text("Root nav")
}
}.environmentObject(navReset)
}
}
struct DetailView : View {
var title : String
#EnvironmentObject private var navReset : NavigationReset
var body: some View {
VStack {
NavigationLink(destination: DetailView(title: "\(Date())")) {
Text("Navigate (\(title))")
}
Button("Reset nav") {
navReset.popToTop()
}
}
}
}
Another trick you could use is changing an id on a NavigationLink -- as soon as that happens, it re-renders and becomes inactive.
class NavigationReset : ObservableObject {
#Published var id = UUID()
func popToTop() {
id = UUID()
}
}
struct ContentView: View {
#StateObject private var navReset = NavigationReset()
var body: some View {
NavigationView {
NavigationLink(destination: DetailView(title: "First")) {
Text("Root nav")
}
.id(navReset.id)
}.environmentObject(navReset)
}
}
struct DetailView : View {
var title : String
#EnvironmentObject private var navReset : NavigationReset
var body: some View {
VStack {
NavigationLink(destination: DetailView(title: "\(Date())")) {
Text("Navigate (\(title))")
}
Button("Reset nav") {
navReset.popToTop()
}
}
}
}
It works by marking the first NavigationLink (ie the one on the Home Screen) with an id. As soon as that id is changed, the NavigationLink is recreated, popping all of the views off of the stack.