iOS NSCoreDataCoreSpotlightDelegate, how to reindex all items - ios

I have implemented NSCoreDataCoreSpotlightDelegate, and I missed out the part where the default expirationDate is 1 month, now items that I have added 3 months ago are not showing up in the search index.
How do I get NSCoreDataCoreSpotlightDelegate to reindex all the items?
I used to be able to call this:
let container = NSPersistentContainer(name: "MyApp")
let psd = NSPersistentStoreDescription(url: FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.kiwlm.MyApp")!.appendingPathComponent("MyApp.sqlite"))
let mcdcsd = MyCoreDataCoreSpotlightDelegate(forStoreWith:psd, model: container.managedObjectModel)
container.persistentStoreDescriptions = [psd]
psd.setOption(mcdcsd, forKey:NSCoreDataCoreSpotlightExporter)
// uncomment the following to reindex all items
// mcdcsd.searchableIndex(CSSearchableIndex.default(), reindexAllSearchableItemsWithAcknowledgementHandler: {})
And the first time, it will reindex all the items, but if I re-run the app with the above line uncommented again, it will not reindex.
If I uninstall the app, install back, then uncomment the above line, then it will index all again for the first time.
How do I get it to reindex everything again?

It happens because implementation of NSCoreDataCoreSpotlightDelegate use beginIndexBatch and endIndexBatchWithClientState to reindexAllSearchableItems. So it's saving index state. When you invoke reindexAllSearchableItems Spotlight will check the state and reindex only new items. When you delete the app you also delete index state. At first launch state is empty and NSCoreDataCoreSpotlightDelegate index all items.
I think for your purpose much better to set expirationDate instead of reindex all items. You can do it at MyCoreDataCoreSpotlightDelegate by override attributeSet:
override func attributeSet(for object: NSManagedObject) -> CSSearchableItemAttributeSet? {
guard let attributeSet = super.attributeSet(for: object) else { return nil }
if object.entity.name == "YourEntityName" {
// Setup necessary attributes
attributeSet.setValue(Date.distantFuture, forKey: "expirationDate")
}
return attributeSet
}
To check expirationDate of searchable items you can use CSSearchQuery:
class QueryExample {
var query : CSSearchQuery? = nil
func startQuery(withTitle title : String) {
var allItems = [CSSearchableItem]()
// All the items that start with the specified title property using case insensitive comparison
let queryString = "title == \"*\(title)*\"c"
let attributes = ["title", "displayName", "keywords",
"contentType"]
self.query = CSSearchQuery(queryString : queryString,
attributes : attributes)
self.query?.foundItemsHandler = { (items : [CSSearchableItem])
-> Void in
allItems.append(contentsOf: items)
}
self.query?.completionHandler = { (error ) -> Void in
for item in allItems {
print("expirationDate:\(item.expirationDate)")
}
}
self.query?.start()
}
}
Invoke query like this:
override func viewDidLoad() {
let query = QueryExample()
query.startQuery(withTitle: "S")
}
And you should see expirationDate at console:
expirationDate:4001-01-01 00:00:00 +0000

Related

Adding a custom object to an array in Firestore

I am making an ordering app for customers to order their specific specs. When the user logs in they can go to a tab that contains a tableview with all their specs, once they click on a cell it will take them to a new view controller that will display more information on the spec. Once on this view controller they will have the ability to add x amount of pallets/rolls/etc of that item. I am able to add the spec to Firestore, but I cannot get it to an array in Firestore which I need. My goal is that on anther tab the user can view all the current specs they are trying to order until they hit submit. I am currently using the user.uid to get to that specific customers orders inside Firestore.
Code:
#IBAction func addPallet(_ sender: Any) {
// Get the current user
let user = Auth.auth().currentUser
if let user = user {
_ = user.uid
}
if spec != nil {
// Get the qty ordered for that spec
let totalQty: Int? = Int(palletsToAddTextField.text!)
let qty = spec!.palletCount * totalQty!
let specToAdd = Spec(specNumber: spec!.specNumber,
specDescription: spec!.specDescription,
palletCount: spec!.palletCount,
palletsOrdered: qty)
orderedArray.append(specToAdd)
let specAdded: [String: Any] = [
"SpecDesc": spec!.specDescription,
"SpecNum": spec!.specNumber,
"PalletCount": spec!.palletCount,
"PalletsOrder": qty
]
db.collection("orders").document(user?.uid ?? "error").setData(specAdded) { err in
if let err = err {
print("Error writing document: \(err)")
} else {
print("Document successfully written!")
}
}
}
}
code for spec:
struct Spec: Codable {
// Properties
var specNumber: String
var specDescription: String
var palletCount: Int
var palletsOrdered = 0
enum CodingKeys: String, CodingKey {
case specNumber
case specDescription
case palletCount
case palletsOrdered
}
}
I need something added like the below picture. The user will add x amount of pallets, then going to the next spec they want and add it to the array in Firestore as well.
Ok i guess i get what you want to do. Try:
db.collection("orders").document(userID).setData(["allMyData" : myArray])
"allMyData" will be the name of the field in which you want to save your array and myArray would be your array (specAdded). Thats what you are looking for?
If the document already exists you will want to use .updateData instead of .setData to keep all other fields that might already exist in that specific doc.
Kind regards

File Provider iOS11 startProvidingItem not invoked

I'm implementing a File Provider Extension for iOS 11.
Dispite watching the conference at https://developer.apple.com/videos/play/wwdc2017/243/ and navigating through Apple's Documentation, I still can't seem to understand how to implement some of the methods for NSFileProviderExtension and NSFileProviderEnumerator objects.
I successfully implemented NSFileProviderItem, having all of them listed in the Navite iOS 11 Files App. However, I can't trigger any document based app to open upon selecting a file.
I overrided all the methods for the NSFileProviderExtension. Some are still empty, but I placed a breakpoint to check whenever they are called.
The NSFileProviderExtension looks something like this:
class FileProviderExtension: NSFileProviderExtension {
var db : [FileProviderItem] = [] //Used "as" a database
...
override func item(for identifier: NSFileProviderItemIdentifier) throws -> NSFileProviderItem {
for i in db {
if i.itemIdentifier.rawValue == identifier.rawValue {
return i
}
}
throw NSError(domain: NSCocoaErrorDomain, code: NSNotFound, userInfo:[:])
}
override func urlForItem(withPersistentIdentifier identifier: NSFileProviderItemIdentifier) -> URL? {
guard let item = try? item(for: identifier) else {
return nil
}
// in this implementation, all paths are structured as <base storage directory>/<item identifier>/<item file name>
let manager = NSFileProviderManager.default
let perItemDirectory = manager.documentStorageURL.appendingPathComponent(identifier.rawValue, isDirectory: true)
return perItemDirectory.appendingPathComponent(item.filename, isDirectory:false)
}
// MARK: - Enumeration
func enumerator(for containerItemIdentifier: NSFileProviderItemIdentifier) throws -> NSFileProviderEnumerator {
var maybeEnumerator: NSFileProviderEnumerator? = nil
if (containerItemIdentifier == NSFileProviderItemIdentifier.rootContainer) {
maybeEnumerator = FileProviderEnumerator(enumeratedItemIdentifier: containerItemIdentifier)
self.db = CustomData.getData(pid: containerItemIdentifier)
} else if (containerItemIdentifier == NSFileProviderItemIdentifier.workingSet) {
// TODO: instantiate an enumerator for the working set
} else {
}
guard let enumerator = maybeEnumerator else {
throw NSError(domain: NSCocoaErrorDomain, code: NSFeatureUnsupportedError, userInfo:[:])
}
return enumerator
}
My enumerateItems looks something like so:
class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
override func enumerateItems(for observer: NSFileProviderEnumerationObserver, startingAt page: NSFileProviderPage) {
let itens = CustomData.getData(pid: enumeratedItemIdentifier)
observer.didEnumerate(itens)
observer.finishEnumerating(upTo: nil)
}
The static function CustomData.getData is used for testing. It returns an array of NSFileProviderItem with the desired properties. It should be replaced with a database, as explained in the conference.
class CustomData {
static func getData(pid : NSFileProviderItemIdentifier) -> [FileProviderItem] {
return [
FileProviderItem(uid: "0", pid: pid, name: "garden", remoteUrl : "https://img2.10bestmedia.com/Images/Photos/338373/GettyImages-516844708_54_990x660.jpg"),
FileProviderItem(uid: "1", pid: pid, name: "car", remoteUrl : "https://static.pexels.com/photos/170811/pexels-photo-170811.jpeg"),
FileProviderItem(uid: "2", pid: pid, name: "cat", remoteUrl : "http://www.petmd.com/sites/default/files/what-does-it-mean-when-cat-wags-tail.jpg"),
FileProviderItem(uid: "3", pid: pid, name: "computer", remoteUrl : "http://mrslamarche.com/wp-content/uploads/2016/08/dell-xps-laptop-620.jpg")
]
}
}
The problem is, when the user presses a document, urlForItem is successfully called but nothing happens upon returning the item url.
What am I doing wrong?
I can't find any examples on the internet.
Cheers
-nls
Turns out, I did not correctly implement providePlaceholder(at url:).
It is now solved.
Cheers
-nls
EDIT:
In order to list the items in your file provider, the method enumerator(for:) should be implemented.
This method will receive a containerItemIdentifier, as if telling you "what folder the user is trying to access". It returns a NSFileProviderEnumerator object, that should also be implemented by you.
Here is an example of how a simple enumerator(for:) method should look like:
class FileProviderExtension: NSFileProviderExtension {
override func enumerator(for containerItemIdentifier: NSFileProviderItemIdentifier) throws -> NSFileProviderEnumerator {
var enumerator: NSFileProviderEnumerator? = nil
if (containerItemIdentifier == NSFileProviderItemIdentifier.rootContainer) {
enumerator = FileProviderEnumerator(enumeratedItemIdentifier: containerItemIdentifier)
}
else {
enumerator = FileProviderEnumerator(enumeratedItemIdentifier: containerItemIdentifier)
}
if enumerator == nill {
throw NSError(domain: NSCocoaErrorDomain, code: NSFeatureUnsupportedError, userInfo:[:])
}
return enumerator
}
(...)
}
Again, as I said, the FileProviderEnumerator should be implemented by you. The important method here is the enumerateItems(for observer:, startingAt page:)
Here it is how it should look:
class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
func enumerateItems(for observer: NSFileProviderEnumerationObserver, startingAt page: NSFileProviderPage) {
if (enumeratedItemIdentifier == NSFileProviderItemIdentifier.rootContainer) {
//Creating an example of a folder item
let folderItem = FileProviderFolder()
folderItem.parentItemIdentifier = enumeratedItemIdentifier //<-- Very important
folderItem.typeIdentifier = "public.folder"
folderItem.name = "ExampleFolder"
folderItem.id = "ExampleFolderID"
//Creating an example of a file item
let fileItem = FileProviderFile()
fileItem.parentItemIdentifier = enumeratedItemIdentifier //<-- Very important
fileItem.typeIdentifier = "public.plain-text"
fileItem.name = "ExampleFile.txt"
fileItem.id = "ExampleFileID"
self.itemList.append(contentsOf: [folderItem, fileItem])
observer.didEnumerate(self.itemList)
observer.finishEnumerating(upTo: nil)
}
else {
//1 > Find directory name using "enumeratedItemIdentifier" property
//2 > Fetch data from the desired directory
//3 > Create File or Folder Items
//4 > Send items back using didEnumerate and finishEnumerating
}
}
(...)
}
Remember that we were creating these FileProviderEnumerators, giving them the containerItemIdentifier. This property is used to determine what folder the user is trying to access.
Very important note: Each item, File or Folder, should have its parentItemIdentifier property defined. If this property is not set, the items won't appear when the user tries to open the parent folder.
Also, as the name suggests, typeIdentifier will hold the Uniform Type Identifier (UTI) for the item.
Finally, the last object we should implement is the NSFileProviderItem. Both File and Folder items are very similar, and should differ in their typeIdentifier property.
Here is a very simple example of a folder:
class FileProviderFolder: NSObject, NSFileProviderItem {
public var id: String?
public var name: String?
var parentItemIdentifier: NSFileProviderItemIdentifier
var typeIdentifier: String
init() {
}
var itemIdentifier: NSFileProviderItemIdentifier {
return NSFileProviderItemIdentifier(self.id!)
}
var filename: String {
return self.name!
}
}
The itemIdentifier is very important because, as stated before, this property will provide the directory name for the folder item when trying to enumerate its contents (refer to enumerator(for:) method).
EDIT2
If the user selects a file, the method startProvidingItem(at url:) should be called.
This method should perform 3 tasks:
1 - Find the selected item ID (usualy using the provided url, but you can use a database too)
2 - Download the file to the local device, making it available at the specified url. Alamofire does this;
3 - Call completionHandler;
Here is a simple example of this method:
class FileProviderExtension: NSFileProviderExtension {
override func urlForItem(withPersistentIdentifier identifier: NSFileProviderItemIdentifier) -> URL? {
// resolve the given identifier to a file on disk
guard let item = try? item(for: identifier) else {
return nil
}
// in this implementation, all paths are structured as <base storage directory>/<item identifier>/<item file name>
let perItemDirectory = NSFileProviderManager.default.documentStorageURL.appendingPathComponent(identifier.rawValue, isDirectory: true)
let allDir = perItemDirectory.appendingPathComponent(item.filename, isDirectory:false)
return allDir
}
override func persistentIdentifierForItem(at url: URL) -> NSFileProviderItemIdentifier? {
// exploit that the path structure has been defined as <base storage directory>/<item identifier>/<item file name>, at urlForItem
let pathComponents = url.pathComponents
assert(pathComponents.count > 2)
return NSFileProviderItemIdentifier(pathComponents[pathComponents.count - 2])
}
override func startProvidingItem(at url: URL, completionHandler: #escaping (Error?) -> Void) {
guard
let itemID = persistentIdentifierForItem(at: url),
let item = try? self.item(for: itemID) as! FileProviderFile else {
return
}
DownloadfileAsync(
file: item,
toLocalDirectory: url,
success: { (response) in
// Do necessary processing on the FileProviderFile object
// Example: setting isOffline flag to True
completionHandler(nil)
},
fail: { (response) in
completionHandler(NSFileProviderError(.serverUnreachable))
}
)
}
(...)
}
Note that, to get the ID from the URL, I'm using the recomended method: the URL it self contains the item ID.
This URL is definedin the urlForItem method.
Hope this helps.
-nls
I thought I'd provide a followup answer, the primary answer is great as a first step. In my case startProvidingItem was not called because I was not storing the files in exactly the directory the system was looking for, that is to say:
<Your container path>/File Provider Storage/<itemIdentifier>/My Awesome Image.png
That is on the slide from WWDC17 on the FileProvider extension, but I did not think it must follow that format so exactly.
I had a directory not named "File Provider Storage" into which I was putting files directly, and startProvidingItem was never called. It was only when I made a directory for the uniqueFileID into which the file was placed, AND renamed my overall storage directory to "File Provider Storage" that startProvidingItem was called.
Also note that with iOS11, you'll need to provide a providePlaceholder call as well to the FileProviderExtension, use EXACTLY the code that is in the docs for that and do not deviate unless you are sure of what you are doing.

NSBatchDeleteRequest causes Merge Conflict

I have an application that will sync with a server with data that can change daily. During the sync, I remove all the data for some entities and reload it with new data. I am using the following code:
func SyncronizeUserComments(theData : [[AnyHashable : Any]])
{
// Delete User Comments for this User and Connection
let commentRequest : NSFetchRequest<NSFetchRequestResult> = PT_UserComments.fetchRequest()
commentRequest.predicate = NSPredicate(format: "connection = %# AND user == %#", Global_CurrentConnection!, Global_CurrentUser!)
coreData.processDeleteRequest(request: commentRequest)
// ADD the Comments to CoreData
for index in 0..<theData.count {
let result : [AnyHashable : Any] = theData[index]
if let commentID = result["Comment_ID"] as? String, let commentText = result["Comment_Text"] as? String, let commentTitle = result["Comment_Title"] as? String
{
let newUserComment = PT_UserComments(context: coreData.persistentContainer.viewContext)
newUserComment.connection = Global_CurrentConnection
newUserComment.user = Global_CurrentUser
newUserComment.comment_ID = commentID
newUserComment.comment_Text = commentText
newUserComment.comment_Title = commentTitle
}
}
// Add the User Comments
print("Added New User Comments: \(theData.count)")
coreData.saveContext()
}
func processDeleteRequest(request : NSFetchRequest<NSFetchRequestResult>)
{
let deleteRequest = NSBatchDeleteRequest(fetchRequest: request)
deleteRequest.resultType = .resultTypeObjectIDs
do {
let result = try coreData.persistentContainer.viewContext.execute(deleteRequest) as? NSBatchDeleteResult
let objectIDArray = result?.result as? [NSManagedObjectID]
let changes = [NSDeletedObjectsKey : objectIDArray]
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes as Any as! [AnyHashable : Any], into: [coreData.persistentContainer.viewContext])
} catch {
fatalError("Fatal Error Deleting Data: \(error)")
}
coreData.saveContext()
}
When I call coreData.saveContext() I will get a Merge Conflict against the deleted data.
In reading about CoreData and the NSBatchDeleteRequest, this deletes at the SQL LITE level and bypasses the in memory cache.
The only way I have been able to get this to work is by setting:
context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
Is this correct, or am I doing something wrong? I am also setting this merge policy in my saveContext() in the Core Data Stack.
I just spent hours debugging the same issue, hopefully this can help someone.
The problem is that NSManagedObjectContext.mergeChanges(fromRemoteContextSave:, into:) updates the managed object context but does not update the row cache version number of the deleted objects relationships to match the updated version number (Z_OPT) in the database file, causing a mismatch at time of the save.
If you're using NSErrorMergePolicyType this will cause the next save to fail, (or even a later one when the relationships become flagged for save), even though everything but the version numbers match. I've not seen this mentioned in the related docs or WWDC video, but I guess Apple assumed people would always pick a non-default merge policy.
So picking NSMergeByPropertyStoreTrumpMergePolicy solves it, as mentioned in the question, but you might not want this policy for all your save operations. To avoid that I ended up writing a custom merge policy that only resolves version mismatches. The code is below (this is untested Swift as I originally wrote in Obj-C, but should be equivalent):
//Configure the merge as below before saving
context.mergePolicy = AllowVersionMismatchMergePolicy(merge: .errorMergePolicyType)
//...
//The custom merge policy
class AllowVersionMismatchMergePolicy: NSMergePolicy {
override func resolve(optimisticLockingConflicts list: [NSMergeConflict]) throws {
do {
//if the default resolve worked leave it alone
return try super.resolve(optimisticLockingConflicts: list)
} catch {
//if any of the conflict is not a simple version mismatch (all other keys being equal), fail
let hasValueConflict = list.contains { conflict -> Bool in
//compare object and row cache
if let objectSnapshot = conflict.objectSnapshot as NSObject?,
let cachedSnapshot = conflict.cachedSnapshot as NSObject? {
return !objectSnapshot.isEqual(cachedSnapshot)
}
//compare row cache and database
if let cachedSnapshot = conflict.cachedSnapshot as NSObject?,
let persistedSnapshot = conflict.persistedSnapshot as NSObject? {
return !cachedSnapshot.isEqual(persistedSnapshot)
}
//never happens, see NSMergePolicy.h
return true
}
if hasValueConflict {
throw error
}
//Use store rollback merge policy to resolve all the version mismatches
return try NSMergePolicy.rollback.resolve(optimisticLockingConflicts: list)
}
}
}

How to check if PFRelation in Parse contains exactly same array in Swift

I am making a chat Application in ios through parse server. I have made a MessageRoom collection which has many to many relationship with users through PFRelation. Now i am struck . Whenever a user starts a new conversation , I add new entry in MessageRoom collection and use its id in the messages of that group. But when i want to fetch a previous conversation , let say a conversation between 5 users , how will i query the messageRoom which has exactly the same 5 users (not more or less) in its relation ?
This is the code i am using to create or get Message Room . It is not working correctly. What it does is instead of making a new messageRoom first time and fetching the same for latter user , it makes a new messaga room every time.
class func createOrGetMessageRoom(users:[PFUser], description:String)->PFObject{
var returnMessageRoom:PFObject = PFObject(className: PF_MESSAGE_ROOM_CLASS_NAME);
let users = users.sort(increasingIDs)
let query:PFQuery = PFQuery(className: PF_MESSAGE_ROOM_CLASS_NAME)
query.whereKey(PF_MESSAGE_ROOM_USERS, containsAllObjectsInArray : users)
query.findObjectsInBackgroundWithBlock{(objects, error )->Void in
if error == nil {
if objects?.count == 0 {
let messageRoom = PFObject(className: PF_MESSAGE_ROOM_CLASS_NAME)
messageRoom[PF_MESSAGE_ROOM_DESCRIPTION] = description
messageRoom[PF_MESSAGE_ROOM_LAST_USER] = PFUser.currentUser()
messageRoom[PF_MESSAGE_ROOM_LAST_MESSAGE] = ""
messageRoom[PF_MESSAGE_ROOM_COUNTER] = 0
messageRoom[PF_MESSAGE_ROOM_UPDATE_TIME] = NSDate()
let messageUsers = messageRoom.relationForKey(PF_MESSAGE_ROOM_USERS)
for user in users {
messageUsers.addObject(user)
}
messageRoom.saveInBackgroundWithBlock{(success,error)->Void in
if error == nil {
returnMessageRoom = messageRoom
}
}
}else{
returnMessageRoom = objects![0]
}
}else{
print("Message.createMessage Erorr");
print(error)
}
}
return returnMessageRoom
}
class func increasingIDs(user1: PFUser, user2: PFUser) -> Bool {
return user1.objectId < user2.objectId
}
I have also checked this application . What it does is whenever it starts a new chat , it concatenates objectIds of users in ascending order and use it as a groupId which is used for future references and used in chat messages as a foreign key.
It'll work in private chat and in group chat , but what happens if a user has started a group chat , and wants to add new users to this chat ?? If we simple change the group id by concatenating this users id , the previous messages which have used the old group id will no longer appear in this message group.
Also tell me if this approach of making groupID through concatenation is better or many to many relationship is better?
One problem with your function createOrGetMessageRoom is that findObjectsInBackgroundWithBlock is asynchronous, and you're not taking that into account.
What this means is that the findObjectsInBackgroundWithBlock function gets a response a long time after createOrGetMessageRoom has returned.
So, the PFObject you create on the first line of your function is always returned - your function does not wait for findObjectsInBackgroundWithBlock to return a MessageRoom.
To fix this, make your code take a callback like this:
class func createOrGetMessageRoom(users:[PFUser], description:String, callback: (PFObject? -> Void)) {
let users = users.sort(increasingIDs)
let query:PFQuery = PFQuery(className: PF_MESSAGE_ROOM_CLASS_NAME)
query.whereKey(PF_MESSAGE_ROOM_USERS, containsAllObjectsInArray : users)
query.findObjectsInBackgroundWithBlock{(objects, error )->Void in
if error == nil {
if objects?.count == 0 {
let messageRoom = PFObject(className: PF_MESSAGE_ROOM_CLASS_NAME)
messageRoom[PF_MESSAGE_ROOM_DESCRIPTION] = description
messageRoom[PF_MESSAGE_ROOM_LAST_USER] = PFUser.currentUser()
messageRoom[PF_MESSAGE_ROOM_LAST_MESSAGE] = ""
messageRoom[PF_MESSAGE_ROOM_COUNTER] = 0
messageRoom[PF_MESSAGE_ROOM_UPDATE_TIME] = NSDate()
let messageUsers = messageRoom.relationForKey(PF_MESSAGE_ROOM_USERS)
for user in users {
messageUsers.addObject(user)
}
messageRoom.saveInBackgroundWithBlock{(success,error)->Void in
if error == nil {
callback(messageRoom)
}
callback(nil)
}
}else{
callback(objects![0])
}
}else{
print("Message.createMessage Erorr");
print(error)
callback(nil)
}
}
}
Usage:
YourClass.createOrGetMessageRoom([], description: "description") { messageRoom in
// Do something...
}
The db schema in my mind, you should have 3 collections, _User, MessageRoom, and Message.
MessageRoom: users, roomName and other infos.
Message: room(pointer of MessageRoom), msg(content), sender(pointer of _User)
below are pseudo code
In your app, query all current user involved messageRooms.
var query = new Parse.Query("MessageRoom")
query.equalTo("users", currentUser);
//other constraint, roomName, createdAt, limit ...
query.find(...)
Pick a messageRoom object, and then use it to getMessages.
var query2 = new Parse.Query("Message");
query2.eqaulTo("room", roomObj);
query2.include("sender");
query2.descending("createdAt");
query2.find(...)

Swift Parse: can not retrieve the value of a bool in _User class

I am developing an app using swift and Parse. For some reasons I have implemented a Bool named "modified" in the _User class. I have been playing around with swift and Parse for a few months but this just does not make sense.
When I try to retrieve the value of the "modified" Bool I keep on getting "false" value even though it is set on "true" on Parse server. Here is the code:
var modified: Bool = PFUser.currentUser().objectForKey("modified") as! Bool
println("User Modified Bool is set to: \(modified)")
I have also tried with
self.modified = PFUser.currentUser().valueForKey("modified") as! Bool
println("User Modified Bool is set to: \(modified)")
and
self.modified = PFUser.currentUser()["modified"] as! Bool
println("User Modified Bool is set to: \(modified)")
Do I have to make a specific query or is there a way to access this value directly?
Edit
I have implemented a specific query. Still get a "false" value though
var queryMainUser: PFQuery = PFUser.query()
queryMainUser.whereKey("username", equalTo: PFUser.currentUser().username)
queryMainUser.findObjectsInBackgroundWithBlock { (mainUsersObjects, mainUsersError) -> Void in
if (mainUsersError == nil) {
var theRef = mainUsersObjects[0] as! PFObject
self.modified = theRef["modified"] as! Bool
println("Any improvement? \(self.modified)")
}
}
Edit 2
Following #danh advices, I tried updating the currentuser instance on the device by implementing this code:
var currentUser = PFUser.currentUser()
currentUser.fetchInBackgroundWithBlock { (object, error) -> Void in
println("Refreshed")
currentUser.fetchIfNeededInBackgroundWithBlock { (result, error) -> Void in
self.modified = currentUser.objectForKey("modified") as! Bool
var idOfUser: String = currentUser.objectId
println("User \(idOfUser) UPDATED")
println(self.modified)
if self.modified == true {
self.deleteData()
self.fetchAllObjects()
}
}
}
When running the console gives me this:
Refreshed
User xTbBw6cNzK UPDATED
false
Here is a screenshot I just took of the server side:
Thank you all for your attention
I am not sure what version of swift you are using. But if you are using Swift 2.0 and Xcode 7, this SHOULD do the job.
This will not work:
let modifiedStatus = PFUser.currentUser()?["modified"] // return value will be nil
This will work for sure:
let modifiedStatus = PFUser.currentUser()?.objectForKey("modified")
print(modifiedStatus) // true as per your table
I know this may sound strange, but some I struggle with something for hours later realising my silly mistake. So it is always good to move back of the current task and later recheck after few hours. So just make sure you cross check the following:
The key "modified" in the main user class of parse
You can retrieve other key values (just to see if nothing else is wrong other than your current retrieving of a key bool value(
Though I am on Swift 2.0, but for sure there is no major change from in this specific code when it comes to move from Swift 1.2 to Swift 2.0.
Just see and if it still doesn't work, we can discuss more on your setup.
I have initialised a "doneSetUp" var as a local variable, it is an int.
then I query the user which just logged in
checks if it exists...
check if the variable userDidSetUp exists in parse and if it does I am converting it to an int and assign it to the local variable doneSetUp I made
then I am using the "doneSetUp" variable which now has a value of 0(false) or 1(true) to decide if the user already setup his account or not and then segue the user to the correct view controller.
mention that all of this code is inside of my logininbackgroundwithblock function.
I hope that helped.
query?.getObjectInBackgroundWithId(user!.objectId!, block: {
(user, error) -> Void in
if let user = user{
if var userDidSetUp: AnyObject = user["doneSetUp"] {
self.doneSetUp = userDidSetUp as! Int
if self.doneSetUp == 0 {
self.performSegueWithIdentifier("procceedToSetup", sender: self)
}else{
self.performSegueWithIdentifier("procceedToApp", sender: self)
}
}
}
})
I know it's an old post, but here's what worked for me.
This is inside the viewDidLoad method.
PFUser.currentUser()?.fetchInBackgroundWithBlock({ (object, error) -> Void in
var modified = PFUser.currentUser()?.objectForKey("modified")?.boolValue
if modified == true {
print(modified) // Output console displays "true"
Hope this helps.

Resources