Parse retrieve objectId from class causes app to stop (not crash) - ios

I have already submitted a question on how to retrieve an objectId (swift - retrieve objectId field causes fatal error) but that seems to only apply to objects during a save because I have spent a long time (days) trying to retrieve the objectId from a saved row in a Parse cloud DB with absolutely no luck. I've mimicked the code in the Parse object retrieval documentation (https://parse.com/docs/ios_guide#objects-retrieving/iOS), using the special values provided for objectId's:
let objectId = gameScore.objectId
I was getting a crash every time but now the app just stops after printing the successful login message. Here is the code and sequence of events.
I signup a new user (let's call him vin).
The signup code:
#IBAction func signUp(sender: AnyObject) {
if countElements(self.userNameTextField.text) > 0 && countElements(self.passWordTextField.text) > 0 {
var megabyker = PFUser()
user.username = self.userNameTextField.text
user.password = self.passWordTextField.text
user.signUpInBackgroundWithBlock{
(succeeded:Bool!, error:NSError!)->Void in
if error == nil {
println("Sign Up successfull")
//associate current user for parse remote push notifications
var installation:PFInstallation = PFInstallation.currentInstallation()
installation.addUniqueObject("goride", forKey: "channels")
installation["user"] = PFUser.currentUser()
installation.saveInBackground()
//default row for allData table
//save default settings row for GUEST user
var alldata:PFObject = PFObject(className: "allData")
alldata["divvy_routes"] = "ON"
alldata["sortBy"] = "distance"
alldata["totalRides"] = 0
alldata["username"] = user.username
//self.useGuestUser = "USER"
alldata["user"] = PFUser.currentUser()
alldata.saveInBackgroundWithBlock{(success:Bool!, error:NSError!) ->Void in
if success != nil {
NSLog("%#","OK-alldata REAL data saved")
self.allDataSignUpObjectId = alldata.objectId
println("printing out objectId var in ALLDATA REAL section: ")
println(self.allDataSignUpObjectId)
self.performSegueWithIdentifier("go-to-main-menu", sender: self)
}
else
{
NSLog("%#",error)
}
}
} else {
let alert = UIAlertView()
alert.title = "Invalid Signup"
alert.message = "Sorry, a username (unique) and a password are required to create a new account."
alert.addButtonWithTitle("OK")
alert.show()
}
}
}
else
{
let alert = UIAlertView()
alert.title = "Invalid Signup"
alert.message = "Sorry, a username (unique) and a password are required to create a new account."
alert.addButtonWithTitle("OK")
alert.show()
}
}
The signup code works fine. It creates a new user and row in the User table and new default row in the allData table. The allData table has a user pointer column to associate it with the User table. I grab the objectId and assign it to an instance variable because I want to pass it to another viewcontroller to use:
self.allDataSignUpObjectId = alldata.objectId
println(self.allDataSignUpObjectId)
And here is a screenshot of the ParseDB where you can see the row has been saved in the user table for the new user. His objectId is 9vRF8BvdlZ and the row in the allData table has an objectId of DEaj0iWZ3X. This is the objectId I am trying to get (DEaj0iWZ3X).
allData table:
Now here is the allData where the new user's default setting row is inserted and it's automatically linked to the Vin's user table:
I then go to the Settings view controller and Save as the newly created user:
#IBAction func updateSettings(sender: AnyObject) {
var alldata:PFObject = PFObject(className:"allData")
var user = PFUser.currentUser()
var query = PFQuery(className:"allData")
query.whereKey("user", equalTo: user)
var id = allDataPass
println(id)
query.getObjectInBackgroundWithId(id) {
(alldata: PFObject!, error: NSError!) -> Void in
if error != nil {
NSLog("%#", error)
} else {
alldata["routes"] = self.RoutesSetting
alldata.saveInBackground()
}
}
}
It saves with no problems and the updated time in the row updates.
But when I logback in as Vin (after resetting the contents and settings on the simulator to get a fresh start), I get to LOGIN but it just stops (not crash, just stops and won't get past the login screen).
Here's the console printout:
2015-02-26 22:19:01.732 MegaByke[51906:6996084] Location Services Not Determined, must ask user
2015-02-26 22:19:01.734 MegaByke[51906:6996084] location services enabled: true
Location services success - <MegaByke.AppDelegate: 0x7ff4b145bd80>
2015-02-26 22:19:15.700 MegaByke[51906:6996084] Reachability Flag Status: -R -----l- networkStatusForFlags
<PFUser: 0x7ff4b167f830, objectId: 9vRF8BvdlZ, localId: (null)> {
username = vin;
}
Login successfull
objectId preFETCH
nil
Here is my login code:
#IBAction func login(sender: AnyObject) {
switch reachability!.currentReachabilityStatus().value{
case NotReachable.value:
var alert = UIAlertView(title: "No Internet Connection", message: "In order to login, you must have internet connection. You can still login as a Guest", delegate: nil, cancelButtonTitle: "Dismiss")
alert.show()
default:
if countElements(self.userNameTextField.text) > 0 && countElements(self.passWordTextField.text) > 0 {
PFUser.logInWithUsernameInBackground(userNameTextField.text, password: passWordTextField.text){
(user:PFUser!, error:NSError!)->Void in
if user != nil
{
println(user)
println("Login successfull")
//associate current user for parse remote push notifications
var installation:PFInstallation = PFInstallation.currentInstallation()
installation.addUniqueObject("goride", forKey: "channels")
installation["user"] = PFUser.currentUser()
installation.saveInBackground()
var user = PFUser.currentUser()
var query = PFQuery(className:"allData")
query.whereKey("user", equalTo: user)
// Retrieve the most recent one
query.orderByDescending("createdAt")
query.limit = 1;
//assign objectId
var alldata = PFObject(className:"allData")
println("objectId preFETCH")
println(alldata.objectId)
//refresh object
alldata.fetch()
let id = alldata.objectId
println("objectId postFETCH")
println(id)
query.getObjectInBackgroundWithId(id) {
(alldata: PFObject!, error: NSError!) -> Void in
if error == nil {
//Load parse data from cloud
NSLog("%#", alldata)
self.performSegueWithIdentifier("go-to-main-menu", sender: self)
} else {
NSLog("%#", error)
}
}
}
else { // Log details of the failure
NSLog("Error: %# %#", error, error.userInfo!)
}
}
}
else{
println(user)
let alert = UIAlertView()
alert.title = "Invalid Login"
alert.message = "Sorry, that username and/or password is incorrect. Please enter a valid combination."
alert.addButtonWithTitle("OK")
alert.show()
println("Login failed")
}
}
}
The println that's supposed to occur after the fetch never gets to the console so I'm assuming I have not successfully retrieved the objectId. I have also checked Parse's user forum and no help there.

Related

How to write data to firebase when offline? Swift3

In a tableView I have a list of jobs. These jobs can be accessed by multiple users, therefore I need to use FIRTransaction. Based on the result of the first write to FirebaseDatabase, I need to write/not write to another path in Firebase.
The schema is as follows:
//Claim job by - Cleaner
//cleaner tries to claim booking at Users/UID/cus/bookingNumber
//if FIRMutableData contains a key ‘claimed’ or key:value “claimed”:true
//update at Users/UID/cus/bookingNumber with key:value
//based on response received write or not to another path
//write to Cleaners/UID/bookingNumber
//key:value
If the internet connection drops before client app receives response from firebase server, write to Cleaners/UID/bookingNumber will not be made.
How can I solve this problem?
#IBAction func claimJob(_ sender: Any) {
dbRef.runTransactionBlock({ (_ currentData:FIRMutableData) -> FIRTransactionResult in
//if valueRetrieved is nil abort
guard let val = currentData.value as? [String : AnyObject] else {
return FIRTransactionResult.abort()
}
self.valueRetrieved = val
guard let uid = FIRAuth.auth()?.currentUser?.uid else {
print("abort no uid line 80")
return FIRTransactionResult.abort()
}
self.uid = uid
for key in self.valueRetrieved.keys {
//unwrap value of 'claimed' key
guard let keyValue = self.valueRetrieved["Claimed"] as? String else {
print("abort line 88")
return FIRTransactionResult.abort()
}
//check if key value is true
if keyValue == "true"{
//booking already assigned show alert,stop transaction
self.alertText = "Booking already taken, please refresh table!"
self.alertActionTitle = "OK"
self.segueIdentifier = "unwindfromClaimDetailToClaim"
self.showAlert()
return FIRTransactionResult.abort()
} else {
//write the new values to firebase
let newData = self.createDictionary()
currentData.value = newData
return FIRTransactionResult.success(withValue: currentData)
}//end of else
}//end of for key in self
return FIRTransactionResult.abort()
}) {(error, committed,snapshot) in
if let error = error {
//display an alert with the error, ask user to try again
self.alertText = "Booking could not be claimed, please try again."
self.alertActionTitle = "OK"
self.segueIdentifier = "unwindfromClaimDetailToClaim"
self.showAlert()
//what if internet connection drops here or client quits app ????????????
} else if committed == true {
//write to Cleaners/UID/bookingNumber
//what if internet connection drops here or client quits app??????
self.cleanersRef.setValue(snapshot?.value)
self.alertText = "Booking claimed.Please check your calendar"
self.alertActionTitle = "OK"
self.segueIdentifier = "unwindfromClaimDetailToClaim"
self.showAlert()
}
}
}//end of claimJob button

How to code a working iCloud iOS App?

With the help of a few tutorials I coded my first iCloud App. It is working well in Xcode simulator and on my iPhone and iPad. But as soon as I upload it for TestFlight testing it isn't working anymore.
Here is the whole code for getting and uploading the data. It is a simple one-ViewController Shopping list App which has two arrays: listItems for the current shopping list and shopItems for all items which are added so far. These arrays are stored as string lists in the iCloud recordZone All data are stored locally on the device and in the cloud.
The App is checking the connectivity, the iCloud availability and the fact, if the shopping list was edited while being offline, before it gets the data from iCloud.
// Init all values
var listItems = [String]()
var shopItems = [String]()
var cloudCheck = true
var onlineCheck = true
// Init the user defaults
let defaults = UserDefaults.standard
let privateDatabase = CKContainer.default().privateCloudDatabase
let recordZone = CKRecordZone(zoneName: "ShopListZone")
let predicate = NSPredicate(value: true)
var editedRecord: CKRecord!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let indexPath = tableView.indexPathForSelectedRow {
tableView.deselectRow(at: indexPath, animated: true)
}
if (reachability?.isReachableViaWiFi)! || (reachability?.isReachableViaWWAN)! {
if isICloudContainerAvailable() && defaults.bool(forKey: "changed") == false {
getCloudData()
}
else if isICloudContainerAvailable() && defaults.bool(forKey: "changed") {
loadOffline()
}
else {
cloudCheck = false
}
} else {
onlineCheck = false
loadOffline()
}
}
// Get the record from iCloud
func getCloudData() {
// Connect to iCloud and fetch the data
let query = CKQuery(recordType: "ShopListData", predicate: predicate)
let operation = CKQueryOperation(query: query)
var myItems = [String]()
var allItems = [String]()
operation.recordFetchedBlock = { record in
myItems = record["ListItems"] as! [String]
allItems = record["ShopItems"] as! [String]
}
operation.queryCompletionBlock = { [unowned self] (cursor, error) in
DispatchQueue.main.async {
if error == nil {
self.listItems = myItems
self.shopItems = allItems
self.tableView.reloadData()
} else {
self.cloudCheck = false
print("iCloud load error: \(String(describing: error?.localizedDescription))")
}
}
}
privateDatabase.add(operation)
cloudCheck = true
}
// Upload and save the record to iCloud
#IBAction func uploadShopListData(_ sender: UIButton) {
// Save the shop list in the user defaults
defaults.set(listItems, forKey: "myItems")
// Set bool if saving while offline
if (reachability?.isReachableViaWiFi)! == false && (reachability?.isReachableViaWWAN)! == false {
defaults.set(true, forKey: "changed")
}
// Save the record
if cloudCheck && onlineCheck {
defaults.set(false, forKey: "changed")
saveRecord()
// Show a short message if records were saved successfully
self.myAlertView(title: "iCloud online", message: NSLocalizedString("Shop list was saved in iCloud.", comment: ""))
} else {
// Show a short message if iCloud isn't available
myAlertView(title: "iCloud offline", message: NSLocalizedString("Shop list was saved on iPhone.", comment: ""))
defaults.set(true, forKey: "changed")
}
}
// Save the shop lists
func saveRecord() {
// Connect to iCloud and start operation
let query = CKQuery(recordType: "ShopListData", predicate: predicate)
privateDatabase.perform(query, inZoneWith: recordZone.zoneID) {
allRecords, error in
if error != nil {
// The query returned an error
OperationQueue.main.addOperation {
print("iCloud save error: \(String(describing: error?.localizedDescription))")
// If there is now record yet, create a new one
self.createRecord()
}
} else {
// The query returned the records
if (allRecords?.count)! > 0 {
let newLists = allRecords?.first
newLists?["ListItems"] = self.listItems as CKRecordValue
newLists?["ShopItems"] = self.shopItems as CKRecordValue
self.privateDatabase.save(newLists!, completionHandler: { returnRecord, error in
if error != nil {
// Print an error message
OperationQueue.main.addOperation {
print("iCloud save error: \(String(describing: error?.localizedDescription))")
}
} else {
// Print a success message
OperationQueue.main.addOperation {
print("Shop list was saved successfully")
}
}
})
}
}
}
}
// Create a new record
func createRecord() {
let myRecord = CKRecord(recordType: "ShopListData", zoneID: (self.recordZone.zoneID))
let operation = CKModifyRecordsOperation(recordsToSave: [myRecord], recordIDsToDelete: nil)
myRecord.setObject(self.listItems as CKRecordValue?, forKey: "ListItems")
myRecord.setObject(self.shopItems as CKRecordValue?, forKey: "ShopItems")
operation.modifyRecordsCompletionBlock = { records, recordIDs, error in
if let error = error {
print("iCloud create error: \(String(describing: error.localizedDescription))")
} else {
DispatchQueue.main.async {
print("Records are saved successfully")
}
self.editedRecord = myRecord
}
}
self.privateDatabase.add(operation)
// Show a short message if icloud save was successfull
self.myAlertView(title: "iCloud online", message: NSLocalizedString("Shop list was saved in iCloud.", comment: ""))
}
Any idea, what did I wrong? I read in another post that I should change the iCloud dashboard from development to production, but others say that this should be done only when the App is already on the way to the App store ..
When you upload your app to TestFlight, it's done using live configuration and app will try to connect to production container.
You have two options to test on device:
1) deploy your iCloud schema to production. As you have nothing released yet, it will not break anything.
2) after you archive your project, export it for 'Development deployment'. You will be asked what iCloud Container Environment should be used. You can select 'Development' and install app locally using Apple Configurator
It was not clear to me that setting iCloud Dashboard to production means, that the record type is used, but the record zone I created during development not.
So I have to create the custom record zone first in the createRecord() method (see above). That's it.

Swift - Parse - Check if username is taken

I am trying to create a function that takes a username as a parameter and checks to see if that username is taken (by comparing it to other PFUsers in the Parse database. This function is in my view controller class. (I know there are similar questions to this but they do not provide quality answers and are more general than this or are not in Swift).
func usernameIsTaken(username: String) -> Bool {
//bool to see if username is taken
var isTaken: Bool = false
//access PFUsers
var query : PFQuery = PFUser.query()!
query.whereKey("User", equalTo: username)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) in
if error == nil {
if (objects!.count > 0){
isTaken = true
println("username is taken")
} else {
println("Username is available. ")
}
} else {
println("error")
}
}
return isTaken
}
The problem is that the condition in the if statement is always false so "Username is available" always prints in the console even if the username is taken."Username is taken" is never printed even when the username is taken. What should I put in the nested if statement to check if the username matches another PFUser?
You are querying for User (class) key, but you need to query for a specific key, for example email.
// First get user's inputted email
let enteredEmailAddress = "sample#gmail.com"
// Then query and compare
var query = PFQuery(className: "_User")
query.whereKey("email", equalTo: enteredEmailAddress)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) in
if error == nil {
if (objects!.count > 0){
isTaken = true
println("username is taken")
} else {
println("Username is available. ")
}
} else {
println("error")
}
}
Just thought I would throw this out there, since this doesn't seem to be well known by people as I've answered a similar question before. Parse does this kind of checking for the user class automatically. If you're trying to create a new user with any of the default fields duplicated in Parse i.e username, email, etc, then Parse will not allow user signup. This is done automatically, with you having to do nothing, except for present the error so the user knows why they weren't able to sign up successfully. An example of signing a user up that checks for username email etc duplicates follows below:
user.signUpInBackgroundWithBlock {
(succeeded: Bool, signupError: NSError?)
-> Void in
if signupError == nil {
//present new controller
println("Signed up")
}
else {
if let errorString = signupError!.userInfo?["error"] as? NSString
{
error = errorString as String
}
else {
error = "We're sorry, an error ocured! Please try again."
}
self.displayAlert("Could not sign up", error: error)
}
}
}
Check the error code. Last time I did this, code 202 = Username Taken, code 203 = e-mail taken.
if signupError == nil {
print("User \(user.username!) signed up OK!")
} else if signupError?.code == 202 {
print("Username taken. Please select another")
} else if signupError?.code == 203 {
print("e-Mail taken. Please select another")
}

iOS swift parse + how to check if email already exist in database

I'm working on iOS Swift project with Parse. I need to allow users to update their email but it should be unique. Currently, my code looks like following:
var user = PFUser.currentUser()
var userName = user.username
var profQuery = PFQuery(className: "User")
profQuery.whereKey("email", equalTo: fnEditEmail)
profQuery.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil && objects!.count < 1 {
if let objects = objects as? [PFObject] {
for object in objects {
println(object.objectId)
object.setValue(self.fnEditEmail.text, forKey: "email")
object.setValue(self.fnEditAge.text, forKey: "age")
object.setValue(self.fnEditGender.text, forKey: "gender")
object.setValue(self.fnEditText.text, forKey: "fullname")
object.setValue(self.keyWord1.text, forKey: "key1")
object.setValue(self.keyWord2.text, forKey: "key2")
object.setValue(self.keyWord3.text, forKey: "key3")
object.setValue(self.keyWord4.text, forKey: "key4")
object.saveInBackgroundWithBlock {
(succeeded: Bool!, error: NSError!) -> Void in
if error == nil {
println "Profile Updated."
} else {
println "Failed"
}
}
}
} else if error == nil && objects!.count >= 1 {
println "email already exist."
} else if error != nil {
println "couldn't update, please try again."
}
}
}
I don't think this is correct code and it's not working either. Could somebody please guide me how can I fit this and also, if I can prevent two PFQuery and findObjectsInBackgroundWithBlock, which I think what is required here; One to check if that email exists in current database and one to update the row.
Parse automatically detects this if you try to set the email field of a PFUser. For instance, when signing a user up to your application, Parse will return an error that the email is already being used and won't allow singup. In fact, it even presents the alert for you I'm pretty sure.
In any other part of your app if the user is trying to update their email, Parse will work the same way, albeit without the error presentation which you would have to take care of.
Because of this, you don't have to do any queries or anything. You would simply try to update the email field of a PFUser object, and save it, and Parse will return the error for you if such an email address already exists.
Bottom line is Parse will never allow non-unique email addresses for any PFUser, so you don't have to write code to worry about this. Just worry about email address validation if that's something you want, and then worry about presenting an alert if Parse returns an error.
var user = PFUser.currentUser()
user.setValue(newEmail, forKey: "Email")
user.saveInBackgroundWithBlock {
(succeeded: Bool!, error: NSError!) -> Void in
if error == nil {
println "Profile Updated."
} else {
println "Failed"
//present alert to user to let them know that it failed
//ask them to try a new email address
}
}

Save current user's message list in parse with swift

I use parse for my app. I want to let user able to type messages that they want to send via textField and save it to that user's messages column in parse with PFRelation via save button in view controller and the messages will be saved as an array and show it in tableView.
The problem is I don't know how to add text in textfield to an array and save it to parse.
Any help is appreciated and let me know if you need any additional information!
UPDATE:
These are screenshots of my parse's class "User"
This is my current user's friend list inside "Friends" column
I've not yet create Messages column because when run relationForKey code in Xcode it will automatically create for me
UPDATE 2:
This is my code:
#IBAction func addMessage(sender: AnyObject) {
var newMessage = addMessageText.text
let message = PFObject(className: "Messages")
var query = PFQuery(className: "Messages")
message["messageTextColumn"] = newMessage
message.saveInBackgroundWithBlock {
(success: Bool, error: NSError?) -> Void in
if (success) {
println("added to Message Class")
query.whereKey("messageTextColumn", equalTo: newMessage)
query.getFirstObjectInBackgroundWithBlock{(object:PFObject!, error: NSError!) -> Void in
if error == nil {
let relation = PFUser.currentUser().relationForKey("Messages")
var addMessageWithObject = object
if addMessageWithObject != nil {
relation.addObject(addMessageWithObject)
println("Added with getFirstObject")
}
else{
println("Error Added with getFirstObject")
}
}
}
} else {
println("added to Message class Error")
}
}
}
I save new message to the array first and then I save it with saveInBackgroundWithBlock.. and inside I query that message to add it to relation.
The messages that I've added appear on Messages class table but not in that user's relation but it shows log
"added to Message Class" and "Added with getFirstObject"
Which means that my code execute exactly like it should be. Probably about the method?
UPDATE 3 this is the object println
<Messages: 0x7fd4484f75f0, objectId: LFXoSaHfQl, localId: (null)> {
ACL = "<PFACL: 0x7fd4484d2e70>";
messageTextColumn = 9;
}
UPDATE 4
this is my code
#IBAction func addMessage(sender: AnyObject) {
var newMessage = addMessageText.text
let message = PFObject(className: "Messages")
var user = PFUser.currentUser()
var query = PFQuery(className: "Messages")
message["messageTextColumn"] = newMessage
message.saveInBackgroundWithBlock {
(success: Bool, error: NSError?) -> Void in
if (success) {
println("added to Message Class")
query.getFirstObjectInBackgroundWithBlock{(object:PFObject!, error: NSError!) -> Void in
if error == nil {
var addMessageWithObject = object
if addMessageWithObject != nil {
user.saveInBackground()
println("Added with getFirstObject")
}
else{
println("Error Added with getFirstObject")
}
}
}
}
}
}
user column is (undefined) as in screenshot here
and the error log can't add non pointer to relation is back
how do I fix this? Thanks!
Here's what you do:
Manually create your Message table on Parse
Add a messages column to your user table of type Relation with Target Class as your Message table.
In your code, in your buttons trigger:
// Get the message text from your textField
let messageText = textField.text
// Create your new Message object
let newMessage = PFObject(className: "Message")
// ... Add your data to your new message object
newMessage["messageTextColumn"] = messageText
newMessage.saveInBackgroundWithBlock {
(success: Bool, error: NSError?) -> Void in
if (success) {
// Add the new message to the user's relation
let relation = yourUser.relationForKey("messagesColumnName")
relation.addObject(newMessage)
// Save the user object
yourUser.saveInBackground()
} else {
// There was a problem, check error.description
}
}
Here's a link to Parse's Relation reference.
UPDATE:
Missing code to save your user object.
query.getFirstObjectInBackgroundWithBlock{(object:PFObject!, error: NSError!) -> Void in
if error == nil {
let relation = PFUser.currentUser().relationForKey("Messages")
var addMessageWithObject = object
if addMessageWithObject != nil {
relation.addObject(addMessageWithObject)
PFUser.currentUser().saveInBackground()
println("Added with getFirstObject")
}
else{
println("Error Added with getFirstObject")
}
}
}
UPDATE 2:
Messages without PFRelation:
Add a column (let's say user) of type Pointer with Target Class as _User to the Messages table to identify each message by their user.
Saving new messages: Save the new message object like above (just without adding the relation and it'e related code):
#IBAction func addMessage(sender: AnyObject) {
var newMessage = addMessageText.text
let message = PFObject(className: "Messages")
message["messageTextColumn"] = newMessage
message["user"] = PFUser.currentUser()
message.saveInBackgroundWithBlock {(success: Bool, error: NSError?) -> Void in
if (success) {
println("added to Message Class")
} else {
// Error saving message
}
}
}
Querying the messages for a user: You can query using the current user as a constraint so no matter which device a particular switches to, he/she will get only his messages.
var query = PFQuery(className:"Messages")
query.whereKey("user", equalTo:PFUser.currentUser())
query.findObjectsInBackgroundWithBlock {
...
}

Resources