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)
}
}
}
}
