I’ve simplified all my code all the way down to this, you may run it to check:
ContentView.swift
import SwiftUI
struct ContentView: View {
@StateObject var viewModel = ViewModel()
var physique: some View {
Record {
ForEach($viewModel.databases, id: .identify) { $database in
NavigationLink(
vacation spot: {
VStack {
if database.isOpen {
Textual content("Opened!")
} else {
Textual content("Closed!")
}
}
},
label: {
DatabaseItem( database: $database )
}
)
}
.environmentObject(viewModel)
}
}
}
class Database {
var identify: String
var password: String?
var isOpen: Bool { password != nil }
init(_ identify: String, password: String? = nil) {
self.identify = identify
self.password = password
}
}
class ViewModel: ObservableObject {
@Printed var databases: [Database] = [
Database("main"), Database("test", password: "123")
]
func shut(_ db: Database) {
db.password = nil
}
}
struct DatabaseItem: View {
@Binding var database: Database
@EnvironmentObject var viewModel: ViewModel
var physique: some View {
HStack {
Picture(systemName: database.isOpen ? "lock.open.fill" : "lock.fill")
.body(width: 32, top: 32)
Textual content(database.identify)
Spacer()
}
.swipeActions {
if database.isOpen {
Button(
motion: { viewModel.shut(database) },
label: { Picture(systemName: "lock.fill") }
)
}
}
}
}
TestApp.swift
import SwiftUI
@foremost
struct TestApp: App {
var physique: some Scene {
WindowGroup {
NavigationStack {
ContentView()
}
}
}
}
Clarification
The ViewModel
holds an inventory of databases. Every database may be in 2 states: opened or closed. If password
of a database is nil
– the database is closed, in any other case it is opened (as is represented by the isOpen
computed property).
We draw an inventory of database objects: a reputation of every database, and a lock icon representing whether or not the database is opened or closed.
When you choose a database merchandise from the checklist, you go to the subsequent display. If you choose opened database, you go to the display that claims “Opened!”. In case you chosen closed database, you go to display saying “Closed!”.
Now, opened databases have an motion (you may swipe left on a database merchandise to disclose it) to shut the database. This motion simply units the password of a database to nil
, successfully closing it. As soon as the database is closed, the UI ought to replace: database icon ought to change to locked, and when you now choose the database, it ought to go to the display saying “Closed!”.
That is the issue. In case you attempt to shut the take a look at
database from the instance above, the icon appropriately adjustments to locked; however you continue to go to the “Opened!” display if you choose the take a look at
databse after closing it. So the icon updates appropriately, however the NavigationLink
‘s vacation spot doesn’t.
You may check out the instance above to higher perceive the issue.
The fascinating level is: if I extract the code from NavigationLink
‘s vacation spot right into a separate view, and go the binding to it, the issue can be fastened.
First create a view containing all the pieces we had in NavigationLink
‘s vacation spot argument:
struct Vacation spot: View {
@Binding var database: Database
var physique: some View {
VStack {
if database.isOpen {
Textual content("Opened!")
} else {
Textual content("Closed!")
}
}
}
}
then, use it in NavigationLink
s vacation spot, like this:
NavigationLink(
vacation spot: {
Vacation spot(database: $database)
},
...
This fixes all the pieces: when you shut take a look at
, it goes to right display (the one saying “Closed!”). However why? Why does utilizing a separate view work, however having the code in place does not? I am new to SwiftUI and iOS growth, and I do not perceive this. Should not the vacation spot of NavigationLink
replace if database.isOpen
has modified? Why does not SwiftUI choose up the adjustments made to database.isOpen
?