Filtration coredata model in tableView - swift

I have two models in my Coredata like:
CountryCD {
#NSManaged public var id: Int16
#NSManaged public var title: String?
#NSManaged public var cities: NSSet?
}
and
CityCD {
#NSManaged public var id: Int16
#NSManaged public var title: String?
#NSManaged public var country: CountryCD?
}
and showing it in tableView with some quantity of sections and rows
country[section].cities[indexPath.row]
I'm trying to add filtration in tableView
code for this right now is:
*
let filtered = countries
.compactMap { (($0.cities as? Set<CityCD>)?
.filter { $0.title?.lowercased()
.contains(text.lowercased()) ?? false } ?? []) }
.filter { !$0.isEmpty }
and it work almost how I need, it gives me [[CityCD]], but I need [CountryCD] to show filtration result in my tableView.
Can someone help me to figure out how to fix this?
source Swift Filter Nested Array

If anyone will face problem aka I did, here is solution I reached with help of #Joakim Danielson clue
in model:
var countriesDict: [CountryCD: Set<CityCD>]
var filteredCountriesDict: [CountryCD: Set<CityCD>]
when populate:
countryDict = savedCountries.reduce([CountryCD: Set<CityCD>]()) { [$1: $1.cities as? Set<CityCD> ?? [] ] }
filteredCountryDict = [:]
tableView.reloadData()
in numberOfSections:
return isFiltering ? filteredCountryDict.count : countryDict.count
in numberOfRowsInSection:
return isFiltering ? Array(filteredCountryDict)[section].value.count :
Array(countryDict)[section].value.count
in cellForRowAt:
let dictionary = .isFiltering ?
Array(filteredCountryDict)[indexPath.section] :
Array(countryDict)[indexPath.section]
let cities = (dictionary as NSSet).allObjects as? [CityCD]
cell.textLabel?.text = cities?[indexPath.row].title
when filtering:
let text = searchingText?.lowercased()
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
isFiltering = !(text == "")
if isFiltering {
countryDict.forEach { (key, value) in
self.filteredCountryDict[key] = value.filter({$0.title?.lowercased().contains(text) ?? false})
}
}else{
filteredCountryDict.removeAll()
}
tableView.reloadData()
hoping would be useful

Related

Firestore responding with "cannot find 'cards' in scope"

I followed this tutorial to get data from firestore and changed what i needed to correspond to my model but it keeps responding with "cannot find 'cards' in scope" and I'm not sure what i did wrong. (i think i got the mvvm labels right)
VIEW
import SwiftUI
struct TestingView: View {
#ObservedObject private var viewModel = CardViewModel()
var body: some View {
List(viewModel.cards) {
Text(cards.name)
}
.onAppear() {
self.viewModel.fetchData()
}
}
}
VIEW MODEL
import Foundation
import Firebase
class CardViewModel: ObservableObject {
#Published var cards = [Cards]()
private var db = Firestore.firestore()
func fetchData() {
db.collection("cards").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
self.cards = documents.map { queryDocumentSnapshot -> Cards in
let data = queryDocumentSnapshot.data()
let name = data["name"] as? String ?? ""
let pronoun = data["pronoun"] as? String ?? ""
let bio = data["bio"] as? String ?? ""
let profileURLString = data["profileURLString"] as? String ?? ""
let gradiantColor1 = data["gradiantColor1"] as? UInt ?? 0
let gradiantColor2 = data["gradiantColor2"] as? UInt ?? 0
let gradiantColor3 = data["gradiantColor3"] as? UInt ?? 0
return Cards(name: name, pronoun: pronoun, bio: bio, profileURLString: profileURLString, gradiantColor1: gradiantColor1, gradiantColor2: gradiantColor2, gradiantColor3: gradiantColor3)
}
}
}
}
MODEL
import Foundation
struct Cards: Identifiable {
var id = UUID().uuidString
var name: String
var pronoun: String
var bio: String
var profileURLString: String
var gradiantColor1: UInt
var gradiantColor2: UInt
var gradiantColor3: UInt
var profileURL: URL {
return URL(string: profileURLString)!
}
}
List will provide an element to its trailing closure -- see card in in my code. Then, you can access that specific card in your Text element.
var body: some View {
List(viewModel.cards) { card in //<-- Here
Text(card.name) //<-- Here
}
.onAppear() {
self.viewModel.fetchData()
}
}
}
I'd suggest that you might want to rename the struct Cards to struct Card since it is one card. Then, your array would be #Published var cards = [Card]() -- ie an array of Cards. From a naming perspective, this would make a lot more sense.

Get data from Firestore and properly append to array

I'm trying to fetch data from Firestore. I've already got the following code but how do I properly append to shelters?
Current error:
Value of type '[String : Any]' has no member 'title'
class FirebaseSession: ObservableObject {
#Published var shelters: [Shelter] = []
let ref = Firestore.firestore().collection("shelters")
getShelters() {
ref.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
let value = document.data()
let shelter = Shelter(id: Int(value.id), title: value.title, image: value.image, availableSpaces: value.available, distance: value.distance, gender: value.gender)
self.$shelters.append(shelter)
}
}
}
}
}
class Shelter {
var id: Int
var title: String
var image: String
var availableSpaces: Int
var distance: Double
var gender: String?
init?(id: Int, title: String, image: String, availableSpaces: Int, distance: Double, gender: String?) {
if id < 0 || title.isEmpty || image.isEmpty || availableSpaces < 0 || distance < 0 {
return nil
}
self.id = id
self.title = title
self.image = image
self.availableSpaces = availableSpaces
self.distance = distance
self.gender = gender
}
}
EDIT:
let shelter = Shelter(id: value["id"] as? Int ?? -1, title: value["title"] as? String ?? "", image: value["image"] as? String ?? "", available: value["available"] as? Int ?? -1, distance: value["distance"] as? Double ?? -1, gender: value["gender"] as? String ?? "")
let shelter = Shelter(id: Int(value.id), title: value.title, image: value.image, availableSpaces: value.available, distance: value.distance, gender: value.gender)
Here value is of type [String:Any]. So you cant do value.title . You need to do value["title"] as? String ?? "" and Similarly for id,image,distance,etc.
So the final code becomes:
let shelter = Shelter(id: Int(value["id"], title: value["title"], image: value["image"], availableSpaces: value["available"], distance: value["distance"], gender: value["gender"])
Downcast it accordingly.
UPDATE
replace your code with this
if let shelter = Shelter(id: value["id"] as? Int ?? -1, title: value["title"] as? String ?? "", image: value["image"] as? String ?? "", available: value["available"] as? Int ?? -1, distance: value["distance"] as? Double ?? -1, gender: value["gender"] as? String ?? "") {
self.shelters.append(shelter)
} else {
print("provided data is wrong.")
}
There are a number of issues with the original code:
Instead of implementing a function to fetch the shelters, the following snippet in your code creates a computed property - not sure this is what you intended:
getShelters() {
...
}
I'd recommend replacing this with a proper function.
No need to use a class for your data model - especially as you seem to be using SwiftUI.
Instead of mapping the fetched documents manually (and having to deal with nil values, type conversion etc. yourself, I'd recommend using Firestore's Codable support.
I've written about this extensively in my article SwiftUI: Mapping Firestore Documents using Swift Codable - Application Architecture for SwiftUI & Firebase | Peter Friese.
Here's how your code might look like when applying my recommendations:
struct Shelter: Codable, Identifiable {
#DocumentID var id: String?
var title: String
var image: String
var availableSpaces: Int
var distance: Double
var gender: String?
}
class FirebaseSession: ObservableObject {
#Published var shelters = [Shelter]()
private var db = Firestore.firestore()
private var listenerRegistration: ListenerRegistration?
deinit {
unsubscribe()
}
func unsubscribe() {
if listenerRegistration != nil {
listenerRegistration?.remove()
listenerRegistration = nil
}
}
func subscribe() {
if listenerRegistration == nil {
listenerRegistration = db.collection("shelters").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
self.shelters = documents.compactMap { queryDocumentSnapshot in
try? queryDocumentSnapshot.data(as: Shelter.self)
}
}
}
}
}
Note that:
we're able to do away with the constructor for Shelter
the shelter property will now be automatically updated whenever a shelter is added to the shelter collection in Firestore
the code won't break if a document doesn't match the expected data structure
I marked the Shelter struct as identifiable, so that you can directly use it inside a List view. #DocumentID instructs Firestore to map the document ID to the respective attribute on the struct.

How do I update tableview cell when It had changed

When I scroll up&down then tableview cell has set overtimes so It affect lagging.
I want to update only when the data is updated.
Post.swift (It is model)
import Foundation
import Parse
class Post: PFObject, PFSubclassing {
#NSManaged var postBy: PFUser?
#NSManaged var postUser: String?
#NSManaged var postUserImg: PFFile?
#NSManaged var postText: String?
#NSManaged var postImg: PFFile?
#NSManaged var sell: NSNumber?
#NSManaged var commentPointer: PFObject?
#NSManaged var commentBy: String?
#NSManaged var comment: String?
#NSManaged var liked: NSArray?
#NSManaged var likeCount: NSNumber?
#NSManaged var mention: NSArray?
#NSManaged var hashtag: NSArray?
static func parseClassName() -> String {
return "Post"
}
override class func query() -> PFQuery<PFObject>? {
let query = PFQuery(className: Post.parseClassName())
return query
}
}
extension Post: FeedCellSupport {
var username:String?{
return postUser
}
var userImg:PFFile?{
return postUserImg
}
var commentText:String?{
guard let commentTxt = comment else {
return ""
}
return commentTxt
}
TableView.swift
protocol FeedCellSupport {
var postDate: String? { get }
var postId: String? { get }
var postObj: PFObject? { get }
var img: PFFile? { get }
var username:String?{ get }
var userImg:PFFile?{ get }
var commentText:String?{ get }
var commentFrom:String?{ get }
var postText: String? { get }
var likes: Int? { get }
var isLiked: Bool? { get }
var isSell: Bool? { get }
var hashtags: NSArray? { get }
var mentions: NSArray? { get }
}
func fetchObjects() {
let postQuery = Post.query()?
.whereKeyExists("objectId")
.includeKeys(["postBy", "commentPointer", "commentPointer.commentBy", "commentBy"])
.order(byDescending: "createdAt")
as! PFQuery<Post>
postQuery.limit = self.page
postQuery.cachePolicy = .networkElseCache
postQuery.findObjectsInBackground { (object:[Post]?, error:Error?) in
if error == nil {
self.results = object!
}else {
print(error?.localizedDescription as Any)
}
self.tableView.reloadData()
self.refresher.endRefreshing()
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ShopDetailCell", for: indexPath) as! ShopDetailCell
if let object = results[indexPath.row] as? FeedCellSupport{
cell.postID = object.postObj!
cell.userNameLabel.text = object.username!
cell.userNameLabel.sizeToFit()
cell.descriptionLabel.text = object.postText!
cell.descriptionLabel.sizeToFit()
cell.commentByLabel.text = object.commentFrom!
cell.commentByLabel.sizeToFit()
cell.commentLabel.text = object.commentText!
cell.commentLabel.sizeToFit()
cell.delegate = self
}
return cell
}
How can I ensure that data is updated only when it changes?
What are your App functionalities? Your problem is associated with the way suitable to accomplish your task, that is: to update app contents.
So it is about business logic: when should the information be updated?
The solution to your problem is using user toggled events to refresh the contents or other possible solutions such as Application Delegate.
Except for these answers, the contents cannot update tableview automatically as your expectation.
Update
I considered this line of code is lagging:
if let object = results[indexPath.row] as? FeedCellSupport{
//other code
}
Because code inside these braces is dealing with data setting. You could try to mock up local data instead of this results array to say whether this is the problem.

compare NSSet in Predicate for Fetchrequest Xcode Swift OSX Core Data

I have two NSManagedObject like
class Person: NSManagedObject {
#NSManaged var firstname: String;
#NSManaged var lastname: String;
#NSManaged var code: String;
#NSManaged var position: Int16;
#NSManaged var lasttime: NSDate;
#NSManaged var isvip: Int16;
#NSManaged var changeposition: NSSet;
}
class ChangePosition: NSManagedObject {
#NSManaged var person:Person
#NSManaged var code: String;
#NSManaged var from: Int16;
#NSManaged var to: Int16;
#NSManaged var time: NSDate;
}
so I have one Person with many ChangePosition as NSSet. Each ChangePosition has a NSDate.
now I want to try to fetch the one ChangePosition from a Person with the highest NSDate. Which predicate construct can I use for this?
so my actually workflow for this is this one
let fetchReqPerson = NSFetchRequest(entityName: "Person");
//fetchReq.predicate = NSPredicate(format: "ANY personchangeposition.time == maxElement(personchangeposition.time)")
do {
let fetchResult = try self.appDelegate.managedObjectContext.executeFetchRequest(fetchReqPerson)
for managedObject in fetchResult {
var bigTime:PersonChangePosition?
for changes in (managedObject as! Person).personchangeposition {
if (changes as! PersonChangePosition).time.compare(_date) == NSComparisonResult.OrderedAscending {
if bigTime == nil {
bigTime = changes as! PersonChangePosition
} else {
if (changes as! PersonChangePosition).time.compare(bigTime!.time) == NSComparisonResult.OrderedDescending {
bigTime = changes as! PersonChangePosition;
}
}
}
}
if bigTime != nil {
//print("bigTime: ",bigTime!.time, "to: ",bigTime!.to)
self.fetchResultPerson.append(bigTime!)
}
}
self.fetchForRecord = self.fetchResultPerson;
//print("fetchResult fetchResultPerson: ",fetchResultPerson.count)
} catch let fetchError as NSError {
print("Fetching error: ",fetchError)
}
//
switch(self.areaSegementControl.selectedSegment){
case 0: self.fetchForRecord = try self.fetchForRecord.filter({$0.to != 4})
break;
case 1: self.fetchForRecord = try self.fetchForRecord.filter({$0.to == 1 || $0.to == 3})
break;
case 2: self.fetchForRecord = try self.fetchForRecord.filter({$0.to == 2})
break;
default:
break;
}
but of course, its rather slow .... not really good

Core Data Fetch Relationship not working

I have a problem with fetching data from relationships.
I have a survey and i want to save the answers, the userIds and the results to core data and fetch the data at the next start of the app.
They are in arrays.
Each of it will be turned to a NSManagedObject "Wert", into the attribute "valueInt" if it is a result and "valueString" if it is an answer or an userId.
extension Wert {
#NSManaged var valueInt: NSNumber?
#NSManaged var valueString: String?
}
Afterwards it will be saved on the NSManagedObject "Message", as a NSSet on answers, results or userIds.
extension Message {
#NSManaged var absenderId: String?
#NSManaged var absenderName: String?
#NSManaged var datum: NSDate?
#NSManaged var gruppenId: String?
#NSManaged var image: NSData?
#NSManaged var latitude: NSNumber?
#NSManaged var longitude: NSNumber?
#NSManaged var messageId: String?
#NSManaged var question: String?
#NSManaged var sound: NSData?
#NSManaged var text: String?
#NSManaged var answers: NSSet?
#NSManaged var results: NSSet?
#NSManaged var userIDs: NSSet?
}
I think that this is working, because after the "addObjectsFromArray" AnswersSet contains some values.
func saveCoreData(AbsenderName: String, AbsenderID: String, Text: String?, Image: NSData?, Datum: NSDate, Latitude: Double?, Longitude: Double?, MessageId: String, Sound: NSData?, question: String?, answers : [String]?, results : [Int]?, userIDs: [String]?) {
let newMessage = NSEntityDescription.insertNewObjectForEntityForName("Message", inManagedObjectContext: context) as NSManagedObject
newMessage.setValue(AbsenderID, forKey: "absenderId")
newMessage.setValue(AbsenderName, forKey: "absenderName")
newMessage.setValue(Text, forKey: "text")
newMessage.setValue(Image, forKey: "image")
newMessage.setValue(Latitude, forKey: "latitude")
newMessage.setValue(Longitude, forKey: "longitude")
newMessage.setValue(Datum, forKey: "datum")
newMessage.setValue(GroupId, forKey: "gruppenId")
newMessage.setValue(MessageId, forKey: "messageId")
newMessage.setValue(Sound, forKey: "sound")
if question != nil && answers != nil && results != nil && userIDs != nil {
newMessage.setValue(question, forKey: "question")
var AnswersArray = [NSManagedObject]()
var ResultsArray = [NSManagedObject]()
var userIDsArray = [NSManagedObject]()
for var index = 0; index < answers?.count ; ++index {
let newWert = NSEntityDescription.insertNewObjectForEntityForName("Wert", inManagedObjectContext: context) as NSManagedObject
newWert.setValue(answers![index], forKey: "valueString")
AnswersArray.append(newWert)
}
for var index = 0; index < results?.count ; ++index {
let newWert = NSEntityDescription.insertNewObjectForEntityForName("Wert", inManagedObjectContext: context) as NSManagedObject
newWert.setValue(results![index], forKey: "valueInt")
ResultsArray.append(newWert)
}
for var index = 0; index < userIDs?.count ; ++index {
let newWert = NSEntityDescription.insertNewObjectForEntityForName("Wert", inManagedObjectContext: context) as NSManagedObject
newWert.setValue(userIDs![index], forKey: "valueString")
userIDsArray.append(newWert)
}
let answersSet = newMessage.mutableSetValueForKey("answers")
let resultsSet = newMessage.mutableSetValueForKey("results")
let userIdsSet = newMessage.mutableSetValueForKey("userIDs")
answersSet.addObjectsFromArray(AnswersArray)
resultsSet.addObjectsFromArray(ResultsArray)
userIdsSet.addObjectsFromArray(userIDsArray)
}
do {
try context.save()
}
catch _ {
print("Error")
}
}
But when i try to fetch the saved values with mutableSetValueForKey(), they contain 0 objects. Everything else is working.
func loadCoreData() -> Int {
var x : [AnyObject] = [AnyObject]()
let request = NSFetchRequest(entityName: "Message")
request.resultType = NSFetchRequestResultType.DictionaryResultType
request.predicate = NSPredicate(format: "gruppenId = %#", GroupId)
let sort1 = NSSortDescriptor(key: "datum", ascending: true)
request.sortDescriptors = [sort1]
do {
x = try context.executeFetchRequest(request)
for (var i = 0 ; i < x.count; ++i ) {
let Absender = x[i].valueForKey("absenderName") as! String
let AbsenderID = x[i].valueForKey("absenderId") as! String
let Text : String? = x[i].valueForKey("text") as? String
let Image : NSData? = x[i].valueForKey("image") as? NSData
let Sound : NSData? = x[i].valueForKey("sound") as? NSData
let Date : NSDate = x[i].valueForKey("datum") as! NSDate
let GruppenID : String = x[i].valueForKey("gruppenId") as! String
let MessageID : String = x[i].valueForKey("messageId") as! String
let longitude : Double? = x[i].valueForKey("longitude") as? Double
let latitude : Double? = x[i].valueForKey("latitude") as? Double
let question : String? = x[i].valueForKey("question") as? String
let answers = x[i].mutableSetValueForKey("answers") as? NSMutableSet
let results = x[i].mutableSetValueForKey("results") as? NSMutableSet
let userIds = x[i].mutableSetValueForKey("userIDs") as? NSMutableSet
}
}
}
It seems that the answers, results etc. are one-to-many relationships. In this case, it is always safer (and more concise) to simply set the to-one relationship.
Thus, rather than maintaining a clumsy array and dealing with mutableSetValueForKey, you could simply use the inverse relationship:
newWert.message = newMessage
NB: Please adopt the convention of using lowerCase variable names to avoid confusion with class names.
NB2: I strongly advise using NSManagedObject subclasses and using dot.notation to set and get values. With one stroke, all the variables you define in your many lines of code above would become utterly superfluous.