Parse.com Multiple Users accessing data - Swift - ios

I am making a to do app using parse.com. I have got data which is set by a user and they should be able to assign that data to another user too. So the user sets a title, text and the name of the user they wish to assign it to. All of this is stored in the database and works well. When the same user logs in the data they set is displayed for him in the table view controller.
However the user they assigned to see the data does not display. So let me explain with code. The code underneath allows the user to see the data they set when they go to Table view screen. The code under is in the viewDidAppear function.
func fetchAllObjectsFromLocalDatastore() {
var query: PFQuery = PFQuery(className: "toDo")
query.fromLocalDatastore()
query.whereKey("username", equalTo: PFUser.currentUser().username)
query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if (error == nil) {
var temp: NSArray = objects as NSArray
self.toDoObjects = temp.mutableCopy() as NSMutableArray
self.tableView.reloadData()
}else {
println(error.userInfo)
}
}
With this, I have another function:
func fetchAllObjects() {
PFObject.unpinAllObjectsInBackgroundWithBlock(nil)
var query: PFQuery = PFQuery(className: "toDo")
query.whereKey("username", equalTo: PFUser.currentUser().username)
query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if (error == nil) {
PFObject.pinAllInBackground(objects, block: nil)
self.fetchAllObjectsFromLocalDatastore()
}else {
println(error.userInfo)
}
}
}
The user he wants to see that data to is assigned in the add new to do screen and that works. It even displays on the Parse.com database. But how can I query it or get it to show that data for the user who the data has been assigned to not just the user who set it.
So when (for example) UserOne sets data, it appears for him when he logs in and goes to table view, but even when he assigns to (for example) UserTwo and UserTwo logs in and goes to table view where the data is meant to be, I just do not know HOW TO DO??!
Please do help me or give me guidance, I am still searching on the solution to this, I feel like it is really simple but I cannot put my finger on it.
UPDATE *2
When the data is set, it is set with who the UserOne would like to give access to the data to.
So here is the saving process I guess:
#IBAction func saveAction(sender: UIBarButtonItem) {
self.object["username"] = PFUser.currentUser().username
self.object["title"] = self.titleField?.text
self.object["text"] = self.textView?.text
self.object["forUser"] = self.userToAssignTo?.text
self.object.saveEventually { (success, error) -> Void in
if (error == nil) {
}else{
println(error.userInfo)
}
}
So then I tried to query the forUser:
func fetchAllObjects() {
PFObject.unpinAllObjectsInBackgroundWithBlock(nil)
var query: PFQuery = PFQuery(className: "toDo")
query.whereKey("forUser", equalTo: PFUser.currentUser().username)
query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if (error == nil) {
PFObject.pinAllInBackground(objects, block: nil)
self.fetchAllObjectsFromLocalDatastore()
}else {
println(error.userInfo)
}
}
}
This still did not seem to work. I'm not sure what I'm doing now! Damn I just don't know how I can let another user see the data that one user has set.
UPDATE 3**
I found it! It was all about the viewDidAppear function, so here is the code:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if (PFUser.currentUser() == nil) {
var logInViewController = PFLogInViewController()
logInViewController.delegate = self
var signUpViewController = PFSignUpViewController()
signUpViewController.delegate = self
logInViewController.signUpController = signUpViewController
self.presentViewController(logInViewController, animated: true, completion: nil)
}else {
//self.fetchAllObjectsFromLocalDatastore()
//self.fetchAllObjects()
self.fetchAllObjectsFromLocalDatastore2()
self.fetchAllObjects2()
}
}
It is basically because the fetchAllObjects was kind of wiping it out for the "forUser" key. But now I need to figure out how to do it so the fetchAllObjects don't wipe each other off because it almost like a refresh button and then all the data is wiped off the screen.

I have been working on a similar issue however with push notifications from parse. I can provide code examples later today. There are a couple ways to approach. When the user creates a new to do you can set a custom object with the user they want to assign to and you can query based on the field. You can save the to do with saveinbackground with block. Another way is to use PFRelation. Watched a few courses on team tree house to review and see different techniques and they have one that is a self destructing messaging app that uses parse for functions much similar to what you want to do.

Related

swift parse app crashes after logout when app resumes

My code has a weird bug I'm having trouble solving.
I've built an app using swift and parse. You log in, and are taken to the main page thats a table view. Now at this point if you are logged in, and leave and comeback to the app, everything is fine and dandy.
However, when I log out, the user is then taken back to the log in screen. Now if the user leaves the app, but comes back, the app crashes giving me the error unexpectedly found nil while unwrapping an optional value.
It makes no sense to me what could be happening. When the app launches fresh from scratch the user is set to nil, then the user is logged in and everything is cool. When you log out, the user is then set back to nil and taken to the log in screen. If you resume the app, crash.
Is this an issue with how I'm logging out the user?
The relevant code is posted below..
On the login page:
#IBAction func loginButton(sender: AnyObject) {
//checks if there is a matching username with a matching password. if so, lets the user log in.
PFUser.logInWithUsernameInBackground(username.text, password: userPassword.text) {
(user: PFUser?, error: NSError?) -> Void in
if user != nil {
// Do stuff after successful login.
//go to main table view
//get current user
//display current users locations
println("login success")
//shows home screen after successful login.
self.performSegueWithIdentifier("showHomeFromLogin", sender: self)
} else {
// The login failed. Check error to see why.
//display error?
self.displayAlert("Login Failed", alertMessage: "Double check to make sure the username and password are correct.")
println("login failed")
}
}
}
var activeField: UITextField?
override func viewDidLoad() {
super.viewDidLoad()
username.delegate = self
userPassword.delegate = self
registerForKeyboardNotifications()
// Do any additional setup after loading the view.
//setup so when we tap outside the edit, we close keyboard.
var tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "DismissKeyboard")
view.addGestureRecognizer(tap)
}
override func viewDidAppear(animated: Bool) {
if PFUser.currentUser() != nil {
self.performSegueWithIdentifier("showHomeFromLogin", sender: self)
}
}
On the home page where the user logs out:
#IBAction func logoutButton(sender: AnyObject) {
PFUser.logOut()
var currentUser = PFUser.currentUser() // this will now be nil
self.performSegueWithIdentifier("logoutSegue", sender: self)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
NSNotificationCenter.defaultCenter().addObserver(self, selector: "launchSync", name: UIApplicationDidBecomeActiveNotification, object: nil)
//setting table view datasource and delegate.
self.tableView.dataSource = self
self.tableView.delegate = self
var currentUser = PFUser.currentUser()
println(currentUser)
}
Breaks in here I think on the line query.whereKey("User", equalTo:PFUser.currentUser()!):
func launchSync() {
var query = PFQuery(className:"ParseLighthouse")
query.whereKey("User", equalTo:PFUser.currentUser()!)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(objects!.count) lighthouses.")
// Do something with the found objects
if let light = objects as? [PFObject] {
for object in light {
println(object.objectId)
}
}
println(objects?.count)
self.syncToLighthouse(objects!)
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
}
This launchsync function is called in the view did appear method on my home screen. Is it possible that when the logout segue is performed, the main view controllers is still running in the background so when I resume, that code cant find the user now that its set back to nil?
On the following line
NSNotificationCenter.defaultCenter().addObserver(self, selector: "launchSync", name: UIApplicationDidBecomeActiveNotification, object: nil)
you say to call the launchSync function whenever the app becomes active.
As mentioned by Paulw11, inside that function you are force unwrapping (using !) on PFUser.currentUser() which will return nil when the user is logged out.
You can address this by ensuring that the current user isn't nil
func launchSync() {
if (PFUser.currentUser() != nil) {
var query = PFQuery(className:"ParseLighthouse")
query.whereKey("User", equalTo:PFUser.currentUser()!)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(objects!.count) lighthouses.")
// Do something with the found objects
if let light = objects as? [PFObject] {
for object in light {
println(object.objectId)
}
}
println(objects?.count)
self.syncToLighthouse(objects!)
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
}
}
If there is the potential for a conditional value to be nil then you need to check the conditional before you use it. When there is no currently logged in user, PFUser.currentUser() will return nil. When you force unwrap this value with ! you get an exception.
You can change your code to conditionally unwrap the value -
func launchSync() {
if let currentUser=PFUser.currentUser() {
var query = PFQuery(className:"ParseLighthouse")
query.whereKey("User", equalTo:currentUser)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(objects!.count) lighthouses.")
// Do something with the found objects
if let light = objects as? [PFObject] {
for object in light {
println(object.objectId)
}
}
println(objects?.count)
self.syncToLighthouse(objects!)
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
}
}

Execute swift code in order

I need the "getUserInfo" to complete before I execute the next section of code (the push to storyboard). Currently the "getUserInfo" is still in process while the storyboard push executes. How can I make these execute in order? I'm need to keep these 2 functions separate, so putting the code in the completion handler of loginUser isn't a good solution. Many thanks to those who are smarter than me :)
func loginUser() {
PFUser.logInWithUsernameInBackground(txtEmailAddress.text, password:txtPassword.text) {
(user: PFUser?, error: NSError?) -> Void in
if user != nil {
// Successful login.
self.txtPassword.resignFirstResponder()
self.txtEmailAddress.resignFirstResponder()
getUserInfo()
// Push to Main.storyboard.
let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
let viewController: AnyObject = storyboard.instantiateInitialViewController()
self.presentViewController(viewController as! UIViewController, animated: true, completion: nil)
} else {
// The login failed. Display alert.
self.displayAlert("Error", message: "Login incorrect")
}
}
}
func getUserInfo() {
let currentUser = PFUser.currentUser()
let userQuery = PFQuery(className: "_User")
userQuery.whereKey("username", equalTo: PFUser.currentUser()!.username!)
userQuery.findObjectsInBackgroundWithBlock({ (results:[AnyObject]?, error:NSError?) -> Void in
if error == nil {
for result in results! {
userType = result["userType"] as! String
if userType == "admin" {
user = "AdminSetting"
} else {
user = "StandardSetting"
}
}
}
})
}
What you want to do is make the asynchronous function (the one with the completion handler) synchronous, so that it returns immediately. However that's usually a bad idea, because if the execution stops in the main thread, the user can't do anything and your app is stuck until the code continues again. This might take a couple seconds depending on the connection, which isn't very good, you should really update your UI asynchronous on asynchronous tasks. There usually aren't good reasons to do something like this, if you have them though, you can tell me.
You could also execute your storyboard code within getUserInfo() as a block/closure passed in as a parameter. That way you can ensure it is executed when the async call in getUserInfo completes
What Shadowman suggested is the correct/most elegant solution.
Here is an example:
func getUserInfo(completion: (results:[AnyObject]?, error:NSError?) -> Void) {
let currentUser = PFUser.currentUser()
let userQuery = PFQuery(className: "_User")
userQuery.whereKey("username", equalTo: PFUser.currentUser()!.username!)
userQuery.findObjectsInBackgroundWithBlock(completion)
}
This way, you get back the actual results from your call after it finished, handy, eh ?
And here is how you call it:
self.getUserInfo { (results, error) -> Void in
// Here the results are already fetched, so proceed with your
// logic (show next controller or whatever...)
if error == nil {
for result in results! {
userType = result["userType"] as! String
if userType == "admin" {
user = "AdminSetting"
} else {
user = "StandardSetting"
}
}
}
// depending on whether this will still run in a background thread, you might have to dispatch this code to the main thread.
// you can check whether this code block is called on the main thread
// by checking if NSThread.isMainThread() returns true
// if not, you will need to use this dispatch block!
dispatch_async(dispatch_get_main_queue(), { () -> Void in
// only call UI code in main thread!
// MOVE TO NEXT controller in here!
})
}

Swift Parse Query how can i make uialertview after for object in objects?

i'm doing parse query and i want to do UIAlertView after for object in objects {} done!, if i put the alertview inside the for object in objects{//here}, nothing happen, how can i detect when for objects in objects {} done then i show the alertview ??
the code looks like this:
var query = PFUser.query()
query = PFQuery(className: "_User")
query.findObjectsInBackgroundWithBlock {
(object: [AnyObject]!, error:NSError?) -> Void in
for object in objects {
// alertview here not working, How can i make it after this loop done?
}
}
Any help?
var query = PFUser.query()
query = PFQuery(className: "_User")
// Heads up, entering background thread, may take some time to update the UI, maybe add an activity indicator even?
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
for object in objects as! [PFObject] {
// alertview here not working, How can i make it after this loop done?
// This would make an alert for every found object
}
// Anything after the for loop will happen after it completes, so if you put something here it will execute, suggest dispatching anything UI related to the main queue
dispatch_async(dispatch_get_main_queue(), { () -> Void in
// visual interface stuff should happen here
})
}

Swift Language: Updating Parse objects

Good evening Coders!
I created a query that gets the username and location of people around me within 7miles of device location, I would like to update these objects every 5-10seconds(I haven't decided yet). What would be the best practice for this? should i create an NSTimer() and call it that way? please help!!
var timer = NSTimer()
override func viewDidLoad() {
super.viewDidLoad()
timer == NSTimer.scheduledTimerWithTimeInterval(5, target: self, selector: "queryFunc", userInfo: nil, repeats: true)
}
func queryFunc() {
var query = PFQuery(className:"GameScore")
query.whereKey("playerName", equalTo:"Sean Plott")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
// The find succeeded.
NSLog("Successfully retrieved \(objects.count) scores.")
// Do something with the found objects
for object in objects { NSLog("%#", object.objectId)
}
} else {
// Log details of the failure
NSLog("Error: %# %#", error, error.userInfo!)
}
}
/*
This is just an example query, the point of this is the timer and how to update objects periodically
*/
}
try to add whereKey:nearGeoPoint:withinMiles: in your query which will find objects within 7 miles from your location
For further details for that check out this link
and when you found data after each query then remove all previous data and replace it with new data and refresh your view.
NSTimer is probably the Good Option go for it. Even I also have used timer to sync in background in every 5 min and it's working fine for me.
Hope this will help you.

Reloading CloudKit data when app comes to foreground

I'm trying to understand CloudKit, but I'm having an issue figuring out a real-world issue if I eventually turned it into an app.
I've got a tableviewcontroller that's populated by CloudKit, which works fine. I've got a detailviewcontroller that is pushed when you tap one of the entries in the tableview controller. The detailviewcontroller pulls additional data from CloudKit that wasn't in the initial tableviewcontroller. All of this works perfectly fine.
Now, the detailviewcontroller allows changing of an image and saving it back to a public database. That's working fine as well.
The scenario I'm dealing with is thinking about this core function being used in an app by multiple people. If one person uploads a photo (overwriting an existing photo for one of the records), what happens if another user currently has the app in the background? If they open the app, they'll see the last photo they saw on that page, and not the new photo. So I want to reload the data from CloudKit when the app comes back to the foreground, but I must be doing something wrong, because it's not working. Using the simulator and my actual phone, I can change a photo on one device and the other device (simulator or phone) doesn't update the data when it comes into the foreground.
Here's the code I'm using: First in ViewController.swift:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
println("viewDidLoad")
NSNotificationCenter.defaultCenter().addObserver(self, selector: "activeAgain", name: UIApplicationWillEnterForegroundNotification, object: nil)
and then the activeAgain function:
func activeAgain() {
println("Active again")
fetchItems()
self.tblItems.reloadData()
println("Table reloaded")
}
and then the fetchItems function:
func fetchItems() {
let container = CKContainer.defaultContainer()
let publicDatabase = container.publicCloudDatabase
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "Items", predicate: predicate)
publicDatabase.performQuery(query, inZoneWithID: nil) { (results, error) -> Void in
if error != nil {
println(error)
}
else {
println(results)
for result in results {
self.items.append(result as! CKRecord)
}
NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
self.tblItems.reloadData()
self.tblItems.hidden = false
})
}
}
}
I tried adding a reload function to viewWillAppear, but that didn't really help:
override func viewWillAppear(animated: Bool) {
tblItems.reloadData()
}
Now here's the code in DetailViewController.swift:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
NSNotificationCenter.defaultCenter().addObserver(self, selector: "activeAgain", name: UIApplicationWillEnterForegroundNotification, object: nil)
self.imageScrollView.contentSize = CGSizeMake(1000, 1000)
self.imageScrollView.frame = CGRectMake(0, 0, 1000, 1000)
self.imageScrollView.minimumZoomScale = 1.0
self.pinBoardScrollView.maximumZoomScale = 8.0
showImage()
and then the showImage function:
func showImage() {
imageView.hidden = true
btnRemoveImage.hidden = true
viewWait.hidden = true
if let editedImage = editedImageRecord {
if let imageAsset: CKAsset = editedImage.valueForKey("image") as? CKAsset {
imageView.image = UIImage(contentsOfFile: imageAsset.fileURL.path!)
imageURL = imageAsset.fileURL
self.title = "Image"
imageView.hidden = false
btnRemoveImage.hidden = false
btnSelectPhoto.hidden = true
}
}
}
and the activeAgain function:
func activeAgain() {
showImage()
println("Active again")
}
Neither of these ViewControllers reload the data from CloudKit when they return. It's like it's cached the table data for the first ViewController and the image for the DetailViewController and it's using that instead of downloading the actual CloudKit data. I'm stumped, so I figure I better try this forum (my first post!). Hopefully I included enough required info to be useful.
Your showImage function is using a editedImage CKRecord. Did you also reload that record? Otherwise the Image field in that record would then still contain the old CKAsset.
Besides just reloading on a moment you feel right, you could also let the changes be pushed by creating a CloudKit subscription. Then your data will even change when it's active and changed by someone else.

Resources