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.
Related
I'm somewhat new to this and this is my first question on stackoverflow. Thanks in advance for your help and bear with me if my formatting sucks
I've got multiple views within my app (all displaying data using tableview subviews) that need to update automatically when the data changes on the database (Firestore), i.e. another user updates the data.
I've found a way to do this which is working well, but I want to ask the community if there's a better way.
Currently, I am creating a Timer object with a timeInterval of 2. On the interval, the timer queries the database and checks a stored data sample against updated data. If the two values vary, I run viewDidLoad which contains my original query, tableView.reloadData(), etc..
Any suggestions or affirmations would be very useful.
var timer = Timer()
var oldChallengesArray = [String]()
var newChallengesArray = [String]()
override func viewDidLoad() {
super.viewDidLoad()
//set tableview delegate
mainTableView.delegate = self
mainTableView.dataSource = self
//set challengesmodel delegate
challengesModel.delegate = self
//get challenges
DispatchQueue.main.async {
self.challengesModel.getChallenges(accepted: true, challengeDenied: false, incomingChallenges: false, matchOver: false)
self.mainTableView.reloadData()
}
scheduledTimerWithTimeInterval()
}
func scheduledTimerWithTimeInterval(){
// Scheduling timer to Call the function "updateCounting" with the interval of 1 seconds
timer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(self.updateTableView), userInfo: nil, repeats: true)
}
#objc func updateTableView(){
ChallengeService.getAllUserChallengeIDs(accepted: true, challengeDenied: false, matchOver: false) { (array) in
if array.isEmpty {
return
} else {
self.newChallengesArray = array
if self.oldChallengesArray != self.newChallengesArray {
self.oldChallengesArray = self.newChallengesArray
self.newChallengesArray.removeAll()
self.viewDidLoad()
}
}
}
}
Firestore is a "realtime database", that means that the database warns you when changes happen to the data. To achieve that the app needs to subscribe to relevant changes in the db. The sample code below can be found here:
db.collection("cities").document("SF")
.addSnapshotListener { documentSnapshot, error in
guard let document = documentSnapshot else {
print("Error fetching document: \(error!)")
return
}
guard let data = document.data() else {
print("Document data was empty.")
return
}
print("Current data: \(data)")
}
Also, I would like to point out that calling viewDidLoad is incorrect, you should never call viewDidLoad yourself, create an func to update the data. Something like this:
DispatchQueue.main.async {
self.mainTableView.reloadData()
}
var ref = Database.database().reference().child("Essages")
var childRef = Database.database().reference().child("Essages").childByAutoId()
#IBAction func sendBtn(_ sender: Any) {
posting()
}
func posting(){
let values = ["message" : captionTextView.text] as [String : Any]
childRef.updateChildValues(values) { (error, ref) in
if error != nil {
print("error")
}else {
let = Timer.scheduledTimer(timeInterval: 90000, target: self, selector: #selector(self.onTick), userInfo: nil, repeats: false)
}
}
}
func onTick(){
childRef.removeValue()
}
If the app is open then the timer is working and it is deleting the data. But if I close the app the timer is not working and the data is not deleted. Please help me in sorting this problem. I'm trying to get feature like snapchat (deleting in 24hrs).
You could try perhaps a new Firebase cloud function feature, which is I think the best and easiest option. It was in beta until couple of weeks ago but now it's fully functional.
There was a similar question on another thread here. You can also find a nice tutorial on the Firebase Blog. In general, following some good coding practices, you shouldn't be triggering this from a client. Data cleanup, and in any other form of updates should be done by the backend itself.
I have recently been playing around with parse,and yes, I know that it is closing soon:(. I have this error however that keeps on coming up. I am trying to update my textview.text to parse every single second. However, when the code runs a message comes up saying "no results matched the query", even though textview.text is not empty. I would really appreciate your help. Thanks.
override func viewDidLoad() {
super.viewDidLoad()
notes["Content"] = detailDescriptionLabel.text
notes.saveInBackgroundWithBlock { (succes, error) -> Void in
if error != nil {
print("unable to save objects")
}
}
scheduledTimerWithTimeInterval()
// Do any additional setup after loading the view, typically from a nib.
self.configureView()
}
func scheduledTimerWithTimeInterval(){
// Scheduling timer to Call the function **Countdown** with the interval of 1 seconds
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("updateText"), userInfo: nil, repeats: true)
}
func updateText () {
let query = PFQuery(className: "Notes")
query.getObjectInBackgroundWithId("Content") { (notes, error) -> Void in
notes?["Content"] = self.detailDescriptionLabel.text
print("Updated")
}
}
Picture of error in Log
Image of Parse Dashboard
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!)")
}
}
}
}
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.