Core Data Fetch Relationship not working - swift

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.

Related

Filtration coredata model in tableView

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

Change a value in my UserModel (class) based on a userid

I have a UserModel:
class UserModel {
var uid: String?
var username : String?
var email: String?
var profileImageUrl: String?
var dateOfBirth: String?
var registrationDate: Int?
var isFollowing: Bool?
var accessLevel: Int?
var onlineStatus: Bool?
init(dictionary: [String : Any]) {
uid = dictionary["uid"] as? String
username = dictionary["username"] as? String
email = dictionary["email"] as? String
profileImageUrl = dictionary["profileImageUrl"] as? String
dateOfBirth = dictionary["dateOfBirth"] as? String
registrationDate = dictionary["userRegistrationDate"] as? Int
accessLevel = dictionary["accessLevel"] as? Int
onlineStatus = dictionary["onlineStatus"] as? Bool
}
}
And I also have a value like [12ih12isd89 : True]
I want to change the value "onlineStatus" for the user "12ih12isd89" to True and I thought the right way to do this is updateValue(:forKey:). But my class UserModel does not have updateValue(:forKey:).
How can I use this in my existing model?
Edit:
How I get the data:
func fetchAllUsers (completion: #escaping ([UserModel]) -> Void) {
let dispatchGroup = DispatchGroup()
var model = [UserModel]()
let db = Firestore.firestore()
let docRef = db.collection("users")
dispatchGroup.enter()
docRef.getDocuments { (querySnapshot, err) in
for document in querySnapshot!.documents {
let dic = document.data()
model.append(UserModel(dictionary: dic))
}
dispatchGroup.leave()
}
dispatchGroup.notify(queue: .main) {
completion(model)
}
}
To me it looks like you need to find the right object in the array and update the property
let dict = ["12ih12isd89" : true]
var model = [UserModel]()
if let user = model.first(where: {$0.uid == dict.keys.first!}) {
user.onlineStatus = dict.values.first!
}
Depending on what ["12ih12isd89" : true] really is you might want to change the access from dict.keys.first! that I used
If your value dictionary contains more than one user, you can use a for loop like this:
var model = [UserModel]()
//Some initalization ...
let values = ["12ih12isd89" : true]
for (k, v) in values {
model.filter({$0.uid == k}).first?.onlineStatus = v
}

Posts Being Uploaded Randomly in Collection View - Swift & Firebase

I have been refactoring my code and now I'm having trouble with the posts.
Whenever I add a new post to the collection view, it is being added in a random cell and out of order, instead of in the first post.
I know the reason is the fetchuser function and from what I'm being told due to the asynchronous loading, but don't know what to do in order to correct this.
Could someone help me figure out what to do so that my posts are added in the first cell?
#objc func observePostsAdoption() {
let postsRef = Database.database().reference().child("posts")
postsRef.queryOrdered(byChild: "postType").queryEqual(toValue: "adopt").observe(.value) { (snapshot) in
var tempPost = [Posts]()
for child in snapshot.children {
if let childSnapshot = child as? DataSnapshot {
let dict = childSnapshot.value as? [String: Any]
let newAdoptiondPost = Posts.transformPost(dict: dict!)
//This will look up all users at once
self.fetchUser(userid: newAdoptiondPost.userid!, completed: {
tempPost.insert(newAdoptiondPost, at: 0)
DispatchQueue.main.async {
self.postsadoption = tempPost
self.adoptionCollectionView.reloadData()
self.refresherAdoption.endRefreshing()
}
})
}
}
}
}
func fetchUser(userid: String, completed: #escaping ()-> Void ) {
Database.database().reference().child("users").child(userid).observeSingleEvent(of: .value) { (snapshot) in
if let dict = snapshot.value as? [String: Any] {
let user = UserProfile.transformUser(dict: dict)
self.users.insert(user, at: 0)
completed()
}
}
}
Here's my Post Struct
class Posts {
//UserView
var uid: String?
var author: UserProfile?
var timestamp: Date?
var userid: String?
func getDateFormattedString() -> String {
let formatter = DateFormatter()
formatter.dateFormat = "MMM d, HH:mm"
return formatter.string(from: self.timestamp!)
}
//Image
var photoUrl: URL?
//PostInformation View
var city: String?
var municipality: String?
var name: String?
var breed : String?
var phone : String?
var address : String?
var petType: String?
var genderType: String?
var comments: String?
}
extension Posts {
static func transformPost(dict: [String: Any]) -> Posts {
let post = Posts()
//Post Picture
let photoUrl = dict["photoUrl"] as? String
post.photoUrl = URL(string: photoUrl!)
//INFO POSTS
post.userid = dict["userid"] as? String
post.city = dict["city"] as? String
post.municipality = dict["municipality"] as? String
post.name = dict["name"] as? String
post.breed = dict["breed"] as? String
post.phone = dict["phone"] as? String
post.address = dict["address"] as? String
post.comments = dict["comments"] as? String
post.petType = dict["petType"] as? String
post.genderType = dict["gender"] as? String
let timestamp = dict["timestamp"] as? Double
post.timestamp = Date(timeIntervalSince1970: timestamp!/1000)
return post
}
}
If you already have the posts ordered by post type you can just do sorting depending on the timestamp. For example
#objc func observePostsAdoption() {
let postsRef = Database.database().reference().child("posts")
postsRef.queryOrdered(byChild: "postType").queryEqual(toValue: "adopt").observe(.value) { (snapshot) in
var tempPost = [Posts]()
for child in snapshot.children {
if let childSnapshot = child as? DataSnapshot {
let dict = childSnapshot.value as? [String: Any]
let newAdoptiondPost = Posts.transformPost(dict: dict!)
//This will look up all users at once
self.fetchUser(userid: newAdoptiondPost.userid!, completed: {
tempPost.insert(newAdoptiondPost, at: 0)
DispatchQueue.main.async {
self.postsadoption = tempPost
self.postsadoption.sort { (p1, p2) -> Bool in
return p1.timeStamp?.compare(p2.timeStamp!) == .orderdDescending
}
self.adoptionCollectionView.reloadData()
self.refresherAdoption.endRefreshing()
}
})
}
}
}
}
With that the posts adoption array will be sorted depending on the timestamp that you have.

Swift Firebase chat room

I have chat app with possibility send messages one to one (fromId/toId). I want to upgrade it with chat rooms. How i can do that? What DB structure do i need for ChatingRoom? What else i need to do that?
My User.swift model:
import Foundation
import Firebase
class User: NSObject {
var id: String?
var name: String?
var login: String?
var email: String?
var profileImageUrl: String?
var role: String?
var isOnline: String?
init(dictionary: [String: AnyObject]) {
self.isOnline = dictionary["isOnline"] as? String
self.id = dictionary["userID"] as? String
self.name = dictionary["name"] as? String
self.login = dictionary["username"] as? String
self.email = dictionary["email"] as? String
self.profileImageUrl = dictionary["profileImageUrl"] as? String
self.role = dictionary["role"] as? String
}
}
Message.swift model:
import UIKit
import Firebase
class Message: NSObject {
var fromId: String?
var text: String?
var timestamp: NSNumber?
var toId: String?
var imageUrl: String?
var videoUrl: String?
var imageWidth: NSNumber?
var imageHeight: NSNumber?
init(dictionary: [String: Any]) {
self.fromId = dictionary["fromId"] as? String
self.text = dictionary["text"] as? String
self.toId = dictionary["toId"] as? String
self.timestamp = dictionary["timestamp"] as? NSNumber
self.imageUrl = dictionary["imageUrl"] as? String
self.videoUrl = dictionary["videoUrl"] as? String
self.imageWidth = dictionary["imageWidth"] as? NSNumber
self.imageHeight = dictionary["imageHeight"] as? NSNumber
}
func chatPartnerId() -> String? {
return fromId == Auth.auth().currentUser?.uid ? toId : fromId
}
}
Well currently you have From / To.
So To is going to be a room rather than a person.
Users will need to be able to join a room (or rooms) in order to see the messages that are sent to that room.
So your need a Rooms node.
If you have a messages node then you can just sort by To (room) instead of To (user) to get all the messages sent in that chat room. From will always be the User that wrote the message

CoreData and Swift: contents gets swapped

I am new to swift and have a problem with CoreData: I can save and load to and from my CoreData store. But when I load the saved data from CoreData the data has been rearranged. I don't want this to happen. How do I solve this?
My code is this:
Meetings.swift
import Foundation
import CoreData
class Meetings: NSManagedObject {
#NSManaged var meetingTimeLeft: NSNumber
#NSManaged var title: String
#NSManaged var date: NSDate
#NSManaged var timeFrom: NSDate
#NSManaged var timeTo: NSDate
#NSManaged var timeFromActual: NSDate
#NSManaged var timeToActual: NSDate
#NSManaged var location: String
#NSManaged var locationRoom: String
#NSManaged var meetingGoals: String
#NSManaged var purpose: String
#NSManaged var averageHourlyCost: NSNumber
#NSManaged var completed: NSNumber
#NSManaged var durationActual: NSNumber
#NSManaged var durationPlanned: NSNumber
#NSManaged var persons: NSSet
#NSManaged var agendas: NSSet
#NSManaged var minutes: NSSet
#NSManaged var relationship: NSManagedObject
#NSManaged var startSetting: StartSetting?
}
extension Meetings {
func addPer(value: Person) {
self.mutableSetValueForKey("persons").addObject(value)
}
func removePer(value: Person) {
self.mutableSetValueForKey("persons").removeObject(Person)
//self.mutableSetValueForKey("persons").addObject(value)
}
func getPer() -> [Person] {
var persons: [Person]
persons = self.persons.allObjects as! [Person]
return persons
}
func addAgenda(value: AgendaItem) {
self.mutableSetValueForKey("agendas").addObject(value)
}
func removeAgenda(value: AgendaItem) {
self.mutableSetValueForKey("agendas").removeObject(AgendaItem)
//self.mutableSetValueForKey("persons").addObject(value)
}
func getAgenda() -> [AgendaItem] {
var agendas: [AgendaItem]
agendas = self.agendas.allObjects as! [AgendaItem]
return agendas
}
func getAgendaItem(i: Int) -> AgendaItem {
var agendas: [AgendaItem]
agendas = self.agendas.allObjects as! [AgendaItem]
let agendaItem = agendas[i]
return agendaItem
}
}
person.swift
import Foundation
import CoreData
class Person: NSManagedObject {
#NSManaged var name: String
#NSManaged var email: String
#NSManaged var phone: NSNumber
#NSManaged var title: String
#NSManaged var company: String
#NSManaged var photo: NSData
#NSManaged var meeting: Meetings
}
AgendaItem.swift
import Foundation
import CoreData
class AgendaItem: NSManagedObject {
#NSManaged var agendaItemTitle: String
#NSManaged var presenter: String
#NSManaged var durationPlanned: NSNumber
#NSManaged var durationActual: NSNumber
#NSManaged var filePresentation: NSData
#NSManaged var fileSupporting: NSData
#NSManaged var agendaTimeStart: NSDate
#NSManaged var meeting: Meetings
}
when saving i do like this:
internal var meetingsModels = [Meetings]()
public let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
managedObjectContext?.deleteObject(meetingsModels[meetingsIndexPassed.row]) meetingsModels.removeAtIndex(meetingsIndexPassed.row)
let entityDescription = NSEntityDescription.entityForName("Meetings", inManagedObjectContext: managedObjectContext!)
let entityDescription2 = NSEntityDescription.entityForName("Person", inManagedObjectContext: managedObjectContext!)
let entityDescription3 = NSEntityDescription.entityForName("AgendaItem", inManagedObjectContext: managedObjectContext!)
let meetings = Meetings(entity: entityDescription!, insertIntoManagedObjectContext: managedObjectContext)
meetings.title = meetingTitle_txt.text!
meetings.date = lDate
meetings.timeFrom = lStartTime
meetings.timeTo = lEndTime
meetings.location = address_txt.text!
meetings.purpose = details[0]
meetings.meetingGoals = details[1]
meetings.locationRoom = details[2]
meetings.durationPlanned = meetingsEditPassed.durationPlanned
meetings.completed = false
for var i = 0; i < personsEdit.count; i++ {
let person = Person(entity: entityDescription2!, insertIntoManagedObjectContext: managedObjectContext)
person.name = personsEdit[i].name
person.email = personsEdit[i].email
person.title = personsEdit[i].title
person.company = personsEdit[i].company
person.phone = personsEdit[i].phone
meetings.addPer(person)
}
for var i = 0; i < agendaEdit.count; i++ {
let agenda = AgendaItem(entity: entityDescription3!, insertIntoManagedObjectContext: managedObjectContext)
agenda.agendaItemTitle = agendaEdit[i].agendaItemTitle
//print(agendaEdit[i].agendaItemTitle)
agenda.presenter = agendaEdit[i].presenter
agenda.durationPlanned = agendaEdit[i].durationPlanned
meetings.addAgenda(agenda)
}
do {
try managedObjectContext?.save()
} catch let error1 as NSError {
print("\(error1)")
}
meetingsModels.append(meetings)
when loading i do this:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
//1
let appDelegate =
UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext!
//2
let fetchRequest = NSFetchRequest(entityName:"Meetings")
//fetchRequest.sortDescriptors = nil
//3
let fetchedResults: [Meetings]
do {
try
fetchedResults = (managedContext.executeFetchRequest(fetchRequest) as? [Meetings])!
if let results: [Meetings] = fetchedResults {
var temp = [Meetings]()
var temp2 = [Meetings]()
for tmp: Meetings in results {
temp.append(tmp)
if tmp.completed.boolValue {
temp2.append(tmp)
}else {
}
}
meetingsModels = temp
meetingsModels2 = temp2
} else {
}
} catch let error1 as NSError {
print("could not fetch meetings: \(error1), \(error1.userInfo)")
}
}
i hope that some of you can help me.
Your meetings data is being "rearranged" just because your Meetings don't belong to any ordered relationship. I'm not able to run your code on my side. However, I can recommend you one of the following:
Use ordered relations any time you want to make sure the order of
data is preserved any time you fetch it.
Use the
sortDescriptors property on your fetch requests. When you
create one, you can add a sort descriptor to it in order to
arrange data.