How i can use FetchedResults in coredata for populating an array? - core-data

I have been able to implement MVVM in swiftui with coredata and the info shows up as i want, so all is well there , i attach the image of fetched data...
However one issue i am facing is i am unable to fetch the results from coredata and pass it to an array using FetchedResults, i am currently using the below code it works with demo data but not with FetchedResults , can any one kindly help with this as to how to populate an Array with the results of FetchedResults from coredata...because the data that flows in the MVVM will come from this repository itself ... thanks
import Foundation
import SwiftUI
import CoreData
import AVFoundation
import Combine
class SongRepository: ObservableObject, Identifiable {
#Published var song = [Song]()
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(entity: Artists.entity(), sortDescriptors: []) var artists1: FetchedResults<Artists>
init() {
loadData()
}
func loadData(){
//Does NOT WORK
// song = artists1.map {
// artist in
// Song(album: artist.album, artistImage: artist.artistImage, artistname: artist.artistname!, genre: artist.genre, songMp3: artist.songMp3, songname: artist.songname)
//Works with demo data like this
song = [Song(album: "Raga Max", artistImage: nil, artistname: "Denny Jack", genre: "Classic", songMp3: nil, songname: "Love"),Song(album: "Rendition", artistImage: nil, artistname: "Ron Peterson", genre: "Western", songMp3: nil, songname: "Western Beats"),Song(album: "Ricardo", artistImage: nil, artistname: "Mahoba Rips", genre: "Folk", songMp3: nil, songname: "Rugged Paths")]
}
}

Related

Unable to use ForEach for distinct records from coredata

I have a table that contains 20 songs from 5 different artists, this number can go up and down , now i want a View where i can display a navigation of these unique singers with their images that are also in table.
So i need only distinct records based on artistname, i query coredata but when i use it in View , i get Generic struct 'ForEach' requires that 'NSFetchRequest<NSDictionary>' conform to 'RandomAccessCollection'
Now to overcome this i use id:.self, this also does not work.
Now i read and learn that ForEach does not like any TYPE that is not sorted , but i have not been able to find a way around this, can any one kindly suggest any solutions, thanks.
This is how i fetch the unique records from core data
let fetchRequest: NSFetchRequest<NSDictionary> = NSFetchRequest(entityName: "Artists")
init(songLVM: Binding<SongListVM>){
_songLVM = songLVM
fetchRequest.propertiesToFetch = ["artistname"]
fetchRequest.returnsDistinctResults = true
fetchRequest.resultType = .dictionaryResultType
}
Below is the entire file
import Foundation
import SwiftUI
import CoreData
struct ArtistList: View {
#Environment(\.managedObjectContext) var managedObjectContext
#Binding var songLVM: SongListVM
#State var namesFilter = [String]()
//-----
let fetchRequest: NSFetchRequest<NSDictionary> = NSFetchRequest(entityName: "Artists")
init(songLVM: Binding<SongListVM>){
_songLVM = songLVM
fetchRequest.propertiesToFetch = ["artistname"]
fetchRequest.returnsDistinctResults = true
fetchRequest.resultType = .dictionaryResultType
}
//-----
var body: some View {
List {
ForEach(fetchRequest) { <---- Error here
idx in
NavigationLink(
destination: ArtistSongs(artistName: idx.artistname ?? "no name", songLVM: $songLVM)) {
ZStack(alignment: .bottomTrailing) {
Image(uiImage: UIImage(data: idx.artistImage ?? Data()) ?? UIImage())
.resizable()
.frame(width: 350, height: 350, alignment: .center)
Text(idx.artistname ?? "no name")
.font(Font.system(size: 50, design: .rounded))
.foregroundColor(.white)
}
}
}
}
}
}
once you figure out what your dictionary is, try this in your ForEach loop:
ForEach(dict.sorted(by: >), id: \.key) { key, value in
...
}
EDIT1:
#FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Artists.artistname, ascending: true)],
animation: .default)
private var artists: FetchedResults<Artists>
...
ForEach(artists, id: \.self) { artist in
...
}

SwiftUI CoreData - How to update the fetch request and the list

I am creating an application in SwiftUI using CoreData and I have a problem. In application you can add song to favorites and it will be added to list (FavoriteSongsView). Until the song is added to favorites everything is fine. In DetailView I click the button and the "heart.fill" icon and the song is added to the list. However, if I click on the icon again to un-favorite the song, it does not disappear from the list. I fought with it a little bit but without any effect. Could you please point out the cause of the problem?
List of favorite songs:
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(
entity: Song.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \Song.number, ascending: true)],
predicate: NSPredicate(format: "favorite <> 'false'")
) var songs: FetchedResults<Song>
var body: some View {
NavigationView{
VStack{
List {
ForEach(songs, id:\.self){ song in
NavigationLink(destination: DetailView(song: song, isSelected: song.favorite)) {
HStack{
Text("\(song.number). ") .font(.headline) + Text(song.title ?? "No title")
}
}
}
}
}
.listStyle(InsetListStyle())
.navigationTitle("Favorite")
}
}
}
Detailed view:
struct DetailView: View {
#State var song : Song
#State var isSelected: Bool
#State var wrongNumber: Bool = false
var body: some View {
VStack{
Text(song.content!)
.padding()
Spacer()
}
.navigationBarTitle("\(song.number). \(song.title ?? "No title")", displayMode: .inline)
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
HStack{
Button(action: {
song.favorite.toggle()
PersistenceController.shared.save()
isSelected=song.favorite
}) {
Image(systemName: "heart.fill")
.foregroundColor(isSelected ? .red : .blue)
}
Button(action: {
alert()
}) {
Image(systemName: "1.magnifyingglass")
}
NavigationLink("DetailView", destination: DetailView(song: song, isSelected: isSelected))
.frame(width: 0, height: 0)
.hidden()
}
}
}
}
}
Use this NSPredicate
NSPredicate(format: "favorite = %d", false)

Updating core data entity with observedobject

I have created a list of clients which get saved in core data. I added some attributes through an AddView and now I am trying to add a new attribute using a different view. In order to do that I understood I need to use the observedObject property wrapper so that it doesn't create a new client but update the existing one. This is the code:
import SwiftUI
import CoreData
struct TopicView: View {
#Environment(\.managedObjectContext) var managedObjectContext
#Environment (\.presentationMode) var presentationMode
#ObservedObject var topic: StudentData
#State var content = ""
#State private var show = false
#StateObject private var keyboard = Keyboard()
var body: some View {
Form{
Section (header: Text("Topics covered")
.bold()
.padding(.all)
.font(.title3)) {
TextEditor(text: Binding(
get: {self.topic.content ?? ""},
set: {self.topic.content = $0 } ))
.frame(height: 250)
}
}.onTapGesture {
hideKeyboard()
}
}
}
Button ("Submit")
{
self.topic.content = self.content
self.topic.objectWillChange.send()
try? self.managedObjectContext.save()
self.presentationMode.wrappedValue.dismiss()
}.foregroundColor(.white)
This is the view where the new attribute should be shown:
import SwiftUI
import CoreData
import MapKit
struct StudentView: View {
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(entity: StudentData.entity(),
sortDescriptors: [],
animation: .spring()) var content: FetchedResults<StudentData>
#Environment (\.presentationMode) var presentationMode
#ObservedObject var topic: StudentData
#State var showModalView = false
#State private var showingSheet = false
let myStudent: StudentData
var body: some View {
NavigationView {
VStack{
Text("Topics Covered")
.font(.system(size: 30, weight: .bold, design: .rounded))
.foregroundColor(.red)
.padding(.horizontal)
Text("\(myStudent.content ?? "")")
.font(.title2)
.bold()
.foregroundColor(.white)
.padding(.horizontal) }}
Spacer()
Spacer()
}.sheet(isPresented: $showModalView, content: {
TopicView(topic: topic)})
The problem is, if I type in the texteditor some text without save it then it shows correctly in the other view. However, when I save it, it does not show in the other view.
Is there anything wrong in my save code?

How do I use a property as an argument to a FetchRequest predicate

I am trying to select database items with a predicate before displaying a view and need to pass a property, but I'm in a catch-22 situation since the property may not be initialized, yielding the message: Cannot use instance member 'subject' within property initializer; property initializers run before 'self' is available
struct ShowInfo: View
{
#State var subject: Subject
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: Info.entity(), sortDescriptors: [NSSortDescriptor(key: "title", ascending: true)],
predicate: NSPredicate(format: "subject.title == %#", $subject.title)
) var infos: FetchedResults<Info>
#State var size = UIScreen.main.bounds.width / 3
var body: some View
{
List
{
Text("Info").font(.system(size: 24)).foregroundColor(Color.green)
ForEach(infos, id: \.infoid)
{ info in
ZStack
{
if info.subject == self.subject.title
{
NavigationLink(destination: EditInfoView(info: info))
{
HStack
{
Text(info.title!).frame(width: 150, height: 40).background(Color.blue).foregroundColor(Color.white).cornerRadius(10)
Text(info.value!)
}
}
}
}
}.onDelete(perform: deleteInfo)
}
}
}
The answer found at SwiftUI View and #FetchRequest predicate with variable that can change is essentially correct, but I discovered that the order in which things appear inside the struct matters to avoid a slew of errors.
I modified my code as follows...
struct ShowInfo: View
{
init (subject: Subject)
{
self.subject = subject
self.infoRequest = FetchRequest<Info>(entity: Info.entity(), sortDescriptors: [NSSortDescriptor(key: "title", ascending: true)],predicate: NSPredicate(format: "subject.title == %#", subject.title!))
}
#Environment(\.managedObjectContext) var moc
var subject: Subject
var infoRequest: FetchRequest<Info>
var infos: FetchedResults<Info>{infoRequest.wrappedValue}
This cleared up all the errors and allowed the app to work correctly. When the init() was after the variable declarations, I got the same error as before as well as other errors. The compiler seemed to be getting confused about what had already been initialized and what had not. I'm not sure why the example shown for question 57871088 worked for others and mine required rearranging the declarations.
I thought the above answer solved my problem, and it did in that case, but in another view the same methodology yielded errors claiming that I hadn't initialized all the properties within the init. After much experimenting, I discovered all those errors went away if I commented out the #Environment statement. Of course that generated errors for the undefined variable moc, so I commented that code out to see what happened, then the original errors reappeared. It seems clear that the compiler is getting confused. Does anyone know a way around that issue?
Here's the code I'm working with now:
struct EditSubjectView: View
{
init(subject: Subject)
{
self.subject = subject
self.formRequest = FetchRequest(entity: Field.entity(), sortDescriptors: [NSSortDescriptor(key: "sequence", ascending: true)], predicate: NSPredicate(format: "subjectid == %#", subject.subjectid!.description))
}
#Binding var subject: Subject
var formRequest: FetchRequest<Field>
var fields : FetchedResults<Field>{formRequest.wrappedValue}
#Environment(\.managedObjectContext) var moc
var body: some View
{
return VStack
{
HStack
{
Text("Subject Title").padding(.leading)
//Spacer()
}
TextField(subject.title!, text: Binding($subject.title)!)
.padding(.all)
.background(Color(red: 239.0/255.0, green: 243.0/255.0, blue: 244.0/255.0, opacity: 1.0))
.cornerRadius(15)
Spacer()
Button("Save")
{
do
{
try self.moc.save()
}
catch
{
print(error.localizedDescription)
}
}.frame(width: 150, height: 30).background(Color.red).foregroundColor(Color.white).cornerRadius(15.0)
}.padding()
}
}

edit a Core Data Object in SwiftUI

I push an object that comes from the Core Data Database to a detail page with text fields.
When the user changes the text in the textfields and presses save the changes should be saved to the core data DB.
Problem: I have no clue how to modify/update/change an existing core data entity.
I probably need to get the original via a #FetchRequest but every time I try I get some problems.
Question 1: Let's say the entity has object.id as UUID, how can I
fetch that object in SwiftUI?
Question 2: How can I overwrite the fetched object with the changed
content of the textfields?
struct ProductDetail: View {
#State var barcode: Barcode
#Environment(\.presentationMode) var presentationMode
#Environment(\.managedObjectContext) var context
//let datahandler = Datahandler()
var body: some View {
VStack {
HStack {
Text("Barcode: ")
Spacer()
TextField("Barcode", text: Binding($barcode.code, ""))
.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(width: 250, alignment: .trailing)
}
HStack {
Text("Amount: ")
Spacer()
TextField("Amount", text: Binding($barcode.amount, ""))
.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(width: 250, alignment: .trailing)
.keyboardType(.numberPad)
}
HStack{
Button("Back") {
self.presentationMode.wrappedValue.dismiss()
}
Spacer()
Button("Save"){
//self.datahandler.updateBarcode(barcode: self.object)
self.editBarcode(barcode: barcode)
self.presentationMode.wrappedValue.dismiss()
}
}
.padding()
}
.padding()
}
func editBarcode(barcode: Barcode) {
// Question 1: Fetch Original object using barcode.id
// Question 2: How to but barcode into context so it can overwrite the core data original?
try? context.save()
}
Attempt:
func editBarcode(barcode: Barcode) {
#FetchRequest(sortDescriptors: [], predicate: NSPredicate(format: "self.id IN %#", barcode.id)) var results: FetchedResults<Barcode>
results.first?.amount = barcode.amount
results.first?.code = barcode.code
try? context.save()
}
Errors:
Argument type 'UUID?' does not conform to expected type 'CVarArg'
Cannot use instance member 'barcode' within property initializer;
property initializers run before 'self' is available
Property wrappers are not yet supported on local properties

Resources