I have a project that find objects in Parse as an Array and gets it when the app finish launches. But it can't get the info when there are no network connections so i'm thinking to use NSUserDefaults and firstly save the data to NSUserDefaults and get from NSUserDefaults when it is offline.
Can somebody give me a example of logic?
this is the code i wrote and i don't know how to get it when it's offline.
the Array the i want to append the data from Parse will be [[String]].
let userDefaults = NSUserDefaults.standardUserDefaults
let query = ClassSub.query()
query.whereKey("nameOfData", containsString: "testString")
query!.findObjectsInBackgroundWithBlock { (busObjects: [PFObject]?, error: NSError?) -> Void in
if error != nil {
print(error)
}
for object in testObjects! {
let testData = object["arrayData"] as? [[String]]
for i in 0..<testData!.count {testData
testArrayappend(testData![i])
self.userDefaults.setObject(NSKeyedArchiver.archivedDataWithRootObject(testData[i]), forKey: "test-time")
}
}
}
the data type in Parse is [[String]] and it looks like [["one","two"],["one","three"],["one","four"]]
i think it's better for you to use parse ios SDK local data store. The local data store allows you to pin your parse objects into a local database and then when you are out of network you can still get the data from your local database. Another huge advantage is the saveEventually feature which allows you to save objects while you are offline and then sync them back to the server as soon as you go online.
In order to use the local data store feature you need to do the following steps:
Enable local data store in parse config
let configuration = ParseClientConfiguration {
$0.applicationId = "{PARSE_APP_ID}"
$0.server = "http://localhost:1337/parse"
$0.localDatastoreEnabled = true
}
Parse.initializeWithConfiguration(configuration)
If you want to query from the local data store (while you are offline) you just need to call an additional function before calling the findObjectsInBackground so your code should look like the following:
let query = PFQuery(className: "MyParseObjectClassName")
// if you are in offline mode then make sure the query
// will access your local data store and not to the server
if (offlineMode){
query.fromLocalDatastore()
}
query.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error: NSError?) -> Void in
// do something with the result
}
When you are using local data store you are required to pin the objects that are fetched from your server. In order to pin an object simply call to pinInBackground() which exist under your PFObject. You can also use pinAllInBackground() to pin multiple objects to your local data store in one call. In order to pin your objects please do the following:
let query = PFQuery(className: "MyParseObjectClassName")
// if you are in offline mode then make sure the query
// will access your local data store and not to the server
if (self.offlineMode){
query.fromLocalDatastore()
}
query.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error: NSError?) -> Void in
if (!self.offlineMode){
// pin all objects
PFObject.pinAllInBackground(objects)
// pin only first object
let obj = objects?.first
obj?.pinInBackground()
}
}
Now in order to know when you are offline or online i recommend you to use Reachability library.
This library provides you two blocks: when you are online and when you are offline. Using those blocks you can determine when your app is connected to the internet and when it doesn't. So when it is not connected to the internet you need to set the offlineMode flag to be true and from now on all the queries will work against your local database otherwise it will work against your server.
Related
I've been wandering around google, stackoverflow and internet trying to understand how to work with core data and deal with the concurrency.
Consider that we have 2 tables, Events and Rooms.
An Event can have 1+ Rooms.
FunctionA - AddEvent
FunctionB - AddRoom
FunctionC - SearchRoom -> returns RoomEntity or nil
My problem, I keep getting these errors
Error Domain=NSCocoaErrorDomain Code=133020 "Could not merge changes." UserInfo={conflictList=(
"NSMergeConflict (0x10a507160) for NSManagedObject (0x1092f00c0) with objectID '0xd000000000040000 <x-coredata://A34C65BD-F9F0-4CCC-A9FB-1B1F5E48C70E/Rooms/p1>' with oldVersion = 116 and newVersion = 124 and old object snapshot = {\n location = Lisboa;\n name = \"\\U00cdndico LX\";\n} and new cached row = {\n location = Lisboa;\n name = \"\\U00cdndico LX\";\n}"
Notice the information of the Rooms is equal
my approach is the following.
1- I call the webservice once ( it brings a json with data of 3 types of Events ) These 3 all have the same json structure and share the same managedObjectContext passed by parameter
2- I create a managedObject
var managedObjectContext: NSManagedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
managedObjectContext = persistentContainer.viewContext
managedObjectContext.parent?.mergePolicy = NSMergePolicyType.mergeByPropertyObjectTrumpMergePolicyType
3-
managedObjectContext.perform(
{
do
{
try self.deleteAllEventsFromDb()
FunctionA(eventList, managedObjectContext) -> save
FunctionA(eventList2, managedObjectContext) -> save
FunctionA(eventList3, managedObjectContext) -> save
self.DatabaseDispatchGroup.enter()
try managedObjectContext.save()
self.DatabaseDispatchGroup.leave()
completion(Result.Success(true))
}
catch let error as NSError
{
print("Could not save. \(error), \(error.userInfo)")
completion(Result.Success(false))
}
})
4- For each Event I execute the same FunctionA to create and save the data in database (managedObjectContext.insert(eventEntity)) . This will work over several tables but lets only consider Events and Rooms(FunctionB).
5- FunctionA contains functionB. Function B search for an existing Room(FunctionC->returns entity?) if it doesn't exists(nil), it creates the entity ( should I save here? )
6- If a Room exists, gets the entity and tries to update the data
Not sure if its making any difference but when I save I do these saves I do it between a dispatchGroup
DatabaseDispatchGroup.enter()
try managedObjectContext.save()
DatabaseDispatchGroup.leave()
I was using a static managedObjectContext which was used for all the database requests but now I decided to create a objectContext per function which accesses the database.
I do keep the same persistentContainer and the same DispatchGroup for all requests
private override init() {
persistentContainer = NSPersistentContainer(name: "DataModel")
persistentContainer.loadPersistentStores() { (description, error) in
if let error = error {
fatalError("Failed to load Core Data stack: \(error)")
}
}
}
It seems to me that my problem is that I am Storing the changes in memory, and always doing updates over the initial data meaning that when I execute save() the context the data is not updated for the next operation?
How/when am I suppose to execute the save function?
Thank you
Once context is being saved, global notification is being posted: ContextDidSave notification.
When using multiple contexts (and not using parent-child approach) you should use this notification and either:
Re-fetch/refresh data in case you need to update view or perform some operation on new data set (using either fetch request or refreshObjects: API).
Merge changes to other contexts (remember about thread confinement! do that only on proper context queues). (merge doc)
There are many articles about it, check for instance this tutorial
and documentation
var objectarray = [PFObject]()
func populateTable() {
query.findObjectsInBackgroundWithBlock { (objects, error) in
self.objectarray.removeAll(keepCapacity: true)
self.searchTableView.reloadData()
if error == nil {
Above is the query I am doing and the below 2 codes are what I can use to use the query to populate a array.
if let objects = objects as [PFObject]! {
self.objectarray = Array(objects.generate())
}
Is there any difference with running this code above to populate my array or running the code below?
for object in objects! {
self.objectarray.append(object)
}
Doing either works to load onto my tableView. Also another question regarding Parse. After doing the above, the user doesn't download PFFiles from the background until I run
getDataInBackgroundWithBlock
right? I want to know if it'd be beneficial to save smaller versions of images onto the server.
So this isn't a question about actually populating a table with data because I already have that working properly.
I have a page that retrieves a list of nearby users - it grabs their name, distance away and profile image. The problem is that because some profile images are bigger than others the correct profile image isn't always put alongside the corresponding name and other data.
let profileImgFile = location["profilePicture"]
profileImgFile!.getDataInBackgroundWithBlock { (data: NSData?, error: NSError?) -> Void in
if error != nil {
print("\(__FUNCTION__) - Error: (error!.localizedDescription)")
} else {
let image = UIImage(data: data!)
self.userImages.append(image!)
self.userTable.reloadData()
}
}
This is how I'm retrieving the images, they're added to an array so that the table can be populated here:
cell.nameLabel.text = "\(userData[indexPath.row])"
cell.profileImage.image = userImages[indexPath.row]
cell.distanceLabel.text = ("\(userDistance[indexPath.row])")
So, I wondered if there was any way of retrieving the images one after the other. So that the next image doesn't begin downloading until the first one has finished.
If not is there any other way of fixing this problem?
Thanks!
I think you should try setting your data async with promises.
Promises in Swift
PromiseKit
And there's a lot of other resources out there.
I have Parse.enableLocalDatastore() in my app delegate before Parse.setApplicationId
Then I have var newPosts = PFObject(className: "Post") as a global variable.
Then I want to get 1,000 latest objects from the "Post" table from localDataStore that I enabled earlier so I do this:
var getNewPosts = PFQuery(className: "Post")
getNewPosts.fromLocalDatastore()
getNewPosts.limit = 1000
getNewPosts.orderByDescending("createdAt")
getNewPosts.findObjectsInBackgroundWithBlock {
(downloadedPosts, error) -> Void in
if downloadedPosts != nil && error == nil {
println(downloadedPosts) // I am getting table with no data
}
}
But I only get empty data rows.
If I comment out the getNewPosts.fromLocalDatastore() line results are fine.
I understand that I am missing the critical Pinning step but not sure from Parse documentation hoe and where to implement it. Can you please help?
You are getting no data....
reasons my be...
Wrong name of class (class names are case sensitive)
Data is not there in local storage
Try synchronous version of findObject: method and pin: method.
I am very new to swift and I don't know Obj C at all so many of the resources are hard to understand. Basically I'm trying to populate the dictionary with PFUsers from my query and then set PFUser["friends"] to this dictionary. Simply put I want a friends list in my PFUser class, where each friend is a PFUser and a string.
Thanks!
var user = PFUser()
var friendsPFUser:[PFUser] = []
var friendListDict: [PFUser:String] = Dictionary()
var query = PFUser.query()
query!.findObjectsInBackgroundWithBlock {
(users: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(users!.count) users.")
// Do something with the found objects
if let users = users as? [PFUser] {
friendsPFUser = users
for user in friendsPFUser{
friendListDict[user] = "confirmed"
}
user["friends"] = friendListDict //this line breaks things
user.saveInBackground()
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
To be clear, this code compiles but when I add
user["friends"] = friendListDict
my app crashes.
For those who might have this issues with. "NSInternalInconsistencyException" with reason "PFObject contains container item that isn't cached."
Adding Objects to a user (such as arrays or dictionaries) for security reasons on Parse, the user for such field that will be modified must be the current user.
Try signing up and using addObject inside the block and don't forget do save it!
It helped for a similar problem I had.