I'm building a sort of hot or not style app in Swift where the user can vote: HOT, NOT and MAYBE on an image, respectively.
For every time the user gets to a image, they vote by tapping the IBAction, which triggers a query that shows the result of the total votes and total hots from Parse as shown in my code below.
I plan to have 1,000 images.
Can I preload some of the objectIDs that correspond to each respective image, and then when the user votes on the image, the data is already preloaded/queried from parse? How would I go about that? Someone recommended in my previous question to use a NOT IN query in Parse. How do I do a NOT IN query in Parse and how would I go about doing that?
For now, I'm writing a query for each ObjectID which would take 1000 queries from 1000 different images... Obviously unscalable.
Further code explanations:
The swipePosition variable is just a counter that counts which image the user is on. The images being stored are in an Array for now stored on Xcode. Maybe they can be preloaded as well if they are stored on Parse?
(I am only showing the "hotButtonQuery" function, but there is also a Not and Maybe buttonQuery function.)
Is there a way to simply this code so that it's scalable? Because, as of now, there's no way I can scale past 25 images.
func hotButtonQuery() {
if swipePosition == 0 {
var query = PFQuery(className:"UserData")
query.getObjectInBackgroundWithId("RlvK3GhfqE") {
(userData: PFObject!, error: NSError!) -> Void in
if error != nil {
println(error)
}
else {
userData.incrementKey("totalVotes", byAmount: 1)
userData.incrementKey("hot", byAmount: 1)
var updateTotalVotesUILabel = userData.objectForKey("totalVotes") as NSInteger
var updateHotsUILabel = userData.objectForKey("hot") as NSInteger
userData.saveInBackground()
println("parse was updated!")
self.totalVotesLabel.text = String(updateTotalVotesUILabel)
self.totalHotsLabel.text = String(updateHotsUILabel)
}
}
} else if swipePosition == 1 {
var query = PFQuery(className:"UserData")
query.getObjectInBackgroundWithId("30WlVtgurP") {
(userData: PFObject!, error: NSError!) -> Void in
if error != nil {
println(error)
}
else {
userData.incrementKey("totalVotes", byAmount: 1)
userData.incrementKey("hot", byAmount: 1)
var updateTotalVotesUILabel = userData.objectForKey("totalVotes") as NSInteger
var updateHotsUILabel = userData.objectForKey("hot") as NSInteger
//println(userData.objectForKey("totalVotes"))
//println("total HOTs:")
//println(userData.objectForKey("hot"))
userData.saveInBackground()
println("parse was updated!")
self.totalVotesLabel.text = String(updateTotalVotesUILabel)
self.totalHotsLabel.text = String(updateHotsUILabel)
}
}
} else if swipePosition == 3 {
var query = PFQuery(className:"UserData")
query.getObjectInBackgroundWithId("5D6ARjk3xS") {
(userData: PFObject!, error: NSError!) -> Void in
if error != nil {
println(error)
}
else {
userData.incrementKey("totalVotes", byAmount: 1)
userData.incrementKey("hot", byAmount: 1)
var updateTotalVotesUILabel = userData.objectForKey("totalVotes") as NSInteger
var updateHotsUILabel = userData.objectForKey("hot") as NSInteger
//println(userData.objectForKey("totalVotes"))
//println("total HOTs:")
//println(userData.objectForKey("hot"))
userData.saveInBackground()
println("parse was updated!")
self.totalVotesLabel.text = String(updateTotalVotesUILabel)
self.totalHotsLabel.text = String(updateHotsUILabel)
}
}
}
just use
query.whereKey("key", doesNotMatchKey: "matchcheck", inQuery: innerQuery)
An example of Not In:
var query = PFUser.query()
if (friendsFilter){
var friendsRelation:PFRelation = PFUser.currentUser().relationForKey("friendsRelation")
query = friendsRelation.query()
}
else{
var friendsRelation:PFRelation = PFUser.currentUser().relationForKey("friendsRelation")
var innerQuery = friendsRelation.query()
query = PFUser.query()
query.whereKey("username", doesNotMatchKey: "username", inQuery: innerQuery)
Related
I have following code to handle pull to refresh response
let copyData = data.reversed() // it is pull to refresh response (load page 1)
for (_,element) in copyData.enumerated() {
let foundElement = allObjects.filter{$0.id == element.id} // Find element in main array
if let firstElement = foundElement.first, let index = allObjects.index(of: firstElement) {
allObjects[index] = element // Replace if found
} else {
allObjects.insert(element, at: 0) // Insert if not found
}
}
self.arrayPosts = allObjects
Where data is codable class which is API response of pull to refresh. allObjects is preloaded data with pagination
Question : Suppose In allObjects i have 50 Object (5 Pages of 10 ID is (1 to 50))
User pull to refresh And I load first Page from API (ID 1,2,3,4,5,6,7,10,11) then how to identify which object is deleted (8,9) ?
Should I compare allObjects 's 10th index with data's 10th index object's ID ?
Is it better way to handle this ? Please suggest
Don't compare pages (i.e. 10 items at a time) - if an item is added/deleted the pages will get out of sync, and you'll end up with missing / duplicate objects.
Presumably your objects are sorted by some key / date, etc.
Take the value of the key in your last downloaded object.
Copy all the existing objects with keys <= that last key into a new array.
Compare your downloaded array against this sub-array.
Objects in the downloaded array that are not in the sub-array should be removed.
Here How I handle this. Code is quite complex for first time read but added comments to understand it
func handleResponse(page:Int,isForRefersh:Bool = false, data:Array<InspirePost>) {
guard data.count != 0 else {
// Check if we are requesting data from pull to referesh and First page is empty then we don't have data to show change state to empty
if self.arrayPosts.count == 0 || (isForRefersh && page == 1) {
self.state = .empty
self.tableView.reloadData()
} else {
self.state = .populated(posts: self.arrayPosts)
}
return
}
// Now we need to check if data called by referesh control then
//1) Replace object in array other wise just append it.
var allObjects = self.state.currentPost
if isForRefersh {
// If both array Has same number of element i.e both has page one loaded
if data.count >= allObjects.count {
allObjects = data
} else {
let copyData = data.reversed()
for (_,element) in copyData.enumerated() {
if let index = allObjects.firstIndex(where: {$0.id == element.id}) {
allObjects[index] = element // Replace if found
} else {
allObjects.insert(element, at: 0) // Insert if not
}
}
let minID = data.min(by: {$0.id ?? 0 < $1.id ?? 0})?.id
// DELETE item
let copyAllObject = allObjects
for (_,element) in copyAllObject.enumerated() {
guard let id = element.id, id >= minID ?? 0 else {
continue
}
if !data.contains(element) {
if let indexInMainArray = allObjects.index(where: {$0.id == id}) {
allObjects.remove(at: indexInMainArray)
}
}
}
}
//When we pull to refersh check the curent state
switch self.state {
case .empty,.populated : // if empty or populated set it as populated (empty if no record was avaiable then pull to refersh )
self.state = .populated(posts: allObjects)
case .error(_, let lastState) : // If there was error before pull to referesh handle this
switch lastState {
case .empty ,.populated: // Before the error tableview was empty or popluated with data
self.state = .populated(posts: allObjects)
case .loading,.error: // Before error there was loading data (There might more pages if it was in loading so we doing paging state ) or error
self.state = .paging(posts: allObjects, nextPage: page + 1)
case .paging(_,let nextPage): // Before error there was paging then we again change it to paging
self.state = .paging(posts: allObjects, nextPage: nextPage)
}
case .loading: // Current state was loading (this might not been true but for safety we are adding this)
self.state = .paging(posts: allObjects, nextPage: page + 1)
case .paging(_,let nextPage): // if there was paging on going don't break anything
self.state = .paging(posts: allObjects, nextPage: nextPage)
}
self.arrayPosts = allObjects
} else {
allObjects.append(contentsOf: data)
self.isMoreDataAvailable = data.count >= self.pageLimit
if self.isMoreDataAvailable {
self.state = .paging(posts: allObjects, nextPage: page + 1)
} else {
self.state = .populated(posts: allObjects)
}
self.arrayPosts = self.state.currentPost
}
self.tableView.reloadData()
}
Where I have
indirect enum PostListStatus {
case loading
case paging(posts:[InspirePost],nextPage:Int)
case populated(posts:[InspirePost])
case error (error:String,lastState:PostListStatus) // keep last state for future if we need to know about data or state
case empty
var currentPost:[InspirePost] {
switch self {
case .paging(let posts , _):
return posts
case .populated( let posts):
return posts
case .error( _, let oldPost) :
switch oldPost {
case .paging(let posts , _):
return posts
case .populated( let posts):
return posts
default:
return []
}
default:
return []
}
}
var nextPage : Int {
switch self {
case .paging(_, let page):
return page
default:
return 1
}
}
}
You may make set for both allObjects and data. Then use subtracting(_:) method in set to find the missing one.
Remove those missing ones from the main array and use it. Once you have correct main array elements, page them while displaying.
I'm trying to persist my channel list locally, so I need to find out a way to query only channels that has been updated after my last "activity".
Group Channel has lastMessage property, which indicates when last message (if it exists) was received. How can I fetch new channels that was created or updated after most recent last massage?
Currently, I load my channels with the snippet below:
self.channelsListQuery = [SBDGroupChannel createMyGroupChannelListQuery];
[self.channelsListQuery setIncludeEmptyChannel: NO];
[self.channelsListQuery setOrder: (SBDGroupChannelListOrderLatestLastMessage)];
[self.channelsListQuery loadNextPageWithCompletionHandler: ^(NSArray<SBDGroupChannel*> *_Nullable channels, SBDError* _Nullable error) {
// My stuff...
}];
You can do this by using getNextMessagesByTimestamp:limit:reverse:messageType:customType:completionHandler: which takes timestamp as a param.
[self.channel getNextMessagesByTimestamp:yourLatestStoredTs limit:30 reverse:NO completionHandler:^(NSArray<SBDBaseMessage *> * _Nullable messages, SBDError * _Nullable error) {
if (error != nil) {
// Error!
return;
}
}];
I had the same requirement to sort Group channels by latest activity and used the Sort() method on the channel array to solve this problem.
Once the channels array of [SBDGroupChannel] is populated and right before the tableview is reloaded, sort the array.
This sorted my group channels by the latest first.
self.groupChannelListQuery = SBDGroupChannel.createMyGroupChannelListQuery()
self.groupChannelListQuery?.limit = 20
self.groupChannelListQuery?.order =
SBDGroupChannelListOrder.latestLastMessage
self.groupChannelListQuery?.loadNextPage(completionHandler: { (channels, error) in
if error != nil {
DispatchQueue.main.async {
self.refreshControl?.endRefreshing()
}
return
}
self.channels.removeAll()
self.cachedChannels = false
for channel in channels! {
self.channels.append(channel)
}
DispatchQueue.main.async {
if self.channels.count == 0 {
self.tableView.separatorStyle =
UITableViewCellSeparatorStyle.none
self.noChannelLabel.isHidden = false
}
else {
self.tableView.separatorStyle =
UITableViewCellSeparatorStyle.singleLine
self.noChannelLabel.isHidden = true
}
**self.channels.sort(by: { (channelA, channelB) -> Bool in
return channelA.createdAt > channelB.createdAt
})**
self.refreshControl?.endRefreshing()
self.tableView.reloadData()
}
})
I am trying to get the title and artist from a certain user using cloud code. I have a pointer to the user class in the Pointer class. I am trying to get all the title and artists (strings) matched to the current user. For some reason the cloud code is not executing, I am not getting anything in the iOS log. I think the cloud code is working, but I feel I am missing something to send the data back to the iOS app.
iOS code
PFCloud.callFunctionInBackground("search", withParameters: ["user": currentUserID!]) {
(response: AnyObject?, error: NSError?) -> Void in
if(error == nil){
if let objects = response as? [PFObject]{
for object in objects{
print(object)
}
}
}else{
print(error)
}
}
Cloud code
Parse.Cloud.define("search", function(request, response){
var review = Parse.Object.extend("Pointer")
var query = new Parse.Query(review)
var userPointer = {
__type: 'Pointer',
className: '_User', // name of class
objectId: request.params.user
}
console.log(userPointer);
query.equalTo("user", userPointer);
console.log(query.equalTo("user", userPointer));
query.find().then(function(results){
var sum = 0;
for(var i = 0; i < results.length; i++){
sum += results[i].get("title");
sum += results[i].get("artist")
}
response.success(sum / results.length);
}, function(error){
response.error(error)
});
});
I've written a Parse cloud code function which returns some data from the database. I see those in the "response" when I do a println in XCode. It looks like it's wrapped in a double optional!?
What I'm making wrong in the if let and in the for loop? How do I get (unwrap) a String Array out of it?
My code in Swift:
PFCloud.callFunctionInBackground("TopTwo", withParameters: ["rating":5]) {
(response: AnyObject?, error: NSError?) -> Void in
if error == nil {
println("Successfully retrieved \(response!.count) scores.")
println("Here are the flower names: \(response)")
if let objects = response as? [PFObject] {
for object in objects {
println(object.objectId)
}
}
} else {
println("Error: \(error!) \(error!.userInfo!)")
}
}
What I see in the console:
Successfully retrieved 2 scores.
Here are the flower names: Optional((
rose,
"sunflower"
))
Maybe there is also an error in my cloude code. Here you can see what I've done:
Parse.Cloud.define("TopTwo", function(request, response) {
var query = new Parse.Query("Flowers");
console.error("Get flowers with the rating: " + request.params.rating);
query.equalTo("stars", request.params.rating);
query.find({
success: function(results) {
console.error("Results: " + results);
var list = [];
for (i = 0; i < results.length; i++) {
list[i] = results[i].get('flowerName');
}
console.error("Flower name list: " + list);
response.success(list);
},
error: function() {
response.error("lookup failed");
}
});
});
And here the parse logs:
Results: [object Object],[object Object]
Flower name list: rose,sunflower
(I'm using XCode 6.3.2 - Swift 1.2)
Many thanks in advance!
Okay, I could solve it on my own.
The object which is returned from cloud code is already an Array. Therefore a casting into NSArray has to be made instead of a casting into [PFObject].
Here is the working Swift code:
PFCloud.callFunctionInBackground("dayTopFive", withParameters: ["day":1]) {
(response: AnyObject?, error: NSError?) -> Void in
if error == nil {
println("Successfully retrieved \(response!.count) scores.")
// This is working:
let objects = response as! NSArray
for object in objects {
println("A top flower is: \(object)")
}
}
}
I would really appreciate your help with the following. I have been battling this small nuisance for a while now but without luck. I have this bit of code thats basically simulates a AI playing TIC TAC TOE against a player.
let Result = RowCheck(value: 0)
if Result != nil {
println("Computer has two in a row")
var WhereToPlayResult = WhereToPlay(Result.location, pattern: Result.pattern)
if !IsOccupied(WhereToPlayResult) {
SetImageForSpot(WhereToPlayResult, player: 0)
aiDeciding = false
CheckForWin()
return
}
return
}
RowCheck just checks for a pattern to play against.
func RowCheck(#ā€ˇvalue:Int) -> (location:String,pattern:String)? {
var AcceptableFinds = ["011","110","101"]
var FindFuncs = [CheckTop,CheckBottom,CheckLeft,CheckRight,CheckMiddleAcross,CheckMiddleDown,CheckDiagionalRightLeft,CheckDiagionalLeftRight]
for Algorthm in FindFuncs {
var AlgorthmResults = Algorthm(value:value)
if (find(AcceptableFinds,AlgorthmResults.pattern) != nil) {
return AlgorthmResults
}
}
return nil
}
But it gives me an error at:
var WhereToPlayResult = WhereToPlay(Result.location, pattern: Result.pattern)
Because your RowCheck method returns an optional (and might return nil), you need to either unwrap your optional or use a different assignment:
let Result = RowCheck(value: 0)
if Result != nil {
var WhereToPlayResult = WhereToPlay(Result!.location, pattern: Result!.pattern)
// ... ^ ^
}
if let Result = RowCheck(value: 0) {
// ...
}
Side note: only classes should be named starting with a capital letter. To stay within Apple's code style, you should variables and functions as result, rowCheck, etc.