Refresh a SwiftUI View on Back Navigation - swiftui

I have a MainView and DetailView. The MainView displays a list of items. From MainView you can go to DetailView using the push navigation. The DetailView allows to add the item. After adding the new item, I am trying to go back to the MainView and refresh the MainView. It goes back but it never displays the new item unless I restart the app.
I added onAppear on the MainView and I can see it is getting fired. But it still does not update the view.
Here is some code in the MainView:
var body: some View {
List {
ForEach(movieListVM.movies, id: \.id) { movie in
NavigationLink(
destination: AddUpdateMovieScreen(movieId: movie.id),
label: {
MovieCell(movie: movie)
})
}.onDelete(perform: deleteMovie)
}
.listStyle(PlainListStyle())
.navigationTitle("Movies")
.navigationBarItems(trailing: Button("Add Movie") {
isPresented = true
})
.sheet(isPresented: $isPresented, onDismiss: {
movieListVM.populateMovies()
}, content: {
AddUpdateMovieScreen()
})
.onAppear(perform: {
movieListVM.populateMovies()
})
.embedInNavigationView()
Here is the code in the ViewModel:
class MovieListViewModel: ObservableObject {
#Published var movies = [MovieViewModel]()
#Published var updated: Bool = false
func deleteMovie(movie: MovieViewModel) {
let movie = CoreDataManager.shared.getMovieById(id: movie.id)
if let movie = movie {
CoreDataManager.shared.deleteMovie(movie)
}
}
func populateMovies() {
let movies = CoreDataManager.shared.getAllMovies()
for movie in movies {
print(movie.title) // THIS PRINTS THE UPDATE OBJECTS
}
DispatchQueue.main.async {
self.movies = movies.map(MovieViewModel.init) // THIS POPULATES THE movies correctly.
}
}
}
Any ideas why the MainView is not updating, even though I am firing the populateMovies function of the MovieListViewModel.
import SwiftUI
import CoreData
struct AddUpdateMovieScreen: View {
#StateObject private var addMovieVM = AddUpdateMovieViewModel()
#Environment(\.presentationMode) var presentationMode
#State private var movieVS = MovieViewState()
var movieId: NSManagedObjectID?
private func saveOrUpdate() {
do {
if movieId != nil {
// UPDATE IS THE ISSUE I AM TRYING TO RESOLVE
try addMovieVM.update(movieVS)
} else {
addMovieVM.save(movieVS)
}
} catch {
print(error)
}
}
var body: some View {
Form {
TextField("Enter name", text: $movieVS.title)
TextField("Enter director", text: $movieVS.director)
HStack {
Text("Rating")
Spacer()
RatingView(rating: $movieVS.rating)
}
DatePicker("Release Date", selection: $movieVS.releaseDate)
HStack {
Spacer()
Button("Save") {
saveOrUpdate()
presentationMode.wrappedValue.dismiss()
}
Spacer()
}
}
.onAppear(perform: {
// if the movieId is not nil then fetch the movie information
if let movieId = movieId {
// fetch the movie
do {
let movieVM = try addMovieVM.getMovieById(movieId: movieId)
movieVS = MovieViewState.fromMovieViewModel(vm: movieVM)
} catch {
print(error)
}
}
})
.navigationTitle("Add Movie")
.embedInNavigationView()
}
}
struct AddMovieScreen_Previews: PreviewProvider {
static var previews: some View {
AddUpdateMovieScreen()
}
}

Since you don't show your code for "AddUpdateMovieScreen", here are my guesses:
if you are passing "movieListVM" to "AddUpdateMovieScreen" as ObservableObject, then use this:
.sheet(isPresented: $isPresented) {
AddUpdateMovieScreen(movieListVM: movieListVM)
}
and:
struct AddUpdateMovieScreen: View {
#ObservedObject var movieListVM: MovieListViewModel
...
if you are passing "movieListVM" to "AddUpdateMovieScreen" as EnvironmentObject, then use this:
.sheet(isPresented: $isPresented) {
AddUpdateMovieScreen().environment(movieListVM)
}
and:
struct AddUpdateMovieScreen: View {
#EnvironmentObject var movieListVM: MovieListViewModel
...
There is no need for "movieListVM.populateMovies()" in the sheet onDismiss.

Related

Changing a TextField text for items in a foreach NavigationLink SwiftUI and saving it

There is a list in on the Main View that has navigation links that bring you to a an Edit Birthday View where the textFieldName is saved with the onAppear method. I need help in allowing the user to change the text in the text field on the Edit Birthday View and having it save when the user dismisses and returns to that particular item in the foreach list. I have tried onEditingChanged and on change method but they don't seem to work. (Also, in my view model i append birthday items when they are created in the Add Birthday View). If you would like to see more code i will make updates. Thank you.
/// MAIN VIEW
import SwiftUI
struct MainView: View {
#EnvironmentObject var vm: BirthdayViewModel
#State var nameTextField: String = ""
var body: some View {
VStack(spacing: 20) {
List {
ForEach(vm.searchableUsers, id: \.self) { birthday in
NavigationLink(destination: EditBirthdayView(birthday: birthday)) {
BirthdayRowView(birthday: birthday)
}
.listRowSeparator(.hidden)
}
.onDelete(perform: vm.deleteBirthday)
}
}
.toolbar {
ToolbarItem {
NavigationLink(destination: AddBirthdayView(textfieldName: $nameTextField)) {
Image(systemName: "plus.circle")
}
}
}
}
}
/// EDIT BIRTHDAY VIEW
import SwiftUI
import Combine
struct EditBirthdayView: View {
#EnvironmentObject var vm: BirthdayViewModel
#State var textfieldName: String = ""
#Environment(\.presentationMode) var presentationMode
var birthday: BirthdayModel
var body: some View {
NavigationView {
VStack {
TextField("Name...", text: $textfieldName)
}
Button {
saveButtonPressed()
} label: {
Text("Save")
}
}
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now()) {
textfieldName = birthday.name
}
}
}
}
func saveButtonPressed() {
vm.updateItem(birthday: birthday)
presentationMode.wrappedValue.dismiss()
}
func updateTextField() {
textfieldName = birthday.name
}
}
struct MainView: View {
#EnvironmentObject var store: BirthdayStore
var body: some View {
List {
ForEach($store.birthdays) { $birthday in
NavigationLink(destination: EditBirthdayView(birthday: $birthday)) {
BirthdayRowView(birthday: birthday)
}
}
.onDelete(perform: deleteBirthday)
}
.listRowSeparator(.hidden)
.toolbar {
ToolbarItem {
NavigationLink(destination: AddBirthdayView() {
Image(systemName: "plus.circle")
}
}
}
}
}
struct EditBirthdayView: View {
#EnvironmentObject var store: BirthdayStore
#Binding var birthday: Birthday
...
TextField("Name", text: $birthday.name)

How to dismiss a Sheet and open a NavigationLink in a new View?

I have a View with a search button in the toolbar. The search button presents a sheet to the user and when he clicks on a result I would like the sheet to be dismissed and a detailView to be opened rather than navigating to the detailView from inside the sheet. The dismiss part is easy, but how do I open the detailView in the NavigationStack relative to the original View that presented the Sheet?
I'm also getting an error on the navigationStack initialization.
HomeScreen:
struct CatCategoriesView: View {
#StateObject private var vm = CatCategoriesViewModel(service: Webservice())
#State var showingSearchView = false
#State var path: [CatDetailView] = []
var body: some View {
NavigationStack(path: $path) { <<-- Error here "No exact matches in call to initializer "
ZStack {
Theme.backgroundColor
.ignoresSafeArea()
ScrollView {
switch vm.state {
case .success(let cats):
LazyVStack {
ForEach(cats, id: \.id) { cat in
NavigationLink {
CatDetailView(cat: cat)
} label: {
CatCategoryCardView(cat: cat)
.padding()
}
}
}
case .loading:
ProgressView()
default:
EmptyView()
}
}
}
.navigationTitle("CatPedia")
.toolbar {
Button {
showingSearchView = true
} label: {
Label("Search", systemImage: "magnifyingglass")
}
}
}
.task {
await vm.getCatCategories()
}
.alert("Error", isPresented: $vm.hasError, presenting: vm.state) { detail in
Button("Retry") {
Task {
await vm.getCatCategories()
}
}
} message: { detail in
if case let .failed(error) = detail {
Text(error.localizedDescription)
}
}
.sheet(isPresented: $showingSearchView) {
SearchView(vm: vm, path: $path)
}
}
}
SearchView:
struct SearchView: View {
let vm: CatCategoriesViewModel
#Environment(\.dismiss) private var dismiss
#Binding var path: [CatDetailView]
#State private var searchText = ""
var body: some View {
NavigationStack {
List {
ForEach(vm.filteredCats, id: \.id) { cat in
Button(cat.name) {
dismiss()
path.append(CatDetailView(cat: cat))
}
}
}
.navigationTitle("Search")
.searchable(text: $searchText, prompt: "Find a cat..")
.onChange(of: searchText, perform: vm.search)
}
}
}
It can be a little tricky, but I'd suggest using a combination of Apple's documentation on "Control a presentation link programmatically" and shared state. To achieve the shared state, I passed a shared view model into the sheet.
I have simplified your example to get it working in a more generic way. Hope this will work for you!
ExampleParentView.swift
import SwiftUI
struct ExampleParentView: View {
#StateObject var viewModel = ExampleViewModel()
var body: some View {
NavigationStack(path: $viewModel.targetDestination) {
List {
NavigationLink("Destination A", value: TargetDestination.DestinationA)
NavigationLink("Destination B", value: TargetDestination.DestinationB)
}
.navigationDestination(for: TargetDestination.self) { target in
switch target {
case .DestinationA:
DestinationA()
case .DestinationB:
DestinationB()
}
}
.navigationTitle("Destinations")
Button(action: {
viewModel.showModal = true
}) {
Text("Click to open sheet")
}
}
.sheet(isPresented: $viewModel.showModal, content: {
ExampleSheetView(viewModel: viewModel)
.interactiveDismissDisabled()
})
}
}
ExampleViewModel.swift
import Foundation
import SwiftUI
class ExampleViewModel: ObservableObject {
#Published var showModal = false
#Published var targetDestination: [TargetDestination] = []
}
enum TargetDestination {
case DestinationA
case DestinationB
}
ExampleSheetView.swift
import SwiftUI
struct ExampleSheetView: View {
let viewModel: ExampleViewModel
var body: some View {
VStack {
Text("I am the sheet")
Button(action: {
viewModel.showModal = false
viewModel.targetDestination.append(.DestinationA)
}) {
Text("Close the sheet and navigate to `A`")
}
Button(action: {
viewModel.showModal = false
viewModel.targetDestination.append(.DestinationB)
}) {
Text("Close the sheet and navigate to `B`")
}
}
}
}
DestinationA.swift
import SwiftUI
struct DestinationA: View {
var body: some View {
Text("Destination A")
}
}
DestinationB.swift
import SwiftUI
struct DestinationB: View {
var body: some View {
Text("Destination B")
}
}

How to pop to specific view in the TabView Application in swiftui. I used StackNavigation also but not working in swiftui

I am facing an issue while popping to a specific view. Let me explain the hierarchy.
ContentView -> 2 tabs, TabAView and TabBView
Inside TabBView. There is 1 view used ConnectView: Where is a Button to connect. After tapping on the button of Connect, the user move to another View which is called as UserAppView. From Here User can check his profile and update also. After the Update API call, need to pop to UserAppView from UserFirstFormView.
Here is the code to understand better my problem.
ContentView.swift
struct ContentView: View {
enum AppPage: Int {
case TabA=0, TabB=1
}
#StateObject var settings = Settings()
#ObservedObject var userViewModel: UserViewModel
var body: some View {
NavigationView {
TabView(selection: $settings.tabItem) {
TabAView(userViewModel: userViewModel)
.tabItem {
Text("TabA")
}
.tag(AppPage.TabA)
TabBView(userViewModel: userViewModel)
.tabItem {
Text("Apps")
}
.tag(AppPage.TabB)
}
.accentColor(.white)
.edgesIgnoringSafeArea(.top)
.onAppear(perform: {
settings.tabItem = .TabA
})
.navigationBarTitleDisplayMode(.inline)
}
.environmentObject(settings)
}
}
This is TabAView:
struct TabAView: View {
#ObservedObject var userViewModel: UserViewModel
#EnvironmentObject var settings: Settings
init(userViewModel: UserViewModel) {
self.userViewModel = userViewModel
}
var body: some View {
Vstack {
/// code
}
.onAppear(perform: {
/// code
})
.environmentObject(settings)
}
}
This is another TabBView:
struct TabBView: View {
#ObservedObject var userViewModel: UserViewModel
init(userViewModel: UserViewModel) {
self.userViewModel = userViewModel
}
var body: some View {
VStack (spacing: 10) {
NavigationLink(destination: ConnectView(viewModel: ConnectViewModel(id: id!), userViewModel: userViewModel)) {
UserCardWidget()
}
}
}
}
There is 1 connectView used on the TabBView through which the user will connect. ConnectViewModel is used here to call connect API.
class ConnectViewModel: ObservableObject {
var id: String?
init(id: String) {
self.id = id
}
func connect(completion: #escaping () -> Void) {
APIService.shared.connectApp(id: self.id!) { connected in
DispatchQueue.main.async {
self.isConnected = connected ?? false
completion()
}
}
}
}
This is connect view
struct ConnectView: View {
#ObservedObject var connectViewModel: ConnectViewModel
#ObservedObject var userViewModel: UserViewModel
#State var buttonTitle = "CONNECT WITH THIS"
#State var isShowingDetailView = false
var body: some View {
VStack {
Spacer()
if let id = connectViewModel.id {
NavigationLink(destination: UserAppView(id: id, userViewModel: userViewModel), isActive: $isShowingDetailView) {
Button(buttonTitle, action: {
connectViewModel.connect {
buttonTitle = "CONNECTED"
isShowingDetailView = true
}
})
}
}
}
}
}
This is the UserAppViewModel where API is hit to fetch some user-related details:
class UserAppViewModel: ObservableObject {
var id = ""
func getdetails() {
APIService.shared.getDetails() { userDetails in
DispatchQueue.main.async {
/// code
}
}
}
}
This is UserAppView class
struct UserAppView: View {
#ObservedObject var userViewModel: UserViewModel
#State private var signUpInButtonClicked: Bool = false
#StateObject private var userAppViewModel = UserAppViewModel()
init(id: String, userViewModel: UserViewModel) {
self.id = id
self.userViewModel = userViewModel
}
var body: some View {
VStack {
Text(userAppViewModel.status)
VStack {
Spacer()
NavigationLink(
destination: ProfileView(userAppViewModel: userAppViewModel, isActive: $signUpInButtonClicked)) { EmptyView() }
if /// Condition {
Button(action: {
signUpInButtonClicked = true
}, label: {
ZStack {
/// code
}
.frame(maxWidth: 77, maxHeight: 25)
})
}
}.onAppear(perform: {
**userAppViewModel.getDetails**(id: id)
})
}
}
From Here, the User Can Navigate to ProfileView.
struct ProfileUpdateView: View {
#State private var navigationSelectionFirstFormView = false
#State private var navigationSelectionLastFormView = false
public var body: some View {
VStack {
NavigationLink(destination: UserFirstFormView(userAppViewModel: userAppViewModel), isActive: $navigationSelectionFirstFormView) {
EmptyView()
}
NavigationLink(destination: UserLastFormView(userAppViewModel: userAppViewModel), isActive: $navigationSelectionLastFormView) {
EmptyView()
}
}
.navigationBarItems(trailing: Button(action: {
if Condition {
navigationSelectionFirstFormView = true
} else {
navigationSelectionLastFormView = true
}
}, label: {
HStack {
Text("Action")
.foregroundColor(Color.blue)
}
})
)
}
}
Further, their user will move to the next screen to update the profile.
struct UserFirstFormView: View {
var body: some View {
VStack {
/// code
///
Button("buttonTitle", action: {
API Call completion: { status in
if status {
self.rootPresentationMode.wrappedValue.dismiss()
}
})
})
.frame(maxHeight: 45)
}
}
}
I am trying to pop from this view once the API response is received but nothing is working.
I have removed most of the code from a confidential point of view but this code will explain the reason and error. Please look into the code and help me.
You could use the navigation link with, tag: and selection: overload and let the viewmodel control what link is open, here a example
enum profileViews {
case view1
case view2}
in your viewModel add an published var that will hold the active view
#Published var activeView: profileViews?
then in your navigation link you can do it like this
NavigationLink(
destination: secondView(profileViewModel: ProfileViewModel ),
tag: profileViews.view1,
selection: self.$profileViewModel.activeView
){}
Then you could pop any view just updating the variable inside the view model
self.profileViewModel.activeView = nil

Published/Observed var not updating in view swiftui w/ called function

Struggling to get a simple example up and running in swiftui:
Load default list view (working)
click button that launches picker/filtering options (working)
select options, then click button to dismiss and call function with selected options (call is working)
display new list of objects returned from call (not working)
I'm stuck on #4 where the returned query isn't making it to the view. I suspect I'm creating a different instance when making the call in step #3 but it's not making sense to me where/how/why that matters.
I tried to simplify the code some, but it's still a bit, sorry for that.
Appreciate any help!
Main View with HStack and button to filter with:
import SwiftUI
import FirebaseFirestore
struct TestView: View {
#ObservedObject var query = Query()
#State var showMonPicker = false
#State var monFilter = "filter"
var body: some View {
VStack {
HStack(alignment: .center) {
Text("Monday")
Spacer()
Button(action: {
self.showMonPicker.toggle()
}, label: {
Text("\(monFilter)")
})
}
.padding()
ScrollView(.horizontal) {
LazyHStack(spacing: 35) {
ForEach(query.queriedList) { menuItems in
MenuItemView(menuItem: menuItems)
}
}
}
}
.sheet(isPresented: $showMonPicker, onDismiss: {
//optional function when picker dismissed
}, content: {
CuisineTypePicker(selectedCuisineType: $monFilter)
})
}
}
The Query() file that calls a base query with all results, and optional function to return specific results:
import Foundation
import FirebaseFirestore
class Query: ObservableObject {
#Published var queriedList: [MenuItem] = []
init() {
baseQuery()
}
func baseQuery() {
let queryRef = Firestore.firestore().collection("menuItems").limit(to: 50)
queryRef
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
self.queriedList = querySnapshot?.documents.compactMap { document in
try? document.data(as: MenuItem.self)
} ?? []
}
}
}
func filteredQuery(category: String?, glutenFree: Bool?) {
var filtered = Firestore.firestore().collection("menuItems").limit(to: 50)
// Sorting and Filtering Data
if let category = category, !category.isEmpty {
filtered = filtered.whereField("cuisineType", isEqualTo: category)
}
if let glutenFree = glutenFree, !glutenFree {
filtered = filtered.whereField("glutenFree", isEqualTo: true)
}
filtered
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
self.queriedList = querySnapshot?.documents.compactMap { document in
try? document.data(as: MenuItem.self);
} ?? []
print(self.queriedList.count)
}
}
}
}
Picker view where I'm calling the filtered query:
import SwiftUI
struct CuisineTypePicker: View {
#State private var cuisineTypes = ["filter", "American", "Chinese", "French"]
#Environment(\.presentationMode) var presentationMode
#Binding var selectedCuisineType: String
#State var gfSelected = false
let query = Query()
var body: some View {
VStack(alignment: .center) {
//Buttons and formatting code removed to simplify..
}
.padding(.top)
Picker("", selection: $selectedCuisineType) {
ForEach(cuisineTypes, id: \.self) {
Text($0)
}
}
Spacer()
Button(action: {
self.query.filteredQuery(category: selectedCuisineType, glutenFree: gfSelected)
self.presentationMode.wrappedValue.dismiss()
}, label: {
Text( "apply filters")
})
}
.padding()
}
}
I suspect that the issue stems from the fact that you aren't sharing the same instance of Query between your TestView and your CuisineTypePicker. So, when you start a new Firebase query on the instance contained in CuisineTypePicker, the results are never reflected in the main view.
Here's an example of how to solve that (with the Firebase code replaced with some non-asynchronous sample code for now):
struct MenuItem : Identifiable {
var id = UUID()
var cuisineType : String
var title : String
var glutenFree : Bool
}
struct ContentView: View {
#ObservedObject var query = Query()
#State var showMonPicker = false
#State var monFilter = "filter"
var body: some View {
VStack {
HStack(alignment: .center) {
Text("Monday")
Spacer()
Button(action: {
self.showMonPicker.toggle()
}, label: {
Text("\(monFilter)")
})
}
.padding()
ScrollView(.horizontal) {
LazyHStack(spacing: 35) {
ForEach(query.queriedList) { menuItem in
Text("\(menuItem.title) - \(menuItem.cuisineType)")
}
}
}
}
.sheet(isPresented: $showMonPicker, onDismiss: {
//optional function when picker dismissed
}, content: {
CuisineTypePicker(query: query, selectedCuisineType: $monFilter)
})
}
}
class Query: ObservableObject {
#Published var queriedList: [MenuItem] = []
private let allItems: [MenuItem] = [.init(cuisineType: "American", title: "Hamburger", glutenFree: false),.init(cuisineType: "Chinese", title: "Fried Rice", glutenFree: true)]
init() {
baseQuery()
}
func baseQuery() {
self.queriedList = allItems
}
func filteredQuery(category: String?, glutenFree: Bool?) {
queriedList = allItems.filter({ item in
if let category = category {
return item.cuisineType == category
} else {
return true
}
}).filter({item in
if let glutenFree = glutenFree {
return item.glutenFree == glutenFree
} else {
return true
}
})
}
}
struct CuisineTypePicker: View {
#ObservedObject var query : Query
#Binding var selectedCuisineType: String
#State private var gfSelected = false
private let cuisineTypes = ["filter", "American", "Chinese", "French"]
#Environment(\.presentationMode) private var presentationMode
var body: some View {
VStack(alignment: .center) {
//Buttons and formatting code removed to simplify..
}
.padding(.top)
Picker("", selection: $selectedCuisineType) {
ForEach(cuisineTypes, id: \.self) {
Text($0)
}
}
Spacer()
Button(action: {
self.query.filteredQuery(category: selectedCuisineType, glutenFree: gfSelected)
self.presentationMode.wrappedValue.dismiss()
}, label: {
Text( "apply filters")
})
}
}

.sheet: Shows only once and then never again

Working with Beta4, it seems that the bug is still existing. The following sequence of views (a list, where a tap on a list entry opens another list) allows to present the ListView exactly once; the onDisappear is never called, so the showModal flag changes, but does not triggers the redisplay of ListView when tapped again. So, for each GridCellBodyEntry, the .sheet presentation works exactly once, and then never again.
I tried around with several suggestions and workarounds, but none worked (e.g., encapsulating with a NavigationViewModel). I even tried to remove the List, because there was an assumption that the List causes that behaviour, but even this did not change anything.
Are there any ideas around?
The setup:
A GridCellBody with this view:
var body: some View {
GeometryReader { geometry in
VStack {
List {
Section(footer: self.footerView) {
ForEach(self.rawEntries) { rawEntry in
GridCellBodyEntry(entityType: rawEntry)
}
}
}
.background(Color.white)
}
}
}
A GridCellBodyEntry with this definition:
struct GridCellBodyEntry: View {
let entityType: EntityType
let viewModel: BaseViewModel
init(entityType: EntityType) {
self.entityType = entityType
self.viewModel = BaseViewModel(entityType: self.entityType)
}
#State var showModal = false {
didSet {
print("showModal: \(showModal)")
}
}
var body: some View {
Group {
Button(action: {
self.showModal.toggle()
},
label: {
Text(entityType.localizedPlural ?? "")
.foregroundColor(Color.black)
})
.sheet(isPresented: $showModal, content: {
ListView(showModal: self.$showModal,
viewModel: self.viewModel)
})
}.onAppear{
print("Profile appeared")
}.onDisappear{
print("Profile disappeared")
}
}
}
A ListView with this definition:
struct ListView: View {
// MARK: - Private properties
// MARK: - Public interface
#Binding var showModal: Bool
#ObjectBinding var viewModel: BaseViewModel
// MARK: - Main view
var body: some View {
NavigationView {
VStack {
List {
Section(footer: Text("\(viewModel.list.count) entries")) {
ForEach(viewModel.list, id: \.objectID) { item in
NavigationLink(destination: ItemView(),
label: {
Text("\(item.objectID)")
})
}
}
}
}
.navigationBarItems(leading:
Button(action: {
self.showModal = false
}, label: {
Text("Close")
}))
.navigationBarTitle(Text(viewModel.entityType.localizedPlural ?? ""))
}
}
}
The BaseViewModel (excerpt):
class BaseViewModel: BindableObject {
/// The binding support.
var willChange = PassthroughSubject<Void, Never>()
/// The context.
var context: NSManagedObjectContext
/// The current list of typed items.
var list: [NSManagedObject] = []
// ... other stuff ...
}
where willChange.send() is called whenever something changes (create, modify, delete operations).
This is a variant of swiftUI PresentaionLink does not work second time
The following simplified code exhibits the behavior you're experiencing (the sheet only displays once):
import SwiftUI
struct ContentView: View {
#State var isPresented = false
#State var whichPresented = -1
var body: some View {
NavigationView {
List {
ForEach(0 ..< 10) { i in
Button(action: {
self.whichPresented = i
self.isPresented.toggle()
})
{ Text("Button \(i)") }
}.sheet(isPresented: $isPresented, content: {
Text("Destination View \(self.whichPresented)") })
}
}
}
}
There appears to be a bug in SwiftUI when you put the .sheet inside a List or a ForEach. If you move the .sheet outside of the List, you should be able to get the correct behavior.
import SwiftUI
struct ContentView: View {
#State var isPresented = false
#State var whichPresented = -1
var body: some View {
NavigationView {
List {
ForEach(0 ..< 10) { i in
Button(action: {
self.whichPresented = i
self.isPresented.toggle()
})
{ Text("Button \(i)") }
}
}
}.sheet(isPresented: $isPresented, content: { Text("Destination View \(self.whichPresented)") })
}
}