Fatal Error while changing app icons in SwiftUI 2.0

Viewed 135

I am trying to allow user customisable app icons to my app. I found tutorials that do this in SwiftUI 1.0 (old lifecycle) and I tried to make it work in the new lifecycle.I specifically used this tutorial. I am getting the fatal error Fatal error: No ObservableObject of type IconNames found. A View.environmentObject(_:) for IconNames may be missing as an ancestor of this view.

First I updated my info.plist file with the following

<key>CFBundleIcons</key>
<dict>
    <key>CFBundlePrimaryIcon</key>
    <dict>
        <key>CFBundleIconName</key>
        <string></string>
        <key>CFBundleIconFiles</key>
        <array>
            <string></string>
        </array>
        <key>UIPrerenderedIcon</key>
        <false/>
    </dict>
    <key>CFBundleAlternateIcons</key>
    <dict>
        <key>Dark</key>
        <dict>
            <key>CFBundleIconFiles</key>
            <array/>
            <key>Item 0</key>
            <string>Dark</string>
        </dict>
    </dict>
</dict>

Then I added the following code to my settings page (a page I navigate to with a navigation link in my content view.

import SwiftUI

struct Settings: View {
    @EnvironmentObject var iconSettings : IconNames
    
    var body: some View {
        Form{
            Section{
                Picker(selection: $iconSettings.currentIndex, label: Text("App icon")){
                    ForEach(0 ..< iconSettings.iconNames.count){ i in
                        HStack{
                            Text(self.iconSettings.iconNames[i] ?? "AppIcon")
                            Image(uiImage: UIImage(named: self.iconSettings.iconNames[i] ?? "AppIcon") ?? UIImage())
                                .resizable()
                                .renderingMode(.original)
                                .frame(width: 50, height: 50, alignment: .leading)
                        }
                    }
                }.onReceive([self.iconSettings.currentIndex].publisher.first()) { (value) in
                    
                    let index = self.iconSettings.iconNames.firstIndex(of: UIApplication.shared.alternateIconName) ?? 0

                    if index != value{

                        UIApplication.shared.setAlternateIconName(self.iconSettings.iconNames[value]){ error in
                            if let error = error {
                                print(error.localizedDescription)
                            } else {
                                print("Success!")
                            }
                        }
                    }
                }
            }
        }
        .navigationTitle("Settings")
    }
}


class IconNames: ObservableObject {
    var iconNames: [String?] = [nil]
    @Published var currentIndex = 0
    init() {
        getAlternateIcons()
        
        if let currentIcon = UIApplication.shared.alternateIconName{
            self.currentIndex = iconNames.firstIndex(of: currentIcon) ?? 0
        }
    }
    func getAlternateIcons() {
        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? Dictionary<String,Any> else{return}
                guard let iconFiles = iconList["CFBundleIconFiles"] as? [String]
                    else{return}
                    
                guard let icon = iconFiles.first else{return}
                iconNames.append(icon)
   
            }
        }
    }
}
1 Answers

In place of Settings view creation you must provide environmentObject, like

Settings().environmentObject(IconNames())
Related