Reputation: 47
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))
})
}
}
}
}
}
Upvotes: 0
Views: 883
Reputation: 291
I would personally just add another property in your listings entity called isFavourite and set it to a boolean. set it initially to false.
Then when you are doing your fetch requests, you can only show favourites using a predicate like this.
let predicateIsFavourite = NSPredicate(format: "isFavourite == %@", NSNumber(value: true))
and in lists / ForEach, you display whether it is a favourite and use a button to toggle it being a favourite. The toggle would just set the value of isFavourite to true (let me know if you would like some code for that but looking at your question, it seems like you know how to do that)
Upvotes: 2