I am creating custom data and I want to save them into CoreData when I make favorites. In order to do that I use the Combine framework by subscribing CoreData values back into my custom data. The problem is when I try to map both CoreData and custom data, there is something wrong and I couldn't display even my custom data on the canvas. To be honest, I don't even know what I am doing because most of the ViewModel codes are based on Nick's tutorial video (from the Swiftful Thinking Youtube channel). Please help me with what is wrong with my codes. Thanks in advance.
I create my CoreData with a name "DataContainer" with entity name "DataEntity". In DataEntity, there are three attributes:
'id' with a type "Integer32"
'isFavorite' with a type "Boolean"
'timestamp' with a type "Date"
import Foundation
import CoreData
// This is CoreData class without using Singleton
class CoreDataManager {
private let container: NSPersistentContainer
@Published var savedEntities: [DataEntity] = []
init() {
container = NSPersistentContainer(name: "DataContainer")
container.loadPersistentStores { _, error in
if let error = error {
print("Error loading CoreData! \(error)")
}
}
fetchData()
}
// MARK: Privates
private func fetchData() {
let request = NSFetchRequest<DataEntity>(entityName: "DataEntity")
do {
savedEntities = try container.viewContext.fetch(request)
} catch let error {
print("Error fetching DataEntity! \(error)")
}
}
// Add to CoreData
private func addFavorite(dataID: DataArray, onTappedFavorite: Bool) {
let newFavorite = DataEntity(context: container.viewContext)
newFavorite.id = Int32(dataID.id)
newFavorite.isFavorite = onTappedFavorite
applyChanges()
}
// Update time
private func updateTime() {
let newTime = DataEntity(context: container.viewContext)
newTime.timestamp = Date()
}
// Save to CoreData
private func save() {
do {
try container.viewContext.save()
} catch let error {
print("Error saving to CoreData! \(error)")
}
}
private applyChanges() {
save()
updateTime()
fetchData()
}
private func update(entity: DataEntity, updateFavorite: Bool) {
entity.isFavorite = updateFavorite
applyChanges()
}
private func delete(entity: DataEntity) {
container.viewContext.delete(entity)
applyChanges()
}
// MARK: Public
func updateFavorite(dataID: DataArray, onTappedFavorite: Bool) {
// Checking the data is already taken
if let entity = savedEntities.first(where: { $0.id == dataID.id }) {
if onTappedFavorite {
update(entity: entity, updateFavorite: onTappedFavorite)
} else {
delete(entity: entity)
}
} else {
addFavorite(dataID: dataID, onTappedFavorite: onTappedFavorite)
}
}
}
This will be my Model:
import Foundation
import SwiftUI
struct DataArray: Identifiable {
let id: Int
let cities: String
let name1: String
let name2: String
let isFavorite: Bool
func updateFavorite(favorited: Bool) -> DataArray {
return DataArray(id: id, cities: cities, name1: name1, name2: name2, isFavorite: favorited)
}
}
public struct ListDataArray {
static let dot = [
DataArray(id: 1,
cities: "Baltimore"
name1: "John",
name2: "Mike",
isFavorite: False),
DataArray(id: 2,
cities: "Frederick"),
name1: "Joe",
name2: "Swift",
isFavorite: False),
DataArray(id: 3,
cities: "Catonsville"
name1: "Susan",
name2: "Oliver",
isFavorite: False),
// There will be a lot of data
]
}
This will be my Home ViewModel:
import Foundation
import SwiftUI
import Combine
class Prospect: ObservableObject {
@Published var datas: [DataArray] = []
private let coreDataServices = CoreDataManager()
private var cancellables = Set<AnyCancellable>()
init() {
fetchDataArrays()
fetchCoreData()
}
private func fetchDataArrays() {
let items = ListDataArray.dot
datas = items
}
private func fetchCoreData() {
coreDataServices.$savedEntities
.map({ (coreData) -> [DataArray] in
// Here is something wrong when I check and try to convert CoreData to DataArray
let arrays: [DataArray] = []
return arrays
.compactMap { (data) -> DataArray? in
guard let entity = coreData.first(where: { $0.id == data.id }) else {
return nil
}
return data.updateFavorite(favorited: entity.isFavorite)
}
})
.sink {[weak self] (receivedEntities) in
self?.datas = receivedEntities
}
.store(in: &cancellables)
}
func updateFavoriteData(dataID: DataArray, isFavorite: Bool) {
coreDataServices.updateFavorite(dataID: dataID, onTappedFavorite: isFavorite)
}
// To View Favorite
@Published var showFavorite: Bool = false
}
This is my View:
import SwiftUI
struct Home: View {
@EnvironmentObject var items: Prospect
var body: some View {
ScrollView {
LazyVStack {
ForEach(items.datas) { data in
VStack {
HStack {
Button {
//Action for making favorite or unfavorite
items.updateFavoriteData(dataID: data, isFavorite: data.isFavorite)
} label: {
Image(systemName: data.isFavorite ? "suit.heart.fill" : "suit.heart")
}
Spacer()
Button {
items.showFavorite.toggle()
} label: {
Image(systemName: "music.note.house.fill")
}
.sheet(isPresented: $items.showFavorite) {
FavoriteView()
.environmentObject(items)
}
}
Text("\(data.id)")
.font(.title3)
Text(data.cities)
.font(.subheadline)
Spacer()
}
padding()
}
.padding()
}
}
}
}
struct FavoriteView: View {
@EnvironmentObject var items: Prospect
var body: some View {
VStack {
List {
ForEach(items.datas) { data in
if data.isFavorite {
VStack(spacing: 10) {
Text(data.cities)
Text(data.name1)
Text(data.name2)
}
.font(.body)
}
}
.padding()
}
Spacer()
}
}
}
struct Home_Previews: PreviewProvider {
static var previews: some View {
Home()
.environmentObject(Prospect())
}
}
arraysas an empty array (let arrays: [DataArray] = []) and then you runcompactMapon it. It will never return anything, becausearraysis empty. Maybe you mean to be mappingcoreDatainstead? It's unclear to me what the objective is in that function.coreData, which is[DataArray]. What you want to do with that is unclear to me.