I am working on an app that stores posts from on-line Json to CoreData for offline content. It is a Real Estate Listings app where users sell and rent properties.
So far i accomplish to save fethed listings in CoreData but I can not implement "Add to Favourites" for the existing entries. I use NSPredicate to filter the listings types .in different views
I have created a entity "Listing" Core Data - Listing
And a "Favorite" entity Core Data - Favorites
My question is: How can i achieve "Add to Favourite"
Option A: Save the "is favourite" listing one more time in another Entity.
Option B: Create in the "Listing" Entity another "is favourite" property and keep it in Core Data as long as is favourite?
// Here is my Listing Model.swift
struct ListingModel: Hashable, Decodable, Encodable, Identifiable {
var id : Int
var title : String
var category : String
var name : String
var image : String
var publishdate : String
var saleprice : Int = 0
var rentprice : Int = 0
var listingtype : String
var latitude : Double!
var longitude : Double!
}
// JSONViewModel.swift
import SwiftUI
import CoreData
class JSONViewModel: ObservableObject {
@Published var listings: [ListingModel] = []
// @Published var tags :[Tags] = []
// saving Json to Core Data...
func saveData(contex: NSManagedObjectContext) {
listings.forEach { (data) in
let entity = Listing(context: contex)
entity.title = data.title
entity.name = data.name
entity.category = data.category
entity.image = data.image
entity.publishdate = data.publishdate
entity.tagline = data.tagline
entity.content = data.content
entity.coverimage = data.coverimage
entity.saleprice = Int64(truncating: NSNumber(value: data.saleprice))
entity.rentprice = Int64(truncating: NSNumber(value: data.rentprice))
entity.phone = data.phone
entity.email = data.email
entity.area = Int64(truncating: NSNumber(value: data.area))
entity.rooms = Int64(truncating: NSNumber(value: data.rooms))
entity.beds = Int64(truncating: NSNumber(value: data.beds))
entity.bathrooms = Int64(truncating: NSNumber(value: data.bathrooms))
entity.expires = data.expires
entity.adresa = data.adresa
entity.listingtype = data.listingtype
entity.latitude = Double(truncating: NSNumber(value: data.latitude))
entity.longitude = Double(truncating: NSNumber(value: data.longitude))
}
// }
// saving all pending data at once
do{
try contex.save()
print("success")
}
catch{
print(error.localizedDescription)
}
}
func fetchData(context: NSManagedObjectContext){
let url = "https://... my api adress"
var request = URLRequest(url: URL(string: url)!)
request.addValue("swiftui2.0", forHTTPHeaderField: "field")
let session = URLSession(configuration: .default)
session.dataTask(with: request) { (data, res, _) in
guard let jsonData = data else{return}
// check for errors
let response = res as! HTTPURLResponse
// checking by status code
if response.statusCode == 404 {
print("error Api Errror")
}
// fetching JSON Data ..
do {
let listings = try JSONDecoder().decode([ListingModel].self, from: jsonData)
DispatchQueue.main.async {
self.listings = listings
self.saveData(contex: context)
}
}
catch {
print(error.localizedDescription)
}
}
.resume()
}
// try to extend the function
func addListingToFavourites(favouritelisting:ListingModel) {
addRecordToFavourites(favouritelisting:favouritelisting.title)
}
func isListingFavourite(favouritelisting:ListingModel) -> Bool {
if let _ = fetchRecord(favouritelisting:favouritelisting.title) {
return true
}
return false
}
func removeListingFromFavourites(favouritelisting:ListingModel) {
removeRecordFromFavourites(favouritelisting:favouritelisting.title)
}
func toggleFavourite(favouritelisting:ListingModel) {
if isListingFavourite(favouritelisting: favouritelisting) {
removeListingFromFavourites(favouritelisting: favouritelisting)
}
else {
addListingToFavourites(favouritelisting: favouritelisting)
}
}
}
and i created also extension JSONViewModel:
extension JSONViewModel {
private func addRecordToFavourites(favouritelisting:String) {
guard let context = managedContext else {return}
if let record = fetchRecord(favouritelisting:favouritelisting) {
print("record \(record) already exists")
return
}
let entity = NSEntityDescription.entity(forEntityName: "Favourite",
in: context)!
let favourite = NSManagedObject(entity:entity, insertInto:context)
favourite.setValue(favouritelisting, forKeyPath:"favouritelisting")
do {
try context.save()
}
catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
self.changed = true
}
private func fetchRecord(favouritelisting:String) -> Favourite? {
guard let context = managedContext else {return nil}
let request = NSFetchRequest<Favourite>(entityName: "Favourite")
request.predicate = NSPredicate(format: "favouritelisting == %@", favouritelisting)
if let users = try? context.fetch(request) {
if users.count > 0 {
return users[0]
}
}
return nil
}
private func removeRecordFromFavourites(favouritelisting:String) {
guard let context = managedContext else {return}
if let record = fetchRecord(favouritelisting:favouritelisting) {
context.delete(record)
self.changed = true
}
}
}
::: Am i on the wright way? still i don't know it this what i should do!
Please find bellow Latest listingsView
import SwiftUI
struct latestListings: View {
@StateObject var jsonModel = JSONViewModel()
@Environment(\.managedObjectContext) var context
@FetchRequest(entity: Listing.entity(),
sortDescriptors:
[NSSortDescriptor(keyPath: \Listing.publishdate, ascending: false)])
var results : FetchedResults<Listing>
var textHeight: CGFloat = 60
var fullWidth: CGFloat = UIScreen.main.bounds.width
var cardWidthHalf: CGFloat = UIScreen.main.bounds.width / 2 + UIScreen.main.bounds.width / 3
var spacing: CGFloat = 10
var viewHeight: CGFloat = UIScreen.main.bounds.height / 2
@State private var isError = false
var body: some View {
VStack(alignment: .leading, spacing: 20) {
VStack(alignment: .leading, spacing: 10) {
HStack {
HStack {
Text("Latest Listings")
.modifier(textSectionTitle())
Spacer()
NavigationLink (destination: AllListingsVertical()) {
ViewMoreButton()
}.buttonStyle(PlainButtonStyle())
}
.padding(.trailing, 20)
}
Divider()
.modifier(dividerStyle())
HStack {
Image(systemName: "wand.and.stars.inverse")
.modifier(textSectionIcon())
Text("Manualy download listings to device storage. Useful for offline use.")
.modifier(textSectionTagline())
}
}
.padding(.top, 10)
.padding(.leading, 20)
.padding(.bottom, 0)
ScrollView(.horizontal, showsIndicators: false, content: {
HStack {
// checkin if core data exists
if results.isEmpty{
if jsonModel.listings.isEmpty{
HStack(alignment: .center) {
HStack(spacing: 10) {
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: Color("dinamicPillsGrass")))
.scaleEffect(2, anchor: .center)
// fetching data
.onAppear(perform: {
jsonModel.fetchData(context: context)
})
}.frame(width: UIScreen.main.bounds.width)
}.modifier(cardHeight())
// when array is clear indicator appears
// as result data is fetched again
}
else{
HStack(spacing: 20) {
ForEach(jsonModel.listings,id: \.self){listing in
NavigationLink (destination: CardDetailView(listing: listing)) {
HStack {
CardViewOrizontal(listing: listing)
}
}.buttonStyle(PlainButtonStyle())
// display fetched Json Data..
}
}
}
}
else{
// results.prefix(?) unde ? cata articole sa arate
HStack(spacing: 20) {
ForEach(results.prefix(10)){listing in
NavigationLink (destination: CardDetailView(fetchedData: listing)) {
HStack {
CardViewOrizontal(fetchedData: listing)
}
}.buttonStyle(PlainButtonStyle())
}
}
.padding(.trailing, 15)
.padding(.leading, 15)
}
// update finish
}.padding(.top, 10)
.padding(.bottom, 10)
})
VStack(alignment: .center) {
Button(action: {
// clearing data in core data..
if Reachability.isConnectedToNetwork() {
//
do{
jsonModel.listings.removeAll()
results.forEach { (listing) in context.delete(listing) }
try context.save()
}
catch{
print(error.localizedDescription)
}
print("Network is connected")
self.isError = false
} else {
print("Network is not connected")
self.isError = true
}
}, label: {
HStack(alignment: .center) {
Image(systemName: "icloud.and.arrow.down")
.modifier(textSectionIcon())
Text("Update")
.modifier(textSectionTagline())
}
.padding(5)
.padding(.trailing, 5)
.background(Color("blueLeading"))
.cornerRadius(20)
.modifier(shadowPills())
}).alert(isPresented: $isError) {
Alert(title: Text("Network is not connected"),
message: Text("WiFi or Cellular not availible. You can still browse offline content!"),
dismissButton: .default(Text("OK")))
}
}.frame(width: fullWidth)
}
.padding(.top, 10)
.padding(.bottom, 60)
.frame(width: fullWidth)
.background(LinearGradient(gradient: Gradient(colors: [Color("dinamicGray1"), Color("dinamicGray2")]), startPoint: .top, endPoint: .bottom))
.cornerRadius(20)
.padding(.top, -50)
.modifier(shadowSection())
}
}
And CardDetailView
//
// CardDetailView.swift
// WebyCoreData
//
// Created by Marius Geageac on 20.12.2020.
//
import SwiftUI
import KingfisherSwiftUI
import MapKit
struct CardDetailView: View {
// noul liked
var fullWidth: CGFloat = UIScreen.main.bounds.width
var halfScreenH: CGFloat = UIScreen.main.bounds.height / 2
@ObservedObject var settingsVM = SettingsViewModel()
@State private var isVisible = false
var listing: ListingModel?
var fetchedData: Listing?
// Modifiers
var cardWidth: CGFloat = UIScreen.main.bounds.width
var imageWidth: CGFloat = UIScreen.main.bounds.width
/// map
var locationCoordinate: CLLocationCoordinate2D {
CLLocationCoordinate2D(
latitude: listing == nil ? fetchedData!.latitude : listing!.latitude,
longitude: listing == nil ? fetchedData!.longitude : listing!.longitude)
}
let paddingPills: CGFloat = 5
let textSizePills: CGFloat = 14
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 5) {
HStack {
Text(listing == nil ? fetchedData!.title! : listing!.title)
.modifier(textSectionTitle())
}
Divider()
.modifier(dividerStyle())
HStack {
Image(systemName: "info.circle")
.modifier(textSectionIcon())
Text(listing == nil ? fetchedData!.tagline! : listing!.tagline)
.modifier(textSectionTagline())
}
}
.padding(.top, 10)
.padding(.leading, 20)
.padding(.bottom, 0)
if self.fetchedData!.isFavorite == false {
Button(action: {
fetchedData!.isFavorite.toggle()
}) {
Image(systemName: "heart.circle")
.modifier(textSectionIcon())
}.padding()
}
else {
Button(action: {
fetchedData!.isFavorite.toggle()
}) {
Image(systemName: "heart.circle.fill")
.modifier(textSectionIcon())
}.padding()
}
}
}
}
import SwiftUI
struct Favorites: View {
@StateObject var jsonModel = JSONViewModel()
var cardWidth: CGFloat = UIScreen.main.bounds.width - 30
var fullWidth: CGFloat = UIScreen.main.bounds.width
// @StateObject var jsonModel = JSONViewModel() @Environment(.managedObjectContext) var context
// Fetching Data From Core Data..
@FetchRequest(entity: Listing.entity(), sortDescriptors:
// [NSSortDescriptor(keyPath: \Listing.publishdate, ascending: false)]) [NSSortDescriptor(keyPath: \Listing.publishdate, ascending: false),],predicate: NSPredicate(format: "isFavourite == %@" , NSNumber(value: true)))
var results : FetchedResults<Listing>
var body: some View {
ScrollView(.vertical) {
VStack(alignment: .center) {
VStack(alignment: .center) {
LazyVStack(spacing: 20) {
ForEach(results){listing in
NavigationLink (destination: CardDetailView(fetchedData: listing)) {
VStack {
CardView(fetchedData: listing)
}.frame(width: UIScreen.main.bounds.width)
.modifier(cardHeight())
}.buttonStyle(PlainButtonStyle())
}
}
}
}.padding(.top, 20)
}
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
allListingsTitlePill() // Title
}
ToolbarItem(placement: .navigationBarTrailing){
HStack {
Button(action: {
}, label: {
Image(systemName: "heart.circle.fill")
.font(.system(size: 40, weight: .regular))
})
}
}
}
}
}