Query Parse to preload all objectID data in Swift - swift

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 and then for each respective vote the IBAction 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 all 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?
For now, I'm writing a query for each ObjectID which would take 1000 queries from 1000 different images... Obviously unscalable.
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...
Thanks a lot!
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)
}
}
}

Respectfully, I would suggest rethinking your whole approach. Don't store objectIds in your code. Don't repeat essentially the same block of code over and over. Keep it simple:
Retrieve and display image
Capture vote and save object
Query for next object and repeat process
If you want to reduce the number of queries, grab them in batches of 10 or so. No sense loading everything since the user is not likely to click through all 1000.
The only thing left to figure out is how you will be keeping track of which images the user has already voted on. You could do this client-side or within Parse. There are a few decent approaches.

Related

Moving Table rows in SwiftUI

Is it possible to support moving rows in a SwiftUI Table view?
I know there's List which can optionally support selection and drag-and-drop to move single or multiple rows. Since it seems similar, I would like to do this with a Table too, but I wasn't able to find any way to do this. Is this possible in SwiftUI? And if it is, what's the best way to do it?
Where I started to figure this out was the WWDC 2021 session "SwiftUI on the Mac: Finishing Touches". I highly recommend this video, as well as the preceding one "SwiftUI on the Mac: Build the Fundamentals". The code for both sessions is available.
Since you didn't include your code to show what you want to do, I have to use my code. I have a table based on an array of an Identifiable struct called Channel. Among a number of fields which are irrelevant to this problem, there is a field "id" of type UUID.
Following the model of the WWDC video, I made an extension to Channel:
import UniformTypeIdentifiers
extension Channel {
static var draggableType = UTType(exportedAs: "com.yourCompany.yourApp.channel")
// define your own type here. don't forget to include it in your info.plist as an exported type
static func fromItemProviders(_ itemProviders: [NSItemProvider], completion: #escaping ([Channel]) -> Void) {
let typeIdentifier = Self.draggableType.identifier
let filteredProviders = itemProviders.filter {
$0.hasItemConformingToTypeIdentifier(typeIdentifier)
}
let group = DispatchGroup()
var result = [Int: Channel]()
for (index, provider) in filteredProviders.enumerated() {
group.enter()
provider.loadDataRepresentation(forTypeIdentifier: typeIdentifier) { (data, error) in
defer { group.leave() }
guard let data = data else { return }
let decoder = JSONDecoder()
guard let channel = try? decoder.decode(Channel.self, from: data)
else { return }
result[index] = channel
}
}
group.notify(queue: .global(qos: .userInitiated)) {
let channels = result.keys.sorted().compactMap { result[$0] }
DispatchQueue.main.async {
completion(channels)
}
}
}
var itemProvider: NSItemProvider {
let provider = NSItemProvider()
provider.registerDataRepresentation(forTypeIdentifier: Self.draggableType.identifier, visibility: .all) {
let encoder = JSONEncoder()
do {
let data = try encoder.encode(self)
$0(data, nil)
} catch {
$0(nil, error)
}
return nil
}
return provider
}
}
This makes an item in the table draggable. Of course, that does no good if there's nothing that will accept the drag. So, you have to make a change to your Table.
Table(selection: $selection, sortOrder: $sortOrder) {
// for clarity, I've removed the table columns
} rows: {
ForEach(document.channels) { channel in
TableRow(channel)
.itemProvider { channel.itemProvider }
}
.onInsert(of: [Channel.draggableType]) { index, providers in
Channel.fromItemProviders(providers) { channels in
document.channels.insert(contentsOf: channels, at: newIndex)
}
}
}
}
Now that will enable you to drag item or items from one window to another. You can, of course, drag within a table now, too. Unfortunately, you will end up making a copy in the new place. Not what you want to do in most cases. How to fix this? Delete the original copy! Of course, you can also run into the problem of indexing in the right place, and if you drag more than one item (from a discontinuous selection, even worse!), the results become, shall we say, undefined.
I still wanted to be able to drag multiple items from another table, so the final onInsert becomes a little more complex (Which I'm sure could be cleaned up a bot further):
Channel.fromItemProviders(providers) { channels in
var newIndex = index
let intraTableDrag = document.channels.contains(where: {$0.id == channels[0].id})
if intraTableDrag && channels.count == 1 {
let oldIndex = document.channels.firstIndex(where: {$0.id == channels[0].id})
if newIndex > oldIndex! {
newIndex -= 1
}
for channel in channels {
let channelID = channel.id
removeChannel(withID: channelID)
}
let maxIndex = document.channels.count
if index > maxIndex {
newIndex = maxIndex
}
}
if (intraTableDrag && channels.count == 1) || !intraTableDrag {
document.channels.insert(contentsOf: channels, at: newIndex)
document.setChannelLocationToArrayOrder()
}
}
}
I hope this is enough to get you started. Good luck!

How to fetch objects from results one by one without do .count

I need to fetch from Realm Results 20 objects or less. A database can be heavy, so Results.count is a long time for calling.
So, what I need is to fetch objects from Results one by one until I get 20 or until last object.
But, when I'm trying to fetch index after the last object it's throwing Realm exception 'Index x is out of bounds (must be less than x)'.
So, this one isn't working:
let searchResult = Ticket().get(filter: "base == nil && deleted == 0 AND orderPaidAt > 0 AND (\(query))").sorted(byKeyPath: "orderPaidAt")
for i in 0..<20 {
if let ticket = searchResult[i] as? Ticket {
...
} else {
break
}
}
If I'm trying to use searchResult.count or searchResult.endIndex it increases a time a lot, especially on old devices. That's why I want to avoid it.
The results are lazily loaded, so you could loop through the results one by one, until the end, or until you hit a self-set count:
let searchResult = Ticket().get(filter: "base == nil && deleted == 0 AND orderPaidAt > 0 AND (\(query))").sorted(byKeyPath: "orderPaidAt")
var count = 0
for thisTicket in searchResult {
// do something
count += 1
if count > 20 { break }
}
This way you are only loading the values that you need, and never calling count or accessing the results out of bounds.
You can use prefix(maxLenght: Int) method to get a subCollection with specified maxLenght.
Example:
realm.objects(ObjectModel.self).prefix(20).count

Dictionary in Dictionary value search

I am downloading information from a Firebase database and it is being inputted via a for loop into:
static var Reports = [String:[String:String]]()
I need to figure out a way to search the inside values for a certain string
I have messed around with this but can't seem to get it inside the inside dictionary (If that makes sense)
for values in Reports.count {
if let item = Reports["favorite drink"] {
print(item)
}
}
I need to have a search string then a number of times the value appears like so:
func findString(dict Dictionary) -> Int {
var ReportsLevel1 = 0
(for loop I'm guessing)
search here for string
return ReportsLevel1
}
Tip: the outside dictionary keys are not set in stone, they depend on what time and date the report was submitted
To find out the numberOfTimes in which "yourSearchString" appears you can do as follows
var numberOfTimes = 0
for internalDictionary in reports.values
{
for value in internalDictionary.values
{
if (value == "yourSearchString") { numberOfTimes += 1 }
}
}
or
let numberOfTimes = reports.flatMap { internalDictsArray in internalDictsArray.value.filter { $0.value == "yourSearchString" } }.count

Performing functions with valueForKey

I am trying to get my app to perform functions. I have two attributes per item (quantity and price) that I want to multiply together and then total for all the didSelectRow items on the list. There is two sections on my tableView. Section 0 is regular and moved to section 1 with didSelectRow. (I only explain this because it comes into play further down)
My code so far is...
`func cartTotalFunc() {
itemFetchRequest().returnsObjectsAsFaults = false
do {
let results = try moc.executeFetchRequest(itemFetchRequest())
print("===\(results)")
// Calculate the grand total.
var grandTotal = 0
for order in results {
let SLP = order.valueForKey("slprice") as! Int
let SLQ = order.valueForKey("slqty") as! Int
grandTotal += SLP * SLQ
}
print("\(grandTotal)")
cartTotal.text = "$\(grandTotal)" as String
} catch let error as NSError {
print(error)
}
}
`
slprice and slqty are strings in Core Data. I am trying to cast them as Int so they will do the arithmetic. I had this working but it totaled every item instead of only the crossed off ones (section 1). I gave it a rest for a while and now when I come back to try to work on it again Xcode is giving me an error of, "can not Could not cast value of type 'NSTaggedPointerString' (0x104592ae8) to 'NSNumber' (0x1051642a0)."
Can anyone help with this, please?

swift parse query limit

Got a question about parse query.limit with swift
using below settings and limit to pull objects from parse
func getUsernames()
query.limit = self.limit
query.findObjectsInBackgroundWithBlock //display on TableView
self.usernamesArray.append(someuser)
self.tableView.reloadData()
}
then in tableView:willDisplayCell
if indexPath.row == self.usernamesArray.count - 1 {
self.limit += 5
self.getUsernames()
}
and in viewDidAppear
if self.usernames.count == 0 {
self.limit = self.limit + 10
self.getUsernames()
}
this works fine. whenever my table scrolls through the second last Cell another 5 is ready which is what i expected yay!.
problem is if usernamesArray.count has total value of 50 and when the last cell(50th cell/count) has reached/scrolled the tableView:willDisplayCell is keep getting called and the self.limit is keep increasing 55, 60, 65 etc .... it doesn't stop when it reaches the LAST Cell or Last data in array. it keeps using the LTE data and query.limit number increases 5 by 5 (when there isn't anymore array value available)
am i doing this right? or should i try different approach?
any master of swift will be appreciated! Thanks
No, you aren't doing it right. First, by just increasing the limit and querying you're always getting another copy of the data you already have and maybe a few additional items. You should be changing the offset (skip) for each new query, not the limit.
You also need to check the response to see how many items were returned. If the number of items returned is less than the request limit then you know you're at the end of the data and you shouldn't make any more requests. Set a flag so that when scrolling you know there isn't anything else you can load.
I found it easier to create a resultSet class that handles all this information.
class ParseResultSet: NSObject {
var limit = 20
var skip = 0
var total = 0
var limitReached = false
var orderByAscendingKey: String?
var orderByDescendingKey: String?
var searchActive: Bool?
func increaseSkipByLimit() {
skip += limit
}
func increaseTotal(byNumber: Int) {
total += byNumber
if byNumber == 0 {
self.limitHasBeenReached()
skip = total
}
else {
self.increaseSkipByLimit()
}
}
func limitHasBeenReached() {
limitReached = true
}
}
I then use a method where I get the objects from Parse in a completion block. I check if the limit has been reached and increment the total if it hasn't
func getObjects(classname: String, include: [String], completion: #escaping (_ result: [PFObject]) -> Void) {
if self.resultSet.limitReached == false || self.resultSet.searchActive == true {
fetchObjectsFromClass(parseClass: classname, include: include, completion: { [weak self] (objects) in
self?.resultSet.increaseTotal(byNumber: objects.count)
completion(objects)
})
}
else {
print("LIMIT REACHED")
}
}
In my case, the fetchObjectsFromClass is a global function where the query is generated and returns an array of PFObjects.
Hopefully this gives you an idea of what you need to do