oculorum
oculorum

Reputation: 49

Core data - Searches for a single object that has a given identifier

I am creating an application in SwiftUI that will be a songbook. I would like, from the view where I have the song content, to click on a button so that a window appears where I can enter the number of the song I want to switch to (each song has its own number). After typing in the number, it's supposed to take me to a view where the song content for that number is. I'm not quite able to pull a song with a given number from the database and display it in the view. Below I am sending my code. I am counting on your help.

CoreData Model:

enter image description here

PersistenceController:

struct PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentContainer

init() {
    container = NSPersistentContainer(name: "Model")
    container.loadPersistentStores { (description, error) in
        if let error = error {
            fatalError("Error \(error.localizedDescription)")
        }
    }
}

func save(completion: @escaping (Error?) -> () = {_ in}) {
    let context = container.viewContext
    if context.hasChanges {
        do {
            try context.save()
            completion(nil)
        } catch {
            completion(error)
        }
    }
}
    
func delete(_ object: NSManagedObject, completion: @escaping (Error?) -> () = {_ in}) {
    let context = container.viewContext
    context.delete(object)
    save(completion: completion)
}

func getSong(number: String) -> Song {
    var song = Song()
    let context = container.viewContext
    let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Song")
    request.predicate = NSCompoundPredicate(format: "number <> '\(number)'")
    
    do {
        song = try context.fetch(request) as? Song ?? Song()
      } catch let error as NSError {
        print("Could not fetch. \(error), \(error.userInfo)")
      }
    return song
}

}

DetailView (a view in which we have the content of the song):

struct DetailView: View {
@State var isSelected: Bool
    var body: some View {
        VStack{
            Text(song.content ?? "No content")
                .padding()
            Spacer()
        }
        .navigationBarTitle("\(song.number). \(song.title ?? "No title")", displayMode: .inline)
        .toolbar {
            ToolbarItemGroup(placement: .navigationBarTrailing) {
                HStack{
                    Button(action: {
                        song.favorite.toggle()
                        isSelected=song.favorite
                    }) {
                        Image(systemName: "heart.fill")
                            .foregroundColor(isSelected ? .red : .blue)
                }
                
                Button(action: {
                    searchView()
                }) {
                    Image(systemName: "magnifyingglass")
                }
            }
        }
    }
}
func searchView(){
    
    let search = UIAlertController(title: "Go to the song", message: "Enter the number of the song you want to jump to.", preferredStyle: .alert)
    search.addTextField {(number) in
        number.placeholder = "Song number"
    }
    let go = UIAlertAction(title: "Go", style: .default) { (_) in
        let song = PersistenceController.shared.getSong(number: (search.textFields![0].text)!)
        DetailView(song: song, isSelected: song.favorite)
    }
    
    let cancel = UIAlertAction(title: "Cancel", style: .destructive) { (_) in
        print("Cancel")
    }
    search.addAction(cancel)
    search.addAction(go)
    UIApplication.shared.windows.first?.rootViewController?.present(search, animated: true, completion: {
    })
}

}

Upvotes: 1

Views: 1153

Answers (1)

vadian
vadian

Reputation: 285290

The getSong method cannot work, the predicate is wrong, the fetch returns always an array, and it's highly recommended to specify the static type of the entity rather than unspecified NSFetchRequestResult

And as song is obviously an NSManagedObject subclass you cannot create an instance with the default initializer Song(). Better return an optional

func getSong(number: String) -> Song? {
    guard let songNumber = Int64(number) else { return nil }
    let context = container.viewContext
    let request = NSFetchRequest<Song>(entityName: "Song")
    request.predicate = NSPredicate(format: "number == %ld", songNumber)
    
    do {
        return try context.fetch(request).first
    } catch {
        print(error)
        return nil
    }
}

Upvotes: 2

Related Questions