Navigate to INCR: 3 and tap either the navigation bar back button or the dismiss button and you'll notice that same view is called again but this time it's a new version because the onAppear
firstLoad = true
and rand
is a different value.
If you comment out @Environment(\.dismiss) var dismiss
and dismiss()
everything works as expected as it did in iOS 14. This issue also occurs with @Environment(\.presentationMode) var presentationMode
Not sure if this is a bug or if I'm making a silly mistake, but this issue is causing a ton of problems for my app because I have to be able to programmatically dismiss a view, so any input would be appreciated.
struct DetailView: View {
@Environment(\.dismiss) var dismiss
@State var isPresenting = false
@State var incrInt = 0
@State var firstLoad = true
@State var rand = Int.random(in: 1..<500)
var body: some View {
Text("INCR: \(incrInt) RAND: \(rand)")
Button("NAVIGATE"){
isPresenting = true
}
Button("DISMISS"){
dismiss()
}
.onAppear(perform: {
if firstLoad{
print("ON APPEAR FIRST LOAD")
print(incrInt)
print(rand)
print("\n")
firstLoad = false
}
})
NavigationLink(destination: DetailView(incrInt: (incrInt 1)), isActive: $isPresenting){}
}
}
struct ContentView: View {
var body: some View {
NavigationView{
VStack{
DetailView()
}
}
}
}
Video Link
https://i.imgur.com/qpu7NT7.mp4
Update Single Source of Truth
class DetailViewModel: ObservableObject {
@Published var isPresenting = false
var incr: Int
var rand = Int.random(in: 1..<500)
init(incr: Int){
self.incr = incr
print("INIT FIRST LOAD")
print(incr)
print(rand)
print("\n")
}
}
struct DetailView: View {
@Environment(\.dismiss) var dismiss
@StateObject var detailViewModel: DetailViewModel
var body: some View {
Text("INCR: \(detailViewModel.incr) RAND: \(detailViewModel.rand)")
Button("NAVIGATE"){
detailViewModel.isPresenting = true
}
Button("DISMISS"){
dismiss()
}
NavigationLink(destination: DetailView(detailViewModel: DetailViewModel(incr: (detailViewModel.incr 1))), isActive: $detailViewModel.isPresenting){}
}
}
struct ContentView: View {
var body: some View {
NavigationView{
VStack{
DetailView(detailViewModel: DetailViewModel(incr: 0))
}
}
}
}
CodePudding user response:
In your "Update" code, you are not using a single source of truth.
You are creating and passing a new DetailViewModel
into DetailView
every time you click on the NavigationLink
.
Use only 1 DetailViewModel, and pass it around. In addition, you are changing
isPresenting
, so all your views that rely on this will be updated with the "new" value. This
cascading is not what you want. Modify your logic. Using DetailViewModel
is a good idea to keep the state of your model across views. Try something like this:
class DetailViewModel: ObservableObject {
// @Published var isPresenting = false // <-- not relevant
var incr: Int
var rand = Int.random(in: 1..<500)
init(incr: Int) {
self.incr = incr
print("----> DetailViewModel init --> inc: \(incr) --> rand: \(rand) \n")
}
func doIncr(_ incr: Int) {
self.incr = incr
print("----> DetailViewModel doIncr --> inc: \(incr) --> rand: \(rand) \n")
}
}
struct DetailView: View {
@Environment(\.dismiss) var dismiss
@ObservedObject var detailViewModel: DetailViewModel
@State var showThyself = false // <--- here
var body: some View {
Text("DetailView INCR: \(detailViewModel.incr) RAND: \(detailViewModel.rand)")
Button("NAVIGATE"){
detailViewModel.doIncr(detailViewModel.incr 1)
showThyself = true
}
Button("DISMISS"){
dismiss()
}
NavigationLink(destination: DetailView(detailViewModel: detailViewModel), isActive: $showThyself){}
.onAppear {
// do something with the current state of your DetailViewModel
print("----> DetailView onAppear \n")
}
}
}
struct ContentView: View {
var detailViewModel = DetailViewModel(incr: 0) // <--- here
var body: some View {
NavigationView{
VStack{
DetailView(detailViewModel: detailViewModel)
}
}
}
}