How do I instantly load core data in a view after closing my popup? - swift

The point of this app is to use core data to permanently add types of fruit to a list. I have two views: ContentView and SecondScreen. SecondScreen is a pop-up sheet. When I input a fruit and press 'save' in SecondScreen, I want to immediately update the list in ContentView to reflect the type of fruit that has just been added to core data as well as the other fruits which have previously been added to core data. My problem is that when I hit the 'save' button in SecondScreen, the new fruit is not immediately added to the list in ContentView. Instead, I have to restart the app to see the new fruit in the list.
Here is the class for my core data:
class CoreDataViewModel: ObservableObject {
let container: NSPersistentContainer
#Published var savedEntities: [FruitEntity] = []
init() {
container = NSPersistentContainer(name: "FruitsContainer")
container.loadPersistentStores { (description, error) in
if let error = error {
print("Error with coreData. \(error)")
}
}
fetchFruits()
}
func fetchFruits() {
let request = NSFetchRequest<FruitEntity>(entityName: "FruitEntity")
do {
savedEntities = try container.viewContext.fetch(request)
} catch let error {
print("Error fetching. \(error)")
}
}
func addFruit(text: String) {
let newFruit = FruitEntity(context: container.viewContext)
newFruit.name = text
saveData()
}
func saveData() {
do {
try container.viewContext.save()
fetchFruits()
} catch let error {
print("Error saving. \(error)")
}
}
}
Here is my ContentView struct:
struct ContentView: View {
//sheet variable
#State var showSheet: Bool = false
#StateObject var vm = CoreDataViewModel()
#State var refresh: Bool
var body: some View {
NavigationView {
VStack(spacing: 20) {
Button(action: {
showSheet.toggle()
}, label: {
Text("Add Fruit")
})
List {
ForEach(vm.savedEntities) { entity in
Text(entity.name ?? "NO NAME")
}
}
}
.navigationTitle("Fruits")
.sheet(isPresented: $showSheet, content: {
SecondScreen(refresh: $refresh)
})
}
}
}
Here is my SecondScreen struct:
struct SecondScreen: View {
#Binding var refresh: Bool
#Environment(\.presentationMode) var presentationMode
#StateObject var vm = CoreDataViewModel()
#State var textFieldText: String = ""
var body: some View {
TextField("Add fruit here...", text: $textFieldText)
.font(.headline)
.padding(.horizontal)
Button(action: {
guard !textFieldText.isEmpty else { return }
vm.addFruit(text: textFieldText)
textFieldText = ""
presentationMode.wrappedValue.dismiss()
refresh.toggle()
}, label: {
Text("Save")
})
}
}
To try and solve this issue, I've created a #State boolean variable called 'refresh' in ContentView and bound it with the 'refresh' variable in SecondScreen. This variable is toggled when the user hits the 'save' button on SecondScreen, and I was thinking that maybe this would change the #State variable in ContentView and trigger ContentView to reload, but it doesn't work.

In your second screen , change
#StateObject var vm = CoreDataViewModel()
to
#ObservedObject var vm: CoreDataViewModel
then provide for the instances that compiler will ask for
hope it helps

You need to use #FetchRequest instead of #StateObject and NSFetchRequest. #FetchRequest will call body to update the Views when the fetch result changes.

Related

How do I get a textfield to display attributes of a core data entity?

I have a list of fruits. the struct FruitRowView provides the layout for the view of each row. In this FruitRowView, there's a TextField which I want to display the name of each fruit. I am having trouble doing this. The reason why I want to use a TextField to display the name of each fruit rather than a Text is so that users can easily edit the name of the fruit right from that TextField. In this case, fruits are the Core Data entity and the fruit name is an attribute of this entity.
Here is my core data class:
class CoreDataViewModel: ObservableObject {
let container: NSPersistentContainer
#Published var savedEntities: [FruitEntity] = []
init() {
container = NSPersistentContainer(name: "FruitsContainer")
container.loadPersistentStores { (description, error) in
if let error = error {
print("Error with coreData. \(error)")
}
}
fetchFruits()
}
func fetchFruits() {
let request = NSFetchRequest<FruitEntity>(entityName: "FruitEntity")
do {
savedEntities = try container.viewContext.fetch(request)
} catch let error {
print("Error fetching. \(error)")
}
}
func addFruit(text: String) {
let newFruit = FruitEntity(context: container.viewContext)
newFruit.name = text
saveData()
}
func saveData() {
do {
try container.viewContext.save()
fetchFruits()
} catch let error {
print("Error saving. \(error)")
}
}
}
Here is my contentView:
struct ContentView: View {
//sheet variable
#State var showSheet: Bool = false
#StateObject var vm = CoreDataViewModel()
#State var refresh: Bool
var body: some View {
NavigationView {
VStack(spacing: 20) {
Button(action: {
showSheet.toggle()
}, label: {
Text("Add Fruit")
})
List {
ForEach(vm.savedEntities) { fruit in
FruitRowView(vm: vm, fruit: fruit)
}
}
}
.navigationTitle("Fruits")
.sheet(isPresented: $showSheet, content: {
SecondScreen(refresh: $refresh, vm: vm)
})
}
}
}
Here is my popup screen (used to create a new fruit)
struct SecondScreen: View {
#Binding var refresh: Bool
#Environment(\.presentationMode) var presentationMode
#ObservedObject var vm: CoreDataViewModel
#State var textFieldText: String = ""
var body: some View {
TextField("Add fruit here...", text: $textFieldText)
.font(.headline)
.padding(.horizontal)
Button(action: {
guard !textFieldText.isEmpty else { return }
vm.addFruit(text: textFieldText)
textFieldText = ""
presentationMode.wrappedValue.dismiss()
refresh.toggle()
}, label: {
Text("Save")
})
}
}
Here is my FruitRowView:
struct FruitRowView: View {
//instance of core data model
#ObservedObject var vm: CoreDataViewModel
var fruit: FruitEntity
#State var fruitName = fruit.name
var body: some View {
TextField("Enter fruit name", text: $fruitName)
}
}
So the error that I'm getting is: 'Cannot use instance member 'fruit' within property initializer; property initializers run before 'self' is available'. This error occurs in the FruitRowView when I try to assign fruitName to fruit.name. I assume that there's an easy workaround for this but I haven't been able to figure it out.
Since the fruitEntities in the view model is a published property, you don't need a state variable in the row. You need the binding for the row view, and you should pass it in the content view.
You don't need to pass the view model to the row view as well.
struct FruitRowView: View {
// No need to pass view model to child, only pass the data
// #ObservedObject var vm: CoreDataViewModel
#Binding var fruitName: String
// You don't need this.
// #State var fruitName = fruit.name
var body: some View {
TextField("Enter fruit name", text: $fruitName)
}
}
struct ContentView: View {
...
List {
ForEach(vm.savedEntities) { fruit in
FruitRowView(fruitName: $fruit.name)
}
}
...
}

SwiftUI: ViewModel making app crash when loading a view

I want to rename an item in a ForEach list. When i try to load the EditListView for a selected list the entire app crashes.
This is a SwiftUI macOS app and the items are saved using CoreData.
The crash happens as soon as you click on "Edit List" for any of the list items.
It doesn't crash if i remove this view model var listVM: MyListViewModel from the EditListViewModel.
Here's the EditListView
struct EditListView: View {
let name: String
#Binding var isVisible: Bool
var list: MyListViewModel
#ObservedObject var editListVM: EditListViewModel
init(name: String,list: MyListViewModel, isVisible: Binding<Bool> ) {
self.list = list
editListVM = EditListViewModel(listVM: list)
_isVisible = isVisible
self.name = name
}
var body: some View {
VStack {
Text(name)
Button(action: {
editListItemVM.update()
}) {
Text("Update List Name")
}
Button(action: {
self.isVisible = false
}) {
Text("Cancel")
}
}......
EditListViewModel
class EditListViewModel: ObservableObject {
var listVM: MyListViewModel
#Published var name: String = ""
init(listVM: MyListViewModel) {
self.listVM = listVM
name = listVM.name
}
func update(){
....}
}
MyListViewModel
struct MyListViewModel: Identifiable {
private let myList: MyList
init(myList: MyList) {
self.myList = myList
}
var id: NSManagedObjectID {
myList.objectID
}
var name: String {
myList.name ?? ""
}
}
MyList Model
#objc(MyList)
public class MyList: NSManagedObject, BaseModel {
static var all: NSFetchRequest<MyList> {
let request: NSFetchRequest<MyList> = MyList.fetchRequest()
request.sortDescriptors = []
return request
}
}
extension MyList {
#nonobjc public class func fetchRequest() -> NSFetchRequest<MyList> {
return NSFetchRequest<MyList>(entityName: "MyList")
}
#NSManaged public var name: String?
}
extension MyList : Identifiable {
}
Here's the Main View
struct MyListsView: View {
#StateObject var vm: MyListsViewModel
#State private var showPopover: Bool = false
init(vm: MyListsViewModel) {
_vm = StateObject(wrappedValue: vm)
}
List {
Text("My Lists")
ForEach(vm.myLists) { myList in
NavigationLink {
MyListItemsHeaderView(name: myList.name)
.sheet(isPresented: $showPopover) {
EditListView(name: myList.name, list: MyListViewModel(myList: MyList()), isVisible: $showPopover)
}
}
}.contextMenu {
Button {
showPopover = true
// Show the EditListView
} label: {
Label("Edit List", systemImage: "pen.circle")
}......
First get rid of your view model objects we don't use those in SwiftUI. We use the View struct and the property wrappers like #FetchRequest make the struct behave like an object. It looks like this:
struct ContentView: View {
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
animation: .default)
private var items: FetchedResults<Item>
var body: some View {
NavigationView {
List {
ForEach(items) { item in
ItemView(item: item)
I recommend looking at Xcode's app template with core data checked.
Then for editing you can use .sheet(item: like this:
struct ItemEditor: View {
#ObservedObject var item: Item // this is the scratch pad item
#Environment(\.managedObjectContext) private var context
#Environment(\.dismiss) private var dismiss // causes body to run
let onSave: () -> Void
#State var errorMessage: String?
var body: some View {
NavigationView {
Form {
Text(item.timestamp!, formatter: itemFormatter)
if let errorMessage = errorMessage {
Text(errorMessage)
}
Button("Update Time") {
item.timestamp = Date()
}
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("Cancel") {
dismiss()
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button("Save") {
// first save the scratch pad context then call the handler which will save the view context.
do {
try context.save()
errorMessage = nil
onSave()
} catch {
let nsError = error as NSError
errorMessage = "Unresolved error \(nsError), \(nsError.userInfo)"
}
}
}
}
}
}
}
struct ItemEditorConfig: Identifiable {
let id = UUID()
let context: NSManagedObjectContext
let item: Item
init(viewContext: NSManagedObjectContext, objectID: NSManagedObjectID) {
// create the scratch pad context
context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
context.parent = viewContext
// load the item into the scratch pad
item = context.object(with: objectID) as! Item
}
}
struct EditItemButton: View {
let itemObjectID: NSManagedObjectID
#Environment(\.managedObjectContext) private var viewContext
#State var itemEditorConfig: ItemEditorConfig?
var body: some View {
Button(action: edit) {
Text("Edit")
}
.sheet(item: $itemEditorConfig, onDismiss: didDismiss) { config in
ItemEditor(item: config.item) {
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
itemEditorConfig = nil // dismiss the sheet
}
.environment(\.managedObjectContext, config.context)
}
}
func edit() {
itemEditorConfig = ItemEditorConfig(viewContext: viewContext, objectID: itemObjectID)
}
func didDismiss() {
// Handle the dismissing action.
}
}
struct ItemView: View {
#ObservedObject var item: Item
var body: some View {
Text("Item at \(item.timestamp!, formatter: itemFormatter)")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
EditItemButton(itemObjectID: item.objectID)
}
}
}
}
the params for EditListView in the main view were incorrect.
Fixed it with the following params:
.sheet(isPresented: $showPopover) {
EditListView(name: myList.name, list: myList, isVisible: $showPopover)
}

Use the same view for adding and editing CoreData objects

I'm working on an iOS app that track people's medication and I got an add view and an edit view, both look almost the same with the exception that on my edit view I use the .onAppear to load all the medication data into the fields with an existing medication using let medication: Medication
My Form looks something like this:
Form {
Group {
TextField("Medication name", text: $name).disableAutocorrection(true)
TextField("Remaining quantity", text: $remainingQuantity).keyboardType(.numberPad)
TextField("Box quantity", text: $boxQuantity).keyboardType(.numberPad)
DatePicker("Date", selection: $date, in: Date()...).datePickerStyle(GraphicalDatePickerStyle())
Picker(selection: $repeatPeriod, label: Text("Repeating")) {
ForEach(RepeatPeriod.periods, id: \.self) { periods in
Text(periods).tag(periods)
}
.onAppear {
if pickerView {
self.name = self.medication.name != nil ? "\(self.medication.name!)" : ""
self.remainingQuantity = (self.medication.remainingQuantity != 0) ? "\(self.medication.remainingQuantity)" : ""
self.boxQuantity = (self.medication.boxQuantity != 0) ? "\(self.medication.boxQuantity)" : ""
self.date = self.medication.date ?? Date()
self.repeatPeriod = self.medication.repeatPeriod ?? "Nunca"
self.notes = self.medication.notes != nil ? "\(self.medication.notes!)" : ""
}
}
}
I thought of using a binding variable like isEditMode and it works fine but I had some issue related to the moc object when calling the add view that doesn't provide an object.
Here's how my editView preview looks like
struct EditMedicationSwiftUIView_Previews: PreviewProvider {
static let moc = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
static var previews: some View {
let medication = Medication(context: moc)
return NavigationView {
EditMedicationSwiftUIView(medication: medication)
}
}
}
Any suggestions?
Here is a simplified version of what I think you are trying to do. It uses code from a SwiftUI sample project. Just create an Xcode SwiftUI project with CoreData.
import SwiftUI
import CoreData
//Standard List Screen where you can select an item to see/edit and you find a button to add
struct ReusableParentView: View {
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
animation: .default)
private var items: FetchedResults<Item>
//Keeps work out of the Views so it can be reused
#StateObject var vm: ReusableParentViewModel = ReusableParentViewModel()
var body: some View {
NavigationView{
List{
ForEach(items) { item in
NavigationLink {
//This is the same view as the sheet but witht he item passed fromt he list
ReusableItemView(item: item)
} label: {
VStack{
Text(item.timestamp.bound, formatter: itemFormatter)
Text(item.hasChanges.description)
}
}
}.onDelete(perform: { indexSet in
for idx in indexSet{
vm.deleteItem(item: items[idx], moc: viewContext)
}
})
}
//Show sheet to add new item
.sheet(item: $vm.newItem, onDismiss: {
vm.saveContext(moc: viewContext)
//You can also cancel/get rid of the new item/changes if the user doesn't save
//vm.cancelAddItem(moc: viewContext)
}, content: { newItem in
NavigationView{
ReusableItemView(item: newItem)
}
//Inject the VM the children Views have access to the functions
.environmentObject(vm)
})
.toolbar(content: {
ToolbarItem(placement: .automatic, content: {
//Trigger new item sheet
Button(action: {
vm.addItem(moc: viewContext)
}, label: {
Image(systemName: "plus")
})
})
})
}
//Inject the VM the children Views have access to the functions
.environmentObject(vm)
}
private let itemFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .medium
return formatter
}()
}
//The Item's View
struct ReusableItemView: View {
//All CoreData objects are ObservableObjects to see changes you have to wrap them in this
#ObservedObject var item: Item
#Environment(\.editMode) var editMode
var body: some View {
VStack{
if editMode?.wrappedValue == .active{
EditItemView(item: item)
}else{
ShowItemView(item: item)
}
}
.toolbar(content: {
ToolbarItem(placement: .automatic, content: {
//If you want to edit this info just press this button
Button(editMode?.wrappedValue == .active ? "done": "edit"){
if editMode?.wrappedValue == .active{
editMode?.wrappedValue = .inactive
}else{
editMode?.wrappedValue = .active
}
}
})
})
}
}
//The View to just show the items info
struct ShowItemView: View {
//All CoreData objects are ObservableObjects to see changes you have to wrap them in this
#ObservedObject var item: Item
var body: some View {
if item.timestamp != nil{
Text("Item at \(item.timestamp!)")
}else{
Text("nothing to show")
}
}
}
//The View to edit the item's info
struct EditItemView: View {
#Environment(\.managedObjectContext) private var viewContext
#EnvironmentObject var vm: ReusableParentViewModel
#Environment(\.editMode) var editMode
//All CoreData objects are ObservableObjects to see changes you have to wrap them in this
#ObservedObject var item: Item
var body: some View {
DatePicker("timestamp", selection: $item.timestamp.bound).datePickerStyle(GraphicalDatePickerStyle())
}
}
struct ReusableParentView_Previews: PreviewProvider {
static var previews: some View {
ReusableParentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
}
class ReusableParentViewModel: ObservableObject{
//Can be used to show a sheet when a new item is created
#Published var newItem: Item? = nil
//If you dont want to create a CoreData item immediatly just present a sheet with the AddItemView in it
#Published var presentAddSheet: Bool = false
func addItem(moc: NSManagedObjectContext) -> Item{
//You should never create an ObservableObject inside a SwiftUI View unless it is using #StateObject which doesn't apply to a CoreData object
let temp = Item(context: moc)
temp.timestamp = Date()
//Sets the newItem variable
newItem = temp
//And returns the new item for other uses
return temp
}
func cancelAddItem(moc: NSManagedObjectContext){
rollbackChagnes(moc: moc)
newItem = nil
}
func rollbackChagnes(moc: NSManagedObjectContext){
moc.rollback()
}
func deleteItem(item: Item, moc: NSManagedObjectContext){
moc.delete(item)
saveContext(moc: moc)
}
func saveContext(moc: NSManagedObjectContext){
do{
try moc.save()
}catch{
print(error)
}
}
}
And if for some reason you don't want to create a CoreData object ahead of time which seems to be what you are doing you can always Create the temp variables and make a sharable editable view that takes in #Binding for each variable you want to edit.
//The View to Add the item's info, you can show this anywhere.
struct AddItemView: View {
#Environment(\.managedObjectContext) private var viewContext
#EnvironmentObject var vm: ReusableParentViewModel
//These can be temporary variables
#State var tempTimestamp: Date = Date()
var body: some View {
EditableItemView(timestamp: $tempTimestamp)
.toolbar(content: {
ToolbarItem(placement: .navigationBarLeading, content: {
//Create and save the item
Button("save"){
let new = vm.addItem(moc: viewContext)
new.timestamp = tempTimestamp
vm.saveContext(moc: viewContext)
}
})
})
}
}
//The View to edit the item's info
struct EditItemView: View {
#EnvironmentObject var vm: ReusableParentViewModel
#Environment(\.managedObjectContext) private var viewContext
#ObservedObject var item: Item
var body: some View {
VStack{
EditableItemView(timestamp: $item.timestamp.bound)
.onDisappear(perform: {
vm.rollbackChagnes(moc: viewContext)
})
//Just save the item
Button("save"){
vm.saveContext(moc: viewContext)
}
}
}
}
//The View to edit the item's info
struct EditableItemView: View {
#Environment(\.managedObjectContext) private var viewContext
#EnvironmentObject var vm: ReusableParentViewModel
//All CoreData objects are ObservableObjects to see changes you have to wrap them in this
#Binding var timestamp: Date
var body: some View {
DatePicker("timestamp", selection: $timestamp).datePickerStyle(GraphicalDatePickerStyle())
}
}

Same view for both create and edit in SwiftUI

since a couple of days I try to find a solution for using the same view for both create and edit an CoreData object in SwiftUI.
Assume that we have a CoreData entity called Entry. And it only has one property name: String
Now we have a Main/Master view with a List of all Entry objects. To navigate to the DetailView I use the following code:
List {
ForEach(entires) { entry in
NavigationLink(destination: DetailView(entry: entry)) {
Text(item.name ?? "empty")
}
}
}
In this DetailView I can then edit the existing object:
struct DetailView: View {
#Environment(\.presentationMode) var presentationMode
#Environment(\.managedObjectContext) private var viewContext
#State var name: String = ""
#ObservedObject var entry: Entry
init(entry: Entry) {
self.entry = entry
self._name = State(initialValue: entry.name ?? "")
}
var body: some View {
NavigationView {
Form {
Section {
TextField("Name", text: $name)
Button("Save") {
guard name != "" else {return}
entry.name = name
do {
try viewContext.save()
print("saved")
} catch {
print(error.localizedDescription)
}
presentationMode.wrappedValue.dismiss()
}
}
Button("Cancel") {
presentationMode.wrappedValue.dismiss()
}.accentColor(.red)
}
.navigationTitle("Entry Name")
}
}
}
But then I need a different view which looks quite the same just for the new entry, since this view does not require a #ObservedObject. This view I access via a simple add button from the Main/Master view and it looks like this:
struct AddView: View {
#Environment(\.presentationMode) var presentationMode
#Environment(\.managedObjectContext) private var viewContext
#State var name: String = ""
var body: some View {
NavigationView {
Form {
TextField("Name", text: $name)
Button("Save") {
guard name != "" else {return}
let entry = Entry(context: viewContext)
entry.name = name
do {
try viewContext.save()
print("saved")
} catch {
print(error.localizedDescription)
}
presentationMode.wrappedValue.dismiss()
}
Button("Cancel") {
presentationMode.wrappedValue.dismiss()
}.accentColor(.red)
}
.navigationTitle("Add new Entry")
}
}
}
What I don't like here is that I have two views which are 99% equal except that one has the #ObservedObject and the other does not. How can I combine those two views into one? What I would need is something like a #ObservedObject var entry: Entry? where I can keep the Entry optional.

UserDefault value does not update in a list SwiftUI

I have two views, embedded in TabView.
I am using userdefaults in a class called usersettings.
class UserSettings: ObservableObject {
#Published var favList: [String] {
willSet {
print("willset")
}
didSet {
UserDefaults.standard.set(favList, forKey: "isAccountPrivate")
print("didset")
}
}
init() {
self.favList = UserDefaults.standard.object(forKey: "isAccountPrivate") as? [String] ?? ["Sepetiniz Boş"]
}
}
In Button View, which acts like add/remove favorite. It successfully adds and remove from the UserDefaults. But when I add something it does not show on the other view (please see the next code after FavButton)
struct FavButton: View {
#Binding var passedFood: String
#ObservedObject var userSettings = UserSettings()
var body: some View {
Button(action: {
if userSettings.favList.contains(passedFood) {
userSettings.favList.remove(at: userSettings.favList.firstIndex(of: passedFood )!)
} else {
userSettings.favList.append(passedFood)
}
})
}
}
But it does not update my list in this other view unless I close and open my app. If I remove something from the list, it actually removes from the userdefault.
If I add a new word within this view, it works too.
My only problem is when I add something from another view (FavButton) it does not show in this view (FavView).
struct FavView: View {
#ObservedObject var userSettings = UserSettings()
#State private var newWord = ""
var body: some View {
NavigationView {
List {
TextField("Ürün Ekleyin...", text: $newWord, onCommit: addNewWord)
ForEach( self.userSettings.favList, id: \.self) { list in
Text(list)
.font(.headline)
.padding()
}
.onDelete(perform: self.deleteRow)
}
.navigationTitle("Sepetim")
}
}
private func deleteRow(at indexSet: IndexSet) {
self.userSettings.favList.remove(atOffsets: indexSet)
}
private func addNewWord() {
let answer = newWord.lowercased().trimmingCharacters(in: .whitespacesAndNewlines)
self.userSettings.favList.append(answer)
guard answer.count > 0 else {
return
}
newWord = ""
}
}
A better approach to follow the SwiftUI idiom is to use the .environmentObject() modifier.
When you declare your app:
struct AppScene: App {
#StateObject private var userSettings = UserSettings() // Use state object to persist the object
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(userSettings) // Inject userSettings into the environment
}
}
}
and then in you ContentView you can reach into your environment and get the object:
struct ContentView: View {
#EnvironmentObject private var userSettings: UserSettings
var body: some View {
Text("Number of items in favList: \(userSettings.favList.count)")
}
}
You need to use same instance of UserSettings in all views where you want to have observed user settings, like
class UserSettings: ObservableObject {
static let global = UserSettings()
//... other code
}
and now
struct FavButton: View {
#ObservedObject var userSettings = UserSettings.global // << here !!
// ... other code
}
and
struct FavView: View {
#ObservedObject var userSettings = UserSettings.global // << here !!
// ... other code
}