0

I have created a class in SceneDelegate.swift file and I am trying to access it in SettingView.swift and ContentView.swift but I keep getting this error "Fatal error: No ObservableObject of type IconNames found. A View.environmentObject(_:) for IconNames may be missing as an ancestor of this view." What do i do? Sharing the code of all three files below.

ContentView File @Environment(.managedObjectContext) private var managedObjectContext @EnvironmentObject var iconSettings: IconNames

@FetchRequest(
    sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
    animation: .default)
private var items: FetchedResults<Item>

var body: some View {
    NavigationView {
        ZStack {
            List {
                ForEach(items) { item in
                    HStack {
                        Text(item.name ?? "")
                        Spacer()
                        Text(item.priority ?? "")
                    }
                }
                .onDelete(perform: deleteItems)
            }//END: LIST
            .navigationBarTitle("Todo", displayMode: .inline)
            .navigationBarItems(leading: EditButton())
            .navigationBarItems(trailing:
              Button(action: {
                self.showingSettingsView.toggle()
               }) {
                Image(systemName: "paintbrush")
                       .imageScale(.large)
                 }
                .sheet(isPresented: $showingSettingsView) {
                    SettingView().environmentObject(iconSettings) //error shown here
                }

SettingView File

@Environment(\.presentationMode) var presentationMode
@EnvironmentObject var iconSettings: IconNames

struct SettingView_Previews: PreviewProvider {
    static var previews: some View {
        SettingView().environmentObject(IconNames())
    }
}

SceneDelegate File

import UIKit
import SwiftUI

class IconNames: ObservableObject {
    var iconNames: [String?] = [nil]
    @Published var currentIndex = 0

    init() {
        getAlternateIconNames()
    
    if let currentIcon = UIApplication.shared.alternateIconName {
        self.currentIndex = iconNames.firstIndex(of: currentIcon) ?? 0
    }
}

func getAlternateIconNames() {
    if let icons = Bundle.main.object(forInfoDictionaryKey: "CFBundleIcons") as? 
 [String: Any],
       let alternateIcons = icons["CFBundleAlternateIcons"] as? [String: Any] {
        for (_, value) in alternateIcons {
            guard let iconList = value as? [String: Any] else { continue }
            guard let iconFiles = iconList["CFBundleIconFiles"] as? [String] else { 
 continue }
            guard let icon = iconFiles.first else { continue }
            
            iconNames.append(icon)
        }
    }
  }
}

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

var window: UIWindow?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options 
connectionOptions: UIScene.ConnectionOptions) {
    
    let context = (UIApplication.shared.delegate as! 
  AppDelegate).persistentContainer.viewContext
    
    let contentView = ContentView().environment(\.managedObjectContext, context)
    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = UIHostingController(rootView: 
  contentView.environmentObject(IconNames()))
        self.window = window
        window.makeKeyAndVisible()
    }

}

func sceneDidDisconnect(_ scene: UIScene) {
    //Called as the scene is being released by the system
}

func sceneDidBecomeActive(_ scene: UIScene) {
    //Called
}

func sceneWillResignActive(_ scene: UIScene) {
    //Called
}

func sceneWillEnterForeground(_ scene: UIScene) {
    //Called
}

func sceneDidEnterBackground(_ scene: UIScene) {
    (UIApplication.shared.delegate as? AppDelegate)?.saveContext()
  }
}
Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
Ansh Patel
  • 15
  • 5
  • The `SceneDelegate` – as well as `AppDelegate` – is not needed at all in your case. Create a class for the Core Data stack and inject it and `IconNames` into the environment in the `@main` struct. `sceneDidEnterBackground` is also available in the environment. – vadian Apr 20 '23 at 04:48
  • @vadian Can you please give a sample code, because I am confused on what you are trying to tell. I am just a beginner and learning. So please help me – Ansh Patel Apr 20 '23 at 14:52
  • It's unclear what Xcode/SwiftUI version we are talking about. If you create a new SwiftUI project in Xcode 13 and 14 and check the Core Data checkbox you will get almost all described components. You can get the scene phase with `@Environment(\.scenePhase)`. – vadian Apr 20 '23 at 15:01
  • @vadian Its XCode 14 and yes I did check the CoreData checkbox. I have tried so many things to make it work, but I keep getting the same error. Can you please share the code you asked me to inject? – Ansh Patel Apr 20 '23 at 15:48

1 Answers1

0

In Xcode 14 you get this code in a new SwiftUI Core Data project for free

import SwiftUI

@main
struct TestApp: App {
    let persistenceController = PersistenceController.shared

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, persistenceController.container.viewContext)
        }
    }
}

There is no AppDelegate and no SceneDelegate.


Create an instance of IconNames as @StateObject and put it in the environment

import SwiftUI

@main
struct TestApp: App {
    let persistenceController = PersistenceController.shared
    @StateObject var iconNames = IconNames()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, persistenceController.container.viewContext)
                .environmentObject(iconNames)
        }
    }
}

The you have access to in any descendant of the root view with

@EnvironmentObject var iconSettings: IconNames

If you want to observe the changes of the scene use

@Environment(\.scenePhase) var scenePhase
vadian
  • 274,689
  • 30
  • 353
  • 361