Reinstantiate an #EnvironmentObject in a view - swift

In my application, I have the need to reinstantiate an #EnvironmentObject at the tap of a button. Where is the right spot to do that?
Here's a screenshot of my view - in the Archived Objects list, I want to have immutable objects. So, when I tap Archive Object, I want to add the current object to the array and create a new current object.
A simplified version of the app looks as follows.
I instantiate the object in the SceneDelegate:
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
var myObject = MyObject()
let myObjects = MyObjects()
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let contentView = ContentView()
.environmentObject(myObject)
.environmentObject(myObjects)
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
}
}
//.....
}
Here's my models:
class MyObject: ObservableObject, Identifiable {
#Published var id = UUID()
}
class MyObjects: ObservableObject {
#Published var values: [MyObject] = [
MyObject(),
MyObject()
]
}
In my view, I have the need to put my current object to an (archive) array and then create a new current object to work with. Here's what I'm trying:
struct ContentView: View {
#EnvironmentObject var currentObject: MyObject
#EnvironmentObject var objects: MyObjects
var sceneDelegate: UISceneDelegate {
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let sd = windowScene.delegate as? SceneDelegate else { fatalError() }
return sd
}
var body: some View {
VStack {
Form {
Section(header: Text("Current Object")) {
Text(currentObject.id.uuidString)
}
Section(header: Text("Archived Objects")) {
List(objects.values, id: \.id) { object in
Text(object.id.uuidString)
}
Button(action: {
self.objects.values.append(self.currentObject)
// as the environment object is get only, I cannot reinstantiate it here...
// self.currentObject = MyObject()
}) {
Text("Archive Object")
}
}
}
Spacer()
}
}
}

Here is a solution. See comments inline.
Tested with Xcode 11.4 / iOS 13.4
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
// Usage
ContentView(current: MyObject())
.environmentObject(MyObjects())
}
}
struct ContentView: View {
#EnvironmentObject var objects: MyObjects
#State private var current: MyObject // tracks current
init(current: MyObject) {
_current = State(initialValue: current)
}
var body: some View {
// rebuilt on *current* changed
ContentInnerView(current: $current)
.environmentObject(current)
}
struct ContentInnerView: View {
#Binding var current: MyObject // change current
#EnvironmentObject var objects: MyObjects
#EnvironmentObject var currentObject: MyObject
init(current: Binding<MyObject>) {
_current = current
}
var sceneDelegate: UISceneDelegate {
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let sd = windowScene.delegate as? SceneDelegate else { fatalError() }
return sd
}
var body: some View {
VStack {
Form {
Section(header: Text("Current Object")) {
Text(currentObject.id.uuidString)
}
Section(header: Text("Archived Objects")) {
List(objects.values, id: \.id) { object in
Text(object.id.uuidString)
}
Button(action: {
self.objects.values.append(self.currentObject)
self.current = MyObject() // << works !!
}) {
Text("Archive Object")
}
}
}
Spacer()
}
}
}
}

Related

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

How to implement multi window with menu commands in SwiftUI for macOS?

Situation
Implement a multi window application, where each window has its own state.
Example
Here is an example (on github) to showcase the question:
import SwiftUI
#main
struct multi_window_menuApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}.commands {
MenuCommands()
}
}
}
struct ContentView: View {
#StateObject var viewModel: ViewModel = ViewModel()
var body: some View {
TextField("", text: $viewModel.inputText)
.disabled(true)
.padding()
}
}
public class ViewModel: ObservableObject {
#Published var inputText: String = "" {
didSet {
print("content was updated...")
}
}
}
Question
How should we programmatically figure out what is the currently selected view so we can update the state when the menu command is about to finish and update the state in the view model?
import Foundation
import SwiftUI
import Combine
struct MenuCommands: Commands {
var body: some Commands {
CommandGroup(after: CommandGroupPlacement.newItem, addition: {
Divider()
Button(action: {
let dialog = NSOpenPanel();
dialog.title = "Choose a file";
dialog.showsResizeIndicator = true;
dialog.showsHiddenFiles = false;
dialog.allowsMultipleSelection = false;
dialog.canChooseDirectories = false;
if (dialog.runModal() == NSApplication.ModalResponse.OK) {
let result = dialog.url
if (result != nil) {
let path: String = result!.path
do {
let string = try String(contentsOf: URL(fileURLWithPath: path), encoding: .utf8)
print(string)
// how to get access to the currently active view model to update the inputText variable?
// viewModel.inputText = string
}
catch {
print("Error \(error)")
}
}
} else {
return
}
}, label: {
Text("Open File")
})
.keyboardShortcut("O", modifiers: .command)
})
}
}
Links that might be useful to figure this out:
http://www.gfrigerio.com/build-a-macos-app-with-swiftui/
https://troz.net/post/2021/swiftui_mac_menus/
https://onmyway133.com/posts/how-to-manage-windowgroup-in-swiftui-for-macos/
Useful links:
How to access NSWindow from #main App using only SwiftUI?
How to access own window within SwiftUI view?
https://lostmoa.com/blog/ReadingTheCurrentWindowInANewSwiftUILifecycleApp/
(this is what I was able to come up with, if anyone has a better idea/approach, please share)
The idea is to create a shared "global" view model that keeps track of opened windows and view models. Each NSWindow has an attribute with a unique windowNumber. When a window becomes active (key), it looks up the view model by the windowNumber and sets it as the activeViewModel.
import SwiftUI
class GlobalViewModel : NSObject, ObservableObject {
// all currently opened windows
#Published var windows = Set<NSWindow>()
// all view models that belong to currently opened windows
#Published var viewModels : [Int:ViewModel] = [:]
// currently active aka selected aka key window
#Published var activeWindow: NSWindow?
// currently active view model for the active window
#Published var activeViewModel: ViewModel?
func addWindow(window: NSWindow) {
window.delegate = self
windows.insert(window)
}
// associates a window number with a view model
func addViewModel(_ viewModel: ViewModel, forWindowNumber windowNumber: Int) {
viewModels[windowNumber] = viewModel
}
}
Then, react on every change on window (when it is being closed and when it becomes an active aka key window):
import SwiftUI
extension GlobalViewModel : NSWindowDelegate {
func windowWillClose(_ notification: Notification) {
if let window = notification.object as? NSWindow {
windows.remove(window)
viewModels.removeValue(forKey: window.windowNumber)
print("Open Windows", windows)
print("Open Models", viewModels)
}
}
func windowDidBecomeKey(_ notification: Notification) {
if let window = notification.object as? NSWindow {
print("Activating Window", window.windowNumber)
activeWindow = window
activeViewModel = viewModels[window.windowNumber]
}
}
}
Provide a way to lookup window that is associated to the current view:
import SwiftUI
struct HostingWindowFinder: NSViewRepresentable {
var callback: (NSWindow?) -> ()
func makeNSView(context: Self.Context) -> NSView {
let view = NSView()
DispatchQueue.main.async { [weak view] in
self.callback(view?.window)
}
return view
}
func updateNSView(_ nsView: NSView, context: Context) {}
}
Here is the view that is updating the global view model with the current window and viewModel:
import SwiftUI
struct ContentView: View {
#EnvironmentObject var globalViewModel : GlobalViewModel
#StateObject var viewModel: ViewModel = ViewModel()
var body: some View {
HostingWindowFinder { window in
if let window = window {
self.globalViewModel.addWindow(window: window)
print("New Window", window.windowNumber)
self.globalViewModel.addViewModel(self.viewModel, forWindowNumber: window.windowNumber)
}
}
TextField("", text: $viewModel.inputText)
.disabled(true)
.padding()
}
}
Then we need to create the global view model and send it to the views and commands:
import SwiftUI
#main
struct multi_window_menuApp: App {
#State var globalViewModel = GlobalViewModel()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(self.globalViewModel)
}
.commands {
MenuCommands(globalViewModel: self.globalViewModel)
}
Settings {
VStack {
Text("My Settingsview")
}
}
}
}
Here is how the commands look like, so they can access the currently selected/active viewModel:
import Foundation
import SwiftUI
import Combine
struct MenuCommands: Commands {
var globalViewModel: GlobalViewModel
var body: some Commands {
CommandGroup(after: CommandGroupPlacement.newItem, addition: {
Divider()
Button(action: {
let dialog = NSOpenPanel();
dialog.title = "Choose a file";
dialog.showsResizeIndicator = true;
dialog.showsHiddenFiles = false;
dialog.allowsMultipleSelection = false;
dialog.canChooseDirectories = false;
if (dialog.runModal() == NSApplication.ModalResponse.OK) {
let result = dialog.url
if (result != nil) {
let path: String = result!.path
do {
let string = try String(contentsOf: URL(fileURLWithPath: path), encoding: .utf8)
print("Active Window", self.globalViewModel.activeWindow?.windowNumber)
self.globalViewModel.activeViewModel?.inputText = string
}
catch {
print("Error \(error)")
}
}
} else {
return
}
}, label: {
Text("Open File")
})
.keyboardShortcut("O", modifiers: [.command])
})
}
}
All is updated and runnable under this github project: https://github.com/ondrej-kvasnovsky/swiftui-multi-window-menu
I came across this question when I was solving a similar problem. I believe the SwiftUI way is to use FocusedValue:
// create an active viewmodel key
struct ActiveViewModelKey: FocusedValueKey {
typealias Value = ViewModel
}
extension FocusedValues {
var activeViewModel: ViewModel? {
get { self[ActiveViewModelKey.self] }
set { self[ActiveViewModelKey.self] = newValue }
}
}
struct ContentView: View {
#StateObject var viewModel: ViewModel = ViewModel()
var body: some View {
TextField("", text: $viewModel.inputText)
...
.focusedValue(\.activeViewModel, viewModel) // inject the focused value
}
}
struct MenuCommands: Commands {
#FocusedValue(\.activeViewModel) var activeViewModel // inject the active viewmodel
var body: some Commands {
CommandGroup(after: CommandGroupPlacement.newItem, addition: {
Divider()
Button(action: {
...
activeViewModel?.inputText = string
}, label: {
Text("Open File")
})
.keyboardShortcut("O", modifiers: [.command])
})
}
}

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
}

Use ViewRouter in SwiftUI Project in Xcode 12

I tried to use this method (see link attached) in a new Xcode 12 project as a way to create a login page for a SwiftUI app, but I had the Problem not knowing what to add to the main App struct. I'm still a beginner and tried adding ContentView().environmentObject(ViewRouter()) to the WindowGroup in the main app struct. Am I totally wrong or why doesn't Xcode build the view? Can somebody help?
Below the working code snippet:
import SwiftUI
import Foundation
import Combine
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: MotherView().environmentObject(ViewRouter()))
self.window = window
window.makeKeyAndVisible()
}
}
. . .
}
class ViewRouter: ObservableObject {
let objectWillChange = PassthroughSubject<ViewRouter,Never>()
var currentPage: String = "page1" {
didSet {
withAnimation() {
objectWillChange.send(self)
}
}
}
}
struct MotherView : View {
#EnvironmentObject var viewRouter: ViewRouter
var body: some View {
VStack {
if viewRouter.currentPage == "page1" {
ContentViewA()
} else if viewRouter.currentPage == "page2" {
ContentViewB()
.transition(.scale)
}
}
}
}
struct ContentViewA : View {
#EnvironmentObject var viewRouter: ViewRouter
var body: some View {
Button(action: {self.viewRouter.currentPage = "page2"}) {
Text("Login")
}
}
}
struct ContentViewB : View {
#EnvironmentObject var viewRouter: ViewRouter
var body: some View {
Button(action: {self.viewRouter.currentPage = "page1"}) {
Text("Logout")
}
}
}
Now I want to substitute the SceneDelegate in the Xcode 12 style, but the following doesn't work. Any idea why?
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
MotherView().environmentObject(ViewRouter())
}
}
}
Pls try next steps:
remove whole SceneDelegate class (in your case no need SceneDelegate class)
Modify your ViewRouter class like:
class ViewRouter: ObservableObject {
#Published var currentPage: String = "page1"
}

ObservableObject has a different instantiation in my class

I have an ObservableObject that stores the current country the user is wanting information on and I want this to be shared across all views and classes. I properly declared it in the scene delegate so there is no issue with that.
import Foundation
class GlobalData: ObservableObject {
#Published var whichCountry: String = "italy"
}
This is my main view of where I call an environment object to get whichCountry. When the users click the button it will open ListOfCountriesView() and pass that EnvironemtnObject through it to update the new country the users want.
import SwiftUI
struct InDepthView: View {
#State var showList = false
#EnvironmentObject var globalData: GlobalData
#ObservedObject var data = getDepthData(globalData: GlobalData())
var body: some View {
VStack(alignment: .leading) {
Button(action: { self.showList.toggle() }) {
VStack(alignment: .leading) {
HStack {
Text("\(self.data.globalDatam.whichCountry.uppercased())")
}
}
}
.sheet(isPresented: $showList) {
ListOfCountriesView().environmentObject(GlobalData())
}
}
}
}
import SwiftUI
struct ListOfCountriesView: View {
#EnvironmentObject var globalData: GlobalData
var body: some View {
ScrollView {
VStack(spacing: 15) {
Text("List of Countries")
.padding(.top, 25)
Button(action: {
self.globalData.whichCountry = "usa"
self.presentationMode.wrappedValue.dismiss()
}) {
VStack {
Text("\(self.globalData.whichCountry)")
.font(.system(size: 25))
Divider()
}
}
}
}
}
}
struct ListOfCountriesView_Previews: PreviewProvider {
static var previews: some View {
ListOfCountriesView().environmentObject(GlobalData())
}
}
When the user changes the country I want this class which is inside my InDepthView.swift file to be updated with the new string. But for some reason, it is still displaying "italy" when it should have changed to "usa" based on what happened in ListOfCountriesView(). So I know that there is two instantiations of GlobalData but I'm not sure how to fix this issue. Any help would be greatly appreciated as I have been spending the past two days trying to fix this issue!
class getDepthData: ObservableObject {
#Published var data : Specific!
#Published var countries : HistoricalSpecific!
var globalDatam: GlobalData
init(globalData: GlobalData) {
globalDatam = globalData
print(globalDatam.whichCountry + " init()")
updateData()
}
func updateData() {
let url = "https://corona.lmao.ninja/v2/countries/" // specific country
let session = URLSession(configuration: .default
session.dataTask(with: URL(string: url+"\(self.globalDatam.whichCountry)")!) { (data, _, err) in
if err != nil {
print((err?.localizedDescription)!)
return
}
let json = try! JSONDecoder().decode(Specific.self, from: data!)
DispatchQueue.main.async {
self.data = json
}
}.resume()
}
}
///////
I added this to the code like you mentioned. but recieving an error
import SwiftUI
struct InDepthView: View {
#State var showList = false
#State var pickerSelectedItem = 1
#EnvironmentObject var globalData: GlobalData
#ObservedObject var data: getDepthData
init() {
self.data = getDepthData(globalData: self.globalData)
}
ERRROR : self' used before all stored properties are initialized
You're creating a second GlobalData instance when you call
#ObservedObject var data = getDepthData(globalData: GlobalData())
Edit: Removed example that was passing the environment object in as an argument. That doesn't work and it crashes.
You will need to refactor a bit to either structure your app a bit differently altogether, or you could remove the environment object, and instead initialise GlobalData() in your first view and then just pass it into each subsequent view as an #ObservedObject, rather than as #EnvironmentObject via scene delegate.
The following is pseudocode but I hope clarifies what I mean:
struct ContentView: View {
#ObservedObject var globalData = GlobalData()
var body: some View {
...
NavigationLink("Linky link", destination: SecondView(globalData: globalData, data: getDepthData(globalData: globalData))
}
}
struct SecondView: View {
#ObservedObject var globalData: GlobalData
#ObservedObject var data: getDepthData
init(globalData: GlobalData, data: getDepthData) {
self.globalData = globalData
self.data = getDepthData
}
...
}