I'm having an issue saving my objects to the Parse object I created. I have Strings in a UITableview that the user uses a UISwitchto indicate they want to save the String. Once everything the user has selected what they want they push the save button on the UIToolbar just below the table. Once the save button is activated two checks are made: The first is to ensure the UISwitchhas been turned on the next is to check the contents of the NSDictionary that contains the strings. I have put in checks to ensure the for in loop is reading the values from the dictionary properly and it is. But the the only object being saved is the last object being ran through the for in loop. Not sure why. Here is my code:
#IBAction func questionSaved(sender: AnyObject){
for (key,value) in savedItems{
if key == cellNumber && value == true{
for (key,value) in physicalQuestions{
questionStore["physicalQuestion"] = value
questionStore.saveInBackgroundWithBlock{ (success, error) -> Void in
//Set Deleted Value to False when Succeeded
if (success){
self.questionStore["deleted"] = false
self.questionStore.saveInBackground()
}else{
self.questionStore["deleted"] = true
self.questionStore.saveInBackground()
}
}
}
}
}
}
Thanks for the help I really appreciate it.
Related
I have a controller, that allows the user to type in a TextField.
Every time the user types a character, the string in that textfield is compared to an array of strings. If there is a match, the resulting array is displayed in a uitableview.
Here's the code:
func searchAutocompleteEntriesWithSubstring(substring:String){
let SUBSSTRING = substring.uppercased()
autocompleteStrings.removeAll()
for thisSchool in schoolArray{
if(thisSchool.name?.uppercased() .contains(SUBSSTRING))!{
autocompleteStrings.append(thisSchool)
}
}
autocompleteTableView.reloadData()
}
Basically, this works fine. BUT!
If the user types rather fast, the autocompleteTableView displays one or more (empty) rows than there actually are strings in the autocompleteStrings array.
I tried encapsulating the above code in DispatchQueue.main.async {}, but that made things even worse.
I guess it has something to do with NeedsLayout or NeedsDisplay, but I've never really understood the mechanism behind it, and how/where to apply these.
I hope you can advise me
Try this code
func searchAutocompleteEntriesWithSubstring(substring: String) {
let filtered = schoolArray.filter() { ($0.name ?? "").uppercased().range(of: substring.uppercased()) != nil }
autocompleteStrings = filtered.map() { $0.name! }
autocompleteTableView.reloadData()
}
Maybe you need a lock?
1.in a async queue.
2.lock locks.
3.matching and array appending
4.lock unlocks.
5.reload in mainqueue
So, I am new to cloudKit and to working with multiple threads in general, which I think is the source of the problem here, so if I simply need to research more, please just comment so and I will take that to heart.
Here is my question:
I am working in Swift 3 Xcode 8.1
I have in my view controller this variable:
var contactsNearby: [String:CLLocation]?
Then at the end of ViewDidLoad I call one of my view controllers methods let's call it:
populateContactsNearby()
inside that method I call:
container.discoverAllIdentities(completionHandler: { (identities, error) in
for userIdentity in identities! {
self.container.publicCloudDatabase.fetch(withRecordID: userIdentity.userRecordID!, completionHandler: { (userRecord, error) in
let contactsLocation = userRecord?.object(forKey: "currentLocation")
if self.closeEnough(self.myLocation!, contactLocation: contactsLocation as! CLLocation) {
var contactsName = ""
contactsFirstName = userIdentity.nameComponents?.givenName
if contactsName != "" && contactsLocation != nil {
self.contactsNearby?["\(contactsName)"] = contactsLocation as? CLLocation
}
}
})
}
})
}
I apologize if I am missing or have an extra bracket somewhere. I have omitted some error checking code and so forth in order to get this down to bare-bones. So the goal of all that is to populate my contactsNearby Dictionary with data from CloudKit. A name as the key a location as the value. I want to use that data to populate a tableview. In the above code, the call to closeEnough is a call to another one of my view controllers methods to check if the contact from CloudKit has a location close enough to my user to be relevant to the apps purposes. Also myLocation is a variable that is populated before the segue. It holds the CLLocation of the app users current location.
The Problem:
The if statement:
if contactsName != "" && contactsLocation != nil { }
Appears to succeed. But my view controllers variable:
var contactsNearby: [String:CLLocation]?
Is never populated and I know there is data available in cloudKit.
If it's relevant here is some test code that I have in cellForRowAtIndexPath right now:
let contact = self.contactsNearby?.popFirst()
let name = contact?.key
if name != nil {
cell.textLabel?.text = name
}else {
cell.textLabel?.text = "nothing was there"
}
My rows alway populate with "nothing was there". I have seen answers where people have done CKQueries to update the UI, but in those answers, the user built the query themselves. That seems different from using a CloudKit function like discoverAllIdentities.
I have tried to be as specific as possible in asking this question. If this question could be improved please let me know. I think it's a question that could benefit the community.
Okay, I need to do some more testing, but I think I got it working. Thank you Paulw11 for your comment. It got me on the right track.
As it turns out there were 2 problems.
First, as pointed out I have an asynchronous call inside a for loop. As recommended, I used a dispatchGroup.
Inside the cloudKit call to discoverAllIdentities I declared a dispatchGroup, kind of like so:
var icloudDispatchGroup = DispatchGroup()
Then just inside the for loop that is going to make an async call, I enter the dispatchGroup:
icloudDispatchGroup.enter()
Then just before the end of the publicCloudDatabase.fetch completion handler I call:
icloudDispatchGroup.leave()
and
icloudDispatchGroup.wait()
Which, I believe, I'm still new to this remember, ends the dispatchGroup and causes the current thread to wait until that dispatchGroup finishes before allowing the current thread to continue.
The Above took care of the multithreading issue, but my contactsNearby[String:CLLocation]? Dictionary was still not being populated.
Which leads me to the 2nd problem
At the top of my view controller I declared my Dictionary:
var contactsNearby: [String: CLLocation]?
This declared a dictionary, but does not initialize it, which I don't think I fully realized, so when I attempted to populate it:
self.contactsNearby?["\(contactsName)"] = contactsLocation as? CLLocation
It quietly failed because it is optional and returned nil
So, in viewDidLoad before I even call populateContactsNearby I initialize the dictionary:
contactsNearby = [String:CLLocation]()
This does not make it cease to be an optional, which Swift being strongly typed would not allow, but merely initializes contactsNearby as an optional empty Dictionary.
At least, that is my understanding of what is going on. If anyone has a more elegant solution, I am always trying to improve.
In case you are wondering how I then update the UI, I do so with a property observer on the contactsNearby Dictionary. So the declaration of the dictionary at the top of the view controller looks like this:
var contactsNearby: [String: CLLocation]? {
didSet {
if (contactsNearby?.isEmpty)! || contactsNearby == nil {
return
}else{
DispatchQueue.main.sync {
self.nearbyTableView.reloadData()
}
}
}
}
I suppose I didn't really need to check for empty and nil. So then in cellForRowAtIndexPath I have something kind of like so:
let cell = tableview.dequeueReusableCell(withIdentifier: "nearbyCell", for: indexPath)
if contactsNearby?.isEmpty == false {
let contact = contactsNearby?.popFirst()
cell.textLabel?.text = contact?.key
}else {
cell.textLabel?.text = "Some Placeholder Text Here"
}
return cell
If anyone sees an error in my thinking or sees any of this heading for disaster, feel free to let me know. I still have a lot of testing to do, but I wanted to get back here and let you know what I have found.
I've creating some voting buttons in my Swift app, where a user can vote for a film they like. My app is also integrated with Parse.
When the user presses the vote up button in my app, it increments a count and adds to my Parse database. I have now added some code that also add the film information to what they have just voted for. To do this I have created a new Class in Parse called Votes. My code is as follows:
#IBAction func upVoteButton(sender: UIButton) {
let hitPoint = sender.convertPoint(CGPointZero, toView: self.tableView)
let hitIndex = self.tableView.indexPathForRowAtPoint(hitPoint)
let object = objectAtIndexPath(hitIndex)
if PFUser.currentUser() != nil {
let votes = PFObject(className: "Votes")
votes["userUpVoting"] = PFUser.currentUser()?.objectId
votes["filmVotedUp_test"] = object!.objectId
votes.saveInBackground()
}
}
This code works, and saves the objectId for the film the user has voted for, but it only adds it as a String. Ideally, I'd like this set up to be a pointer in the database.
How can I do this from my code?
Or, is there no point in it being set up in this way? Thanks
First, you need t set up a pointer object in the class Votes that points to _User (User Class). In your code, instead of setting PFUser.currentUser()?.objectId to userUpVoting, just set PFUser.currentUser() to userUpVoting. When you need to set a pointer, you need to set the whole object, not just the object id. Another thing I noticed in your code: make sure that, before the app goes into production, you change votes.saveInBackground to votes.saveInBackgroundWithBlock and handle the errors.
For example, you would use:
classInfo.saveInBackgroundWithBlock({ (success: Bool, error: NSError?) in
if success == true {
// Object(s) was/were successfully saved
} else if error != nil {
// Display an alert to the user
// Use error!.localizedDescription as the message - it will give the user the specific error that occured
} else {
// Display an alert to the user - something went wrong
}
})
The code is from a book. In terms of overall app architecture (MVC), it's part of the Model. The model has two main components:
An array of tags called tags
A dictionary of tag - query called searches
The app saves these pieces of data in the NSUserDefaults (iOS defaults system) and on iCloud. The following method is called when a change in iCloud is signaled. The parameter is an instance of NSNotification.userInfo
// add, update, or delete searches based on iCloud changes
func performUpdates(userInfo: [NSObject: AnyObject?]) {
// get changed keys NSArray; convert to [String]
let changedKeysObject = userInfo[NSUbiquitousKeyValueStoreChangedKeysKey]
let changedKeys = changedKeysObject as! [String]
// get NSUbiquitousKeyValueStore for updating
let keyValueStore = NSUbiquitousKeyValueStore.defaultStore()
// update searches based on iCloud changes
for key in changedKeys {
if let query = keyValueStore.stringForKey(key) {
saveQuery(query, forTag: key, saveToCloud: false)
} else {
searches.removeValueForKey(key)
tags = tags.filter{$0 != key}
updateUserDefaults(updateTags: true, updateSearches: true)
}
delegate.modelDataChanged() // update the view
}
}
My question is on the if - else inside the for loop. The for loop iterates over keys that where changed; either the user adds a new search, updates an existing search, or deletes a search. But, I don't understand the logic behind the if-else. Some clarifying thoughts would be appreciated. I've read it over and over but it doesn't tick with me.
if let query = keyValueStore.stringForKey(key)
means that if keyValueStore contains a string corresponding to key, then this string will be assigned to the constant query.
This is called "safe unwrapping":
inside the if let ... condition, the query is safely saved with saveQuery because using if let ... guarantees that the value of keyValueStore.stringForKey(key) won't be nil.
If the value is nil, then in the else branch, the filter method is used to update the tags array without the key we just processed: tags.filter{$0 != key} means "return all items in tags that are different from key" (the $0 represents the current item from the array processed by filter).
I have a custom UICell that contains a UISwitch element. When I render my table, cellForRowAtIndexPath() will check to see if the users preference matches a specific category, if it does they are subscribed, else they are not. I have written some logic to toggle the switch based on this result:
if let userPreferences = user["preferences"] as? Array<AnyObject>
{
if userPreferences[indexPath.row].objectId == game.getGameId() {
prefCell.subscribed?.setOn(false, animated: true)
}
}
I have breakpointed the line where the switch is disabled, and it breaks 9 times out of the current 11 items I retrieve in the userPreferences collection, however none of the switches on my UI are updated. Why is this happening?
EDIT: After some further testing it appears that the if statement is always satisfied, this is extremely strange as in the event that the strings don't match, Swift still claims that they did.
For example if I have a list of the ID's
xyz12345
abc12345
efg12345
And the following loop executed:
if let userPreferences = user["preferences"] as? Array<AnyObject>
{
for preference in userPreferences
{
if preference.objectId == game.getGameId()
{
prefCell.subscribed.on = true;
} else {
prefCell.subscribed.on = false;
}
}
}
The first comparison could be something like preference = xyz12345 and game.getGameId() = abc12345. I'd expect the second case to be invoked as these strings certainly do not match, however the case passes as true and the switch is set, why am I witnessing this behaviour?