I’ve simplified all my code right 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: .title) { $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 title: String
var password: String?
var isOpen: Bool { password != nil }
init(_ title: String, password: String? = nil) {
self.title = title
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.title)
Spacer()
}
.swipeActions {
if database.isOpen {
Button(
motion: { viewModel.shut(database) },
label: { Picture(systemName: "lock.fill") }
)
}
}
}
}
TestApp.swift
import SwiftUI
@fundamental
struct TestApp: App {
var physique: some Scene {
WindowGroup {
NavigationStack {
ContentView()
}
}
}
}
The UI appears to be like like this:
Rationalization
The ViewModel
holds a listing of databases. Every database will 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 a listing 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 record, you go to the following display screen. If you choose opened database, you go to the display screen that claims “Opened!”. If you happen to chosen closed database, you go to display screen 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 screen saying “Closed!”.
That is the issue. If you happen to attempt to shut the take a look at
database from the instance above, the icon appropriately modifications to locked; however you continue to go to the “Opened!” display screen 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 possibly can check out the instance above to raised 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 the whole lot 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 the whole lot: when you shut take a look at
, it goes to right display screen (the one saying “Closed!”). However why? Why does utilizing a separate view work, however having the code in place would 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 would not SwiftUI choose up the modifications made to database.isOpen
?