UITableViewController with Section and Parse as BackEnd server - ios

I don't find any good and update answers about loading data from Parse to an UITableViewController with sections.
I know that using PFQueryTableViewController is only possible for 1 section.
I have a class Recipes with the following columns in Parse:
Section; Name; Calories
Hence my database looks like this
Morning; Eggs; 120
Morning, Bacon; 250
Lunch; Meat; 340
....
I compute a function to query the data from Parse like this:
func queryData(){
var query = PFQuery(className: self.recipesClass as String)
//query.cachePolicy = .CacheElseNetwork
query.orderByDescending("createdAt")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// Results were successfully found, looking first on the
// network and then on disk.
// Do something with the found objects
if let objects = objects as? [PFObject] {
for object in objects {
self.tableSections.addObject(object)
}
} else {
// The network was inaccessible and we have no cached data for
// this query.
}
}
}
Where tableSections is a NSMutableArray.
From here, I'm a bit lost on how to proceed to achieve the required results.
Please help,
Thank you in advance

You will want to create different sections based on the 'Section' property of each returned object. This is a bit tricky for someone new to UITableView, but it can be accomplished in around an hour with Sensible TableViews (http://sensiblecocoa.com/).
Sensible TableViews will let you take an array of objects and separate it into several sections. It even interfaces directly with Parse.com if you so desire. I use it in almost all of my apps for its simple, clean approach to tables and cloud data.
Start with their online guide here:
http://sensiblecocoa.com/usermanual/latest/
You can also skip right to Parse.com Integration here: http://sensiblecocoa.com/usermanual/latest/#ExploringParseComBinding

Related

Use Realm with Collection View Data Source Best Practise

I'll make it short as possible.
I have an API request that I fetch data from (i.e. Parse).
When I'm getting the results I'm writing it to Realm and then adding them to a UICollectionView's data source.
There are requests that take a bit more time, which run asynchronous. I'm getting the needed results after the data source and collection view was already reloaded.
I'm writing the needed update from the results to my Realm database.
I have read that it's possible to use Realm's Results. But I honestly didn't understood it. I guess there is a dynamic and safe way working with collection views and Realm. Here is my approach for now.
This is how I populate the collection view's data source at the moment:
Declaration
var dataSource = [Realm_item]()
where Realm_item is a Realm Object type.
Looping and Writing
override func viewDidLoad() {
super.viewDidLoad()
for nowResult in FetchedResultsFromAPI
{
let item = Realm_item()
item.item_Title = nowResult["Title"] as! String
item.item_Price = nowResult["Price"] as! String
// Example - Will write it later after the collectionView Done - Async request
GetFileFromImageAndThanWriteRealm(x.image)
// Example - Will write it later after the collectionView Done - Async request
dataSource.append(item)
}
//After finish running over the results *Before writing the image data*
try! self.realm.write {
self.realm.add(self.dataSource)
}
myCollectionView.reloadData()
}
After I write the image to Realm to an already created "object". Will the same Realm Object (with the same primary key) automatically update over in the data source?
What is the right way to update the object from the data source after I wrote the update to same object from the Realm DB?
Update
Model class
class Realm_item: Object {
dynamic var item_ID : String!
dynamic var item_Title : String!
dynamic var item_Price : String!
dynamic var imgPath : String?
override class func primaryKey() -> String {
return "item_ID"
}
}
First I'm checking whether the "object id" exists in the Realm. If it does, I fetch the object from Realm and append it to the data source. If it doesn't exist, I create a new Realm object, write it and than appending it.
Fetching the data from Parse
This happens in the viewDidLoad method and prepares the data source:
var query = PFQuery(className:"Realm_item")
query.limit = 100
query.findObjectsInBackgroundWithBlock { (respond, error) -> Void in
if error == nil
{
for x in respond!
{
if let FetchedItem = self.realm.objectForPrimaryKey(Realm_item.self, key: x.objectId!)
{
self.dataSource.append(FetchedItem)
}
else
{
let item = Realm_item()
item.item_ID = x.objectId
item.item_Title = x["Title"] as! String
item.item_Price = x["Price"] as! String
let file = x["Images"] as! PFFile
RealmHelper().getAndSaveImageFromPFFile(file, named: x.objectId!)
self.dataSource.append(item)
}
}
try! self.realm.write {
self.realm.add(self.dataSource)
}
self.myCollectionView.reloadData()
print(respond?.count)
}
}
Thank you!
You seem to have a few questions and problems here, so I'll do my best.
I suggest you use the Results type as your data source, something like:
var dataSource: Results<Realm_item>?
Then, in your viewDidLoad():
dataSource = realm.objects(Realm_item).
Be sure to use the relevant error checking before using dataSource. We use an optional Results<Realm_item> because the Realm object you're using it from needs to be initialised first. I.e., you'll get something like "Instance member * cannot be used on type *" if you try declaring the results like let dataSource = realm.objects(Realm_item).
The Realm documentation (a very well-written and useful reference to have when you're using Realm as beginner like myself), has this to say about Results...
Results are live, auto-updating views into the underlying data, which means results never have to be re-fetched. Modifying objects that affect the query will be reflected in the results immediately.
Your mileage may vary depending on how you have everything set up. You could try posting your Realm models and Parse-related code for review and comment.
Your last question:
What is the right way to update the "object" from the Data Source after i wrote the update to same object from the Realm DB?
I gather you're asking the best way to update your UI (CollectionView) when the underlying data has been updated? If so...
You can subscribe to Realm notifications to know when Realm data is updated, indicating when your app’s UI should be refreshed for example, without having to re-fetch your Results.

Handle large JSON files in TableView

I'm trying to parse a large JSON file (approx: 1000 rows containg a tuple with 8 strings) and display this in a UITableView. What I already have is working but I am looking for a more efficient way of displaying them.
At the moment my code looks likes this:
public func GET(request: String, callback: (result: JSON?, response: NSHTTPURLResponse?, error: NSError?) -> Void) {
let session = NSURLSession.sharedSession()
let url = NSURL(string : "SOMEURL")
let task = session.dataTaskWithURL(url!){
(data, response, error) -> Void in
if error != nil {
callback(result: nil, response: response as? NSHTTPURLResponse, error: error!)
} else {
callback(result: JSON(data : data!), response: response as? NSHTTPURLResponse, error: nil)
}
}
task.resume()
}
This does parse the data using SwiftJSON (see JSON(data : data!)), then when it comes to actually filling an array i use a class containing two attributes (one for the Main text in table and one for detail text)
class SomeClass {
let MainText : String
let DetailText : String
init(MainText : String, DetailText : String) {
self.MainText = MainText
self.DetailText = Detailtext
}
}
Now in the UITableView i have a .swift file and in the
override func ViewDidLoad() {
//code
}
I use a loop to get the data from the result callback in the GET method to append to an array of
var rows : [SomeClass] = []
This is very CPU intensive but I did not find another way to deal with this problem. I tried only displaying 50 rows in the table and only creating 50 class items for the rows. But none of that matters, what I fear is that the SwiftyJSON way of dealing with this problem is not the right one but i thought that maybe I am overlooking something.
If I understood your problem, you are worried about CPU / Energy Efficiency.
What you should consider, if it's not how your app already works, is implementing the parsing process in the background thread, make your [SomeClass] array observable and update the table when it changes (aka when the background parsing added an new value to it).
So first make your parsing function run in background (for instance with the Async GCD wrapper) :
func callback(JSON?, response: NSHTTPURLResponse, error: NSError?) {
Async.background {
//Do your JSON parsing stuff here, XXX is a SomeClass object
rows <- rows + [XXX]
}
}
You might have noticed the unusual syntax for the array appending method. That's because making your array "observable" is part of the solution. I advise you to get the Observable-Swift library to make it easier to observe.
Once added to your project, change your array declaration :
var rows = Observable([SomeClass]())
Now implement the method that will be called when your callback parsed a new item (for instance in your viewDidLoad:)
rows.afterChange += { self.table.reloadData() }
where table is your table view
If you want to implement a power-friendly runtime, you might want to update the table every time 50 or 100 objects are added to the array. This can be done so (if you want to do so do not implement the method right above):
rows.afterChange += { if $1.count / 100 = 1 { self.table.reloadData() }}
where 100 is the value of new object required to be added in order to update the table. With Observable-Swift, $0 represents the array before it was updated and $1 the array after its update.
One last thing : the rows array is no longer of type [SomeClass] but Observable<SomeClass>. If you want to access the [SomeClass] value, just replace rows by rows.value
Hope I didn't misunderstood your question. Anyway if I did, I think that can still help providing a better implementation of JSON parsing.
You should not be worried about how much of data you have to display in TableView.
TableView class handles everything for you as long as you pass the json object properly as a Tablesource.
It's actually a pretty good concern about how you use the resources. Normally, we will go with pagination if you don't want to query back whole amount of data from a request. Then, you will implement some proper logic based on the skip and limit in order to get further data.
As for the UITableView, there is nothing to worry about. Because, it's developed in an efficient way. The total number of cell in memory is the total number of cell visible. The UITableView will help populating the data via delegation methods. It's not like: you have 500 rows of data, then it has 500 UITableViewCell. It's reusability.

How do I use the data within my query within a table view cell?

I am making a basic iOS application, within that application I have a table-view-controller. I need this controller to populate the cells based on information in my Parse.com database.
Here is the information I specifically need:
candidateName (which is a string)
candidateImage (which is an image file)
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// Retrieves the name from DB
let nameQuery = PFQuery(className: "Candidate")
// Network request
nameQuery.findObjectsInBackgroundWithBlock {
(result: [AnyObject]?, error: NSError?) -> Void in
if error == nil && result != nil {
// The find succeeded.
println(result?.count)
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo)")
}
}
}
I am getting the objects back as expected, but how do I get specifically just the name and the image? Do I do two separate queries for each piece of information?
There are tons of great tutorials out there for iOS Swift + Parse. Dive into a tutorial and you'll have an easy time learning how to get the job done.
In response to your latter questions, you will only need one query as long as you have associated the name and image to the Candidate class.
Good luck and have fun with it!
I'm going to apologize in advance, I'm going to write the answers in objective-C, as I have yet to learn swift! :-) So basically in your class create an NSArray. Let's suppose you name it data. In your viewDidLoad, just use:
PFQuery *query = [PFQuery queryWithClassName:#"Candidate"];
data = [query findObjects];
In your numberOfRowsInSection, just put return [query countObjects];
In cellForIndexPath:(NSIndexPath *)indexPath, you can get the data of an object by doing ((PFObject *)(data[indexPath.row]))[#"attr"]
Hope this helps.

GCD with nested Parse Queries

func getPosts(skip: Int){
var query = PFQuery(className: self.parseClassName!)
query.includeKey("posted_by")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil && objects != nil {
if let objects = objects as? [PFObject] {
var requestGroup = dispatch_group_create()
for post in objects
{
dispatch_group_enter(requestGroup)
let queryKommentar1 = PFQuery(className:"Comment")
queryKommentar1.whereKey("posted_to", equalTo: post)
queryKommentar1.limit = 3
queryKommentar1.includeKey("written_by")
queryKommentar1.findObjectsInBackgroundWithBlock() {
(commentObjects: [AnyObject]?, error: NSError?) -> Void in
//Creating UITableViewCells from data and store in array
dispatch_group_leave(requestGroup)
}
}
println("Successfully retrieved \(objects.count) posts.")
dispatch_group_notify(requestGroup, dispatch_get_main_queue()) {
println("All done")
}
}
}
}
}
So, I'm not sure if I misunderstood dispatch groups, but my intention is to make two Parse Queries, targeting different classes, and create TableViewCells from the data provided. This works fine, but since I don't want the data to load when the user is scrolling the table, I want to preload the data and create the cells, and store them in an Array. Since I would like to remove any Activity Indication, and reload the table, by the time the fetch is complete I though (after som Googleing) that dispatch groups might be a good solution for this. However, "All done" is never printed in the console.
When I made a dispatch group around the outer query (entering the group just before the query, and leaving as the last line in the block) that worked fine. What am I doing wrong? Is it impossible to use this when nesting asynchronous calls?
FYI, I removed a lot of code, like creating the cells and using the data from Parse, since I would like to spare you from reading that mess.
This dispatch group pattern is basically right.
I would suggest logging some message at dispatch_group_enter and at dispatch_group_leave and see if it's getting called as you think it should and that every enter is offset by a leave.
If the number of occurrences of dispatch_group_leave are less than the number of calls to dispatch_group_enter, the dispatch_group_notify block will not get called.
Perhaps you have some path in that inner findObjectsInBackgroundWithBlock closure that is preventing it from hitting the dispatch_group_leave call.

Trying to use a pointer to another class as part of a Parse Query in Swift

Okay so I have a view where I need to show all the posts by a business near the user's location. The way my database is set up is such that there is a "BusinessPost" class that contains, among other things, a pointer to my "Businesses" class, so that each post has an owner.
When I'm trying to write the query for my view, I can't seem to figure out how to only get the posts from the user's current location. I can get all the posts from every business, or I can get every business in the user's location, but I can't figure out a way to combine the two in order to get just the posts that came from businesses near where the user is.
My thinking was to first get all the businesses near the user's location, and then make a second query using the "inQuery" argument, to find all the posts that had a pointer reference to a business near the user's location that was gotten in the first query.
However, this won't work, and I've been stuck on this for a couple of days. Does anyone have any suggestions? Help would be greatly appreciated.
getPostsFromLocatedBusinesses.whereKey("Businesses", matchesKey: "objectId", inQuery: getBusinessesInLocation)
EDIT 1: Okay so i rewrote it according to what you suggested and came up with the following, but it still doesn't produce any results:
override func queryForTable() -> PFQuery! {
var getPosts = PFQuery(className: self.parseClassName)
var getBusinesses = PFQuery(className: "Businesses")
if locationUpdated == false {
return nil
}
else {
getBusinesses.whereKey("City", equalTo: self.city)
getBusinesses.whereKey("State", equalTo: self.state)
getPosts.whereKey("Business", matchesQuery: getBusinesses)
getPosts.includeKey("Business")
self.tableView.hidden = false
self.tableView.reloadData()
return getPosts
}
}
In order to query based on two separate Parse classes, where one part of the query is a pointer to another class, the best way to do so is to have an outer query on the class which contains the pointer, and then an inner query that finds whatever supplementary objects you're looking for. In my case, this meant finding businesses that were in the user's location, and then only showing Business Posts near the user's location, where in the "BusinessPost" class, the key "Business" is a pointer to my "Business" class, that contains other information about the business that the UI needed. The query would look as follows:
override func queryForTable() -> PFQuery! {
var getPosts = PFQuery(className: self.parseClassName)
var getBusinesses = PFQuery(className: "Businesses")
if locationUpdated == false {
return nil
}
else {
getBusinesses.whereKey("City", equalTo: self.city)
getBusinesses.whereKey("State", equalTo: self.state)
getPosts.whereKey("Business", matchesQuery: getBusinesses)
getPosts.includeKey("Business")
self.tableView.hidden = false
self.tableView.reloadData()
return getPosts
}
Hope this can help others that are looking to accomplish a similar query with Parse and Swift.

Resources