swiftswiftuiobservableobjectenvironmentobject

Fatal error: No ObservableObject of type IconNames found. A View.environmentObject(_:) for IconNames may be missing as an ancestor of this view


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

Solution

  • 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