ios – Thread 1: “+entityForName: nil is not a legal NSPersistentStoreCoordinator for searching for entity name ‘Article'” = CodeGen : Manual/None – Config


I have been working on an News App, in that i want to check Whether user is opening the app first time in a day, if yes then fetch the ArticlesData json from API or if no then fetch it from coreData which is previously got saved.

My 2 Entites and i have set the codeGen of both these 2 Entites to Manual/None in CodeGen Config and both these entites have one to on relationShip

ArticleEntity

Source Entity

NSManagedSubclass for both these entites – manualy created which is confirmable to Decodable

//Article.swift

import Foundation
import CoreData

class Article: NSManagedObject, Decodable {
    
    @NSManaged var title: String?
    @NSManaged var articleDescription: String?
    @NSManaged var content: String?
    @NSManaged var url: String?
    @NSManaged var image: String?
    @NSManaged var publishedAt: String?
    @NSManaged var source: Source?
    @NSManaged var category: String?
    
    enum CodingKeys: String, CodingKey {
        case title, articleDescription = "description", content, url, image, publishedAt, source, category
    }

    required convenience init(from decoder: Decoder) throws {
        // Extract managed object context from the decoder's userInfo dictionary
        guard let context = decoder.userInfo[CodingUserInfoKey.managedObjectContext] as? NSManagedObjectContext else {
            throw DecoderConfigurationError.missingManagedObjectContext
        }

        // Initialize the Article with the managed object context
        self.init(context: context)

        // Decode other properties
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.title = try container.decode(String.self, forKey: .title)
        self.articleDescription = try container.decode(String.self, forKey: .articleDescription)
        self.content = try container.decode(String.self, forKey: .content)
        self.url = try container.decode(String.self, forKey: .url)
        self.image = try container.decode(String.self, forKey: .image)
        self.publishedAt = try container.decode(String.self, forKey: .publishedAt)
        self.category = try container.decode(String.self, forKey: .category)
        
        // Decode Source entity
        let sourceData = try container.decode(SourceData.self, forKey: .source)
        
        // Create and associate source
        let source = Source(context: context)
        source.name = sourceData.name
        source.url = sourceData.url
        self.source = source
    }
}

//Source.swift

import Foundation
import CoreData

class Source: NSManagedObject, Decodable {
    
    @NSManaged var name: String? // Adjust the type based on your data model
    @NSManaged var url: String?
    
    enum CodingKeys: CodingKey {
        case name, url
    }

    required convenience init(from decoder: Decoder) throws {
        // Extract managed object context from the decoder's userInfo dictionary
        guard let context = decoder.userInfo[CodingUserInfoKey.managedObjectContext] as? NSManagedObjectContext else {
            throw DecoderConfigurationError.missingManagedObjectContext
        }

        // Initialize the Source with the managed object context
        self.init(context: context)

        // Decode other properties
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.name = try container.decode(String.self, forKey: .name)
        self.url = try container.decode(String.self, forKey: .url)
    }
}

//CoreDataStack.swift

import Foundation
import CoreData

class CoreDataStack {
    static let shared = CoreDataStack()

    lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "ArticlesCD")
        container.loadPersistentStores { _, error in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        }
        return container
    }()

    var viewContext: NSManagedObjectContext {
        return persistentContainer.viewContext
    }

    func saveContext() {
        if viewContext.hasChanges {
            do {
                try viewContext.save()
            } catch {
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
    }
    

}

//Main Logic FetchData.swift

import Foundation
import CoreData

class FetchNews {
    
    private let lastLaunchDateKey = "LastLaunchDate"

    
    func fetchData(overRideValue : Bool = false, category: String, context: NSManagedObjectContext, completion: @escaping ([ArticleData]) -> ()) {
            let isFirstLaunchOfDay = checkIsFirstLaunchOfDay(overRideValue: overRideValue)
            
            if isFirstLaunchOfDay {
                // Proceed with fetching data
                let mainURL = "https://gnews.io/api/v4/top-headlines?category=\(category)"
                let apikey = "apiKEY"
                let remainingPart = "&lang=en&country=ind&max=10&apikey=\(apikey)"
                
                let urlString = mainURL + remainingPart
                
                let url = URL(string: urlString)
                
                let urlSession = URLSession.shared.dataTask(with: url!) { (data, response, error) in
                    if let _ = error {
                        print("Problem in fetching the data")
                    }
                    
                    if let data = data {
                        do {
                            let receivedArticles = try JSONDecoder().decode(ArticlesData.self, from: data)
                            DispatchQueue.main.async {
                                self.saveToCoreData(articles: receivedArticles.articles, category: category, context: context)
                                self.updateLastLaunchDate()
                                print("Fetched The data from api")
                                completion(receivedArticles.articles)
                            }
                            
                        } catch {
                            print("Error in the struct or code")
                        }
                    }
                }
                urlSession.resume()
            } else if(!isFirstLaunchOfDay) {
                // Fetch data from Core Data, it's not the first launch of the day
                let existingArticles = fetchArticlesFromCoreData(category: category, context: context)
                print("Fetched The data from coreData")
                completion(existingArticles)
            }
        }
    
    private func saveToCoreData(articles: [ArticleData], category: String, context : NSManagedObjectContext) {
        
        deleteArticles(for: category, context: context)

        for articleData in articles {
            let article = Article(context: context)
            article.title = articleData.title
            article.articleDescription = articleData.description
            article.content = articleData.content
            article.url = articleData.url
            article.image = articleData.image
            article.publishedAt = articleData.publishedAt
            article.category = category
            
            // Assuming you have a Source entity
            let source = Source(context: context)
            source.name = articleData.source.name
            source.url = articleData.source.url
            article.source = source
        }
        
        do {
            try context.save()
            print("Data saved to Core Data")
        } catch {
            print("Error saving data to Core Data: \(error)")
        }
    }
    
    private func fetchArticlesFromCoreData(category: String, context: NSManagedObjectContext) -> [ArticleData] {
        let request = NSFetchRequest<Article>(entityName: "Article")
        request.predicate = NSPredicate(format: "category == %@", category)
        
        do {
            let fetchedArticles = try context.fetch(request)
            return fetchedArticles.map { article in
                // Map Core Data Article objects to ArticleData
                return ArticleData(
                    title: article.title ?? "",
                    description: article.articleDescription ?? "",
                    content: article.content ?? "",
                    url: article.url ?? "",
                    image: article.image ?? "",
                    publishedAt: article.publishedAt ?? "",
                    source: SourceData(name: article.source?.name ?? "", url: article.source?.url ?? "")
                )
            }
        } catch {
            print("Error fetching articles from Core Data: \(error)")
            return []
        }
    }
    
    private func updateLastLaunchDate() {
        let userDefaults = UserDefaults.standard
        userDefaults.set(Date(), forKey: lastLaunchDateKey)
    }
    
    private func checkIsFirstLaunchOfDay(overRideValue : Bool = false) -> Bool {
        if(overRideValue == true){
            return true
        }
        let userDefaults = UserDefaults.standard
        let lastLaunchDate = userDefaults.object(forKey: lastLaunchDateKey) as? Date
        
        let calendar = Calendar.current
        let currentDate = Date()
        
        if let lastLaunchDate = lastLaunchDate, calendar.isDate(currentDate, inSameDayAs: lastLaunchDate) {
            // Not the first launch of the day
            return false
        }
        
        return true
    }
    
    private func deleteArticles(for category: String, context: NSManagedObjectContext) {
            let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Article")
            request.predicate = NSPredicate(format: "category == %@", category)

            let deleteRequest = NSBatchDeleteRequest(fetchRequest: request)

            do {
                try context.execute(deleteRequest)
                print("Deleted existing articles for category: \(category)")
            } catch {
                print("Error deleting existing articles: \(error)")
            }
        }
    
}

//I am accessing that Main logic from below view allArticlesListView.swift

import SwiftUI

struct allArticlesListView: View {
    
    @Environment(\.managedObjectContext) private var viewContext
    @Environment(\.colorScheme) var colorScheme
    
    var fetchNews = FetchNews()
    
    @State var title = "Sports"
    @State var isGotData = false
    @State var articles : [ArticleData] = []
    
    
    
    var body: some View {
        
        if isGotData {
            
            ScrollView(.vertical){
                
                VStack(spacing : 0){
                    
                    VStack(alignment: .leading,spacing: 15) {
                        HStack{
                            Text("TOP NEWS")
                                .font(.largeTitle.bold())
                                .frame(height: 45)
                                .padding(.horizontal,15)
                        }
                        
                        GeometryReader{
                            
                            let rect = $0.frame(in: .scrollView(axis : .vertical))
                            let minY = rect.minY
                            let topValue : CGFloat = 75.0
                            
                            let offset = min(minY - topValue, 0)
                            let progress = max(min( -offset / topValue, 1), 0)
                            let scale = 1 + progress
                            
                            
                            ZStack{
                                Rectangle()
                                    .fill(Color.blue)
                                    .overlay(alignment: .leading) {
                                        Circle()
                                            .fill(Color.blue)
                                            .overlay {
                                                Circle()
                                                    .fill(.white.opacity(0.2))
                                                
                                            }
                                            .scaleEffect(2,anchor: .topLeading)
                                            .offset(x: -50,y: -40)
                                    }
                                    .clipShape(RoundedRectangle(cornerRadius: 25,style: .continuous))
                                    .scaleEffect(scale,anchor: .bottom)
                                
                                VStack(alignment: .center, spacing: 4){
                                    Text("\(title)".capitalized)
                                        .font(.title.bold())
                                }
                                .foregroundStyle(.white)
                                .frame(maxWidth: .infinity, alignment : .leading)
                                .padding(15)
                                .offset(y : progress * -25 )
                                
                            }
                            .offset(y : -offset)
                            //Moving till top value
                            .offset(y : progress * -topValue)
                            
                        }
                        .padding(.horizontal,15)
                        .containerRelativeFrame(.horizontal)
                        .frame(height: 125)
                        
                        LazyVStack(spacing: 15) {
                            ForEach(articles, id: \.self) { article in
                                NavigationLink(destination: detailedArticleView(singleArticle: article)) {
                                    VStack(alignment: .leading) {
                                        ArticleView(article: article, category: title)
                                    }
                                }
                            }
                            
                            
                        }
                        .padding(15)
                        .mask{
                            Rectangle()
                                .visualEffect { content, geometryProxy in
                                    content
                                        .offset(y : backgroundLimitOffset(geometryProxy))
                                }
                        }
                        .background{
                            GeometryReader {
                                
                                let rect = $0.frame(in: .scrollView)
                                let minY = min(rect.minY - 125, 0)
                                let progress = max(min(-minY / 25, 1), 0)
                                
                                RoundedRectangle(cornerRadius: 30 * progress,style: .continuous)
                                    .fill(colorScheme == .dark ? .black : .white)
                                    .overlay(alignment: .top){
                                        
                                    }
                                /// Limiting Background Scroll below the header
                                    .visualEffect { content, geometryProxy in
                                        content
                                            .offset(y : backgroundLimitOffset(geometryProxy))
                                    }
                                
                            }
                        }
                        
                    }
                    .padding(.vertical,15)
                    
                }
                
            }
            .scrollTargetBehavior(CustomScrollBehaviourSample())
            .scrollIndicators(.hidden)
            .background {
                
                LinearGradient(colors: [.green, .white,.cyan,.indigo], startPoint: .topLeading, endPoint: .bottomTrailing)
                    .ignoresSafeArea()
                
            }
        }else{
            
            ProgressView()
                .onAppear {
                    
                    fetchNews.fetchData(overRideValue: false, category: title, context: viewContext) { fetchedArticles in
                        articles = fetchedArticles
                        isGotData = true
                        print("Fetched articles")
                        
                    }
                    
                }
            
        }
        
        
    }
    
    
    /// Background Limit Offset
    func backgroundLimitOffset(_ proxy : GeometryProxy) -> CGFloat {
        
        let minY = proxy.frame(in: .scrollView).minY
        
        return minY < 75 ? -minY + 75 : 0
        
    }

}

//I am calling the above view from this below view CategoryView.swift

import SwiftUI

struct CategoryView: View {
    
    private var twoColumnGrid = [GridItem(.flexible()), GridItem(.flexible())]
    
    let categories : [String] = [Category.world,Category.nation,Category.business,Category.technology,Category.entertainment,Category.sports,Category.science,Category.health]
    
    
    var body: some View {
        ScrollView(){
            LazyVGrid(columns: twoColumnGrid, spacing: 20) {
                
                // Display the item
                ForEach(categories, id: \.self) { category in
                    
                    NavigationLink {
                        allArticlesListView(title : category)
                            .navigationTitle("")
                            .navigationBarTitleDisplayMode(.inline)
                    } label: {
                        Rectangle()
                            .overlay(
                                content: {
                                    Image("\(category)")
                                        .resizable()
                                        .scaledToFill() // Use scaledToFill to fill the entire rectangle
                                }
                            )
                            .overlay(alignment: .bottomLeading) {
                                ZStack {
                                    Text(category)
                                        .font(.callout)
                                        .padding(6)
                                        .foregroundColor(.white)
                                }
                                .background(Color.black)
                                .opacity(0.8)
                                .cornerRadius(10.0)
                                .padding(6)
                            }
                            .frame(maxWidth: 150, minHeight: 150)
                            .clipped() // Ensure the content is clipped to the rectangle
                            .clipShape(UnevenRoundedRectangle(topLeadingRadius: 20, bottomLeadingRadius: 20, bottomTrailingRadius: 20, topTrailingRadius: 0, style: .continuous))
                            .padding()                            
                    }
                    .navigationBarTitleDisplayMode(.large)
                }
            }
        }
        .padding()
        .scrollIndicators(.hidden)
    }
} 

I am calling the above view inside Content.swift View and Content View From @main VIEW

import SwiftUI

struct ContentView: View {
    
    @State private var selectionIndex = 0
    
    var body: some View {
        
        if (AuthService.shared.currentUser != nil) {
            TabView(selection: $selectionIndex){
                
                allArticlesListView()
                    .tabItem {
                        Label("Categories", systemImage: "rectangle.portrait.on.rectangle.portrait.angled.fill")
                    }
                    .tag("0")
                    .navigationTitle("Hello")
                
                MainView()
                    .tabItem {
                        Label("CatchUp👆🏽", systemImage: "rectangle.portrait.on.rectangle.portrait.angled.fill")
                    }
                    .tag("2")
                
                VStack{
                    Text("Go Back")
                    Button {
                        do {
                            try AuthService.shared.signOut()
                        } catch  {
                            print(error.localizedDescription)
                        }
                    } label: {
                        Text("Sign Out")
                    }
                    
                }
                .tabItem {
                    Label("More", systemImage: "rectangle.portrait.on.rectangle.portrait.angled.fill")
                }
                .tag("1")
                
                
            }
        }else if(AuthService.shared.firstLogin){
            
            onBoardingView()
            
        }else{
            SignInView()
        }
        
    }
}

import SwiftUI
import FirebaseCore

@main
struct TrendTroveApp: App {
    
    let coreDataStack = CoreDataStack.shared

    init(){
        FirebaseApp.configure()
    }
    
    var body: some Scene {
        WindowGroup {
            NavigationStack{
                ContentView()
                    .environment(\.managedObjectContext, coreDataStack.viewContext)
            }
        }
    }
}

If I run the above code i am getting error : I debugged the code and found which line is producing error – When i call the fetchNews.fetchData Method inside the allArticlesListView.swift, it will go to that method in that FetchNews Class it will go into else block of that method which calls fetchArticlesFromCoreData Method in that method the line 1 inside the do block code is giving me the error “let fetchedArticles = try context.fetch(request)”. Eventhough I am doing this in do catch block the error is not getting catched.

Intresting thing is : When i pass the allArticlesListView() [which already has defualt values] in the content view instead of CategoryView() – The code is working super fine.

I know it is long code but the thing is the code is working fine in one way so it means there is no problem from entites, thier manual declarations, they confirming to decodable and their relationship.
Problem Might be in CoreDataStack, how i am passing persistance container and logic in FetchNews or any backGround thread running unkowingly or some other problem. But to show the full picture i have added everything. It would be great if someOne analyse this and provide some solutions or suggestions, As a junior developer i am open to discuss and learn.

Latest articles

spot_imgspot_img

Related articles

Leave a reply

Please enter your comment!
Please enter your name here

spot_imgspot_img