I am creating an app that loads data into a UITableView from Parse. I used some code that seems to work for loading the retrieved data into the cells in the table, but it is buggy and I did some research and found the PFQueryTableViewController. This seems like the solution to my problem, but all the documentation is in Objective-C (which I do not have any experience coding in), and there are no tutorials from what I could find on how to use this class in Swift. There are a few snippets that talk about how to initialize it, but nothing that really goes through how to set it up in Swift. I tried to go through the Obj-C documentation and set it up myself in Swift but I can't seem to get it right and would really appreciate some help. Does anyone have a suggested resource or has anyone set one up and could step through how to do this in Swift?
EDIT: I have found some resources and tried to get it set up, the table view loads but nothing is loaded into the cells and I am not sure. I am going to leave the code below. The commented out section wasn't relevant to my code but left it for reference. Any further help as to why the table isn't loading would be helpful!
class MyCookbookVC : PFQueryTableViewController {
override init!(style: UITableViewStyle, className: String!) {
super.init(style: style, className: className)
textKey = "Name"
pullToRefreshEnabled = true
paginationEnabled = true
objectsPerPage = 25
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func viewDidLoad() {
tableView.reloadData()
}
override func queryForTable() -> PFQuery! {
let query = PFUser.query()
query.whereKey("CreatedBy", equalTo: PFUser.currentUser())
query.orderByAscending("Name")
//if network cannot find any data, go to cached (local disk data)
if (self.objects.count == 0){
query.cachePolicy = kPFCachePolicyCacheThenNetwork
}
return query
}
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!, object: PFObject!) -> PFTableViewCell! {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as PFTableViewCell
cell.textLabel?.text = object["Name"] as? String
/*if let profileImage = object["profileImage"] as? PFFile {
cell.imageView.file = profileImage
}
else {
cell.imageView.image = kProfileDefaultProfileImage
}
cell.imageView.layer.cornerRadius = cell.imageView.frame.size.width / 2;
cell.imageView.clipsToBounds = true
cell.imageView.layer.borderWidth = 3.0
cell.imageView.layer.borderColor = UIColor.whiteColor().CGColor */
/* cell.textLabel?.font = UIFont(name: kStandardFontName, size: kStandardFontSize)
cell.textLabel?.textColor = UIColor.whiteColor()
cell.backgroundColor = kbackgroundColor */
return cell
}
}
Your query is querying PFUser when it should it be querying your "Recipe" class. Change this line
let query = PFUser.query()
to
let query = PFQuery.queryWithClassName("Recipe")
I posted my working solution in the Parse Google Group. Maybe it will help you.
https://groups.google.com/forum/#!topic/parse-developers/XxLjxX29nuw
You're using PFUser.query() which is useful for querying for users, but you want to use PFQuery(className: YOUR_PARSE_CLASS)
Related
I am building a tableView which displays a message when empty.
I'm using the really helpful answers on this question (Handling an empty UITableView. Print a friendly message) This has led me to a function:
func emptyMessage(message:String, viewController:UITableViewController) {
let VCSize = viewController.view.bounds.size
let messageLabel = UILabel(frame: CGRect(x:0, y:0, width:VCSize.width, height:VCSize.height))
messageLabel.text = message
messageLabel.textColor = UIColor.black
messageLabel.numberOfLines = 0
messageLabel.textAlignment = .center;
messageLabel.font = UIFont(name: "Futura", size: 15)
messageLabel.sizeToFit()
viewController.tableView.backgroundView = messageLabel;
viewController.tableView.separatorStyle = .none;
}
I could call this in every table views data source like this :
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if projects.count > 0 {
return 1
} else {
TableViewHelper.EmptyMessage("You don't have any projects yet.\nYou can create up to 10.", viewController: self)
return 0
}
}
which would work. However I would rather not have to implement that repeatedly and instead have one custom tableview with a method in the data source asking what message you would like to add.
I've tried extending the TableView class or making a subclass of tableView but I think this isn't the solution. Instead I think the solution is to overwrite the UITableViewDataSource protocol but my knowledge of protocols and delegation isn't sufficient.
I hope i'm on the right track with this. And to clarify I could do it in the way mentioned above but i'm trying to override the functionality to make a smart solution where i'm not repeating myself.
There is a very good library :
https://github.com/dzenbot/DZNEmptyDataSet
This can be used for all types of containers like UITableView, UICollectionView etc.
After conforming to some DZNEmptyDataSetSource, DZNEmptyDataSetDelegate , you can simply implement these functions:
func title(forEmptyDataSet scrollView: UIScrollView) -> NSAttributedString? {
let str = "Empty Data."
let attrs = [NSFontAttributeName: UIFont.preferredFont(forTextStyle: UIFontTextStyle.headline)]
return NSAttributedString(string: str, attributes: attrs)
}
func description(forEmptyDataSet scrollView: UIScrollView) -> NSAttributedString? {
let str = "Any Details about empty data."
let attrs = [NSFontAttributeName: UIFont.preferredFont(forTextStyle: UIFontTextStyle.body)]
return NSAttributedString(string: str, attributes: attrs)
}
Apart from that, you can add a button to to perform some actions. Please refer the library for more features.
Just updated to the newer FirebaseUI Pod - a few things have changed but one of the big ones is the way that the FUI Table View works. I had it working well on an older version but am struggling with this below - and the lack of documentation/examples.
self.dataSource = FUITableViewDataSource(query: <#T##FIRDatabaseQuery#>, view: <#T##UITableView#>, populateCell: <#T##(UITableView, IndexPath, FIRDataSnapshot) -> UITableViewCell#>)
I don't understand where the indexpath is being called from. Do I need to make a seperate NSIndexPath to pass into that? I also don't really understand where this is supposed to live - previously, with it was FirebaseTableViewDataSource, I would set it in my viewDidLoad, and it would create the cells etc straight from that. It almost now looks as though it needs to live in my cellForRowAtIndexPath. Does anyone have any advice on this?
The test for this latest version uses a tableView:bind: method (seems like a UITableView class extension they made) and I was able to get it to work.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let firebaseRef = FIRDatabase.database().reference().child(/*insert path for list here*/)
let query = firebaseRef.queryOrderedByKey() /*or a more sophisticated query of your choice*/
let dataSource = self.tableView.bind(to: query, populateCell: { (tableView: UITableView, indexPath: IndexPath, snapshot: FIRDataSnapshot) -> UITableViewCell in
let cell = tableView.dequeueReusableCell(withIdentifier: "cellIdentifier", for: indexPath)
let value = snapshot.value as! NSDictionary
let someProp = value["someProp"] as? String ?? ""
cell.textLabel?.text = someProp
return cell
})
}
Also make sure you are observing your query for changes or else the tableView won't get populated
self.query?.observe(.value, with: { snapshot in
})
I am making an application where the user can see certain items/users within his x km radius (much like Tinder where you can set the radius of girls/guys you want to see in your area). So in my cellForRowAtIndexPath function I am determining whether a cell can be shown.
If he is in the radius, the event is shown. If the location is too far away, it shouldn't be using a cell.
My current code just hides the cell, but it is still clickable. I want it to NOT use a cell in the first place, but I couldn't find how to do it. Any ideas?
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> AllGeneralMeetsTableViewCell {
//get location from backend
let locLati = object?["coordLat"] as? Double
let locLongi = object?["coordLong"] as? Double
let currentLocation:CLLocation = CLLocation(latitude: localLati , longitude: localLongi)
let meetLocation:CLLocation = CLLocation(latitude: locLati! , longitude: locLongi!)
let meters:CLLocationDistance = currentLocation.distanceFromLocation(meetLocation)
// make distance in km
let distInKm = meters/1000
//get distance that user has set in his settings
let cell = tableView.dequeueReusableCellWithIdentifier("GeneralMeetsIdentifier") as! AllGeneralMeetsTableViewCell!
if (distInKm <= settingsKm) {
// Extract values from the PFObject to display in the table cel
if let title = object?["title"] as? String {
cell?.titleCell?.text = title
}
if let message = object?["message"] as? String {
cell?.messageCell?.text = message
}
if let image = object?["image"] as? PFFile {
image.getDataInBackgroundWithBlock({ (imageData: NSData?,error: NSError?) -> Void in
if (error == nil) {
let image1 = UIImage(data:imageData!)
cell.imageCell.image = image1
}
})
}
return cell
}
else {
return cell
}
}
}
The data is being returned by the following query
override func queryForTable() -> PFQuery {
let query = PFQuery(className: self.parseClassName!)
let FBID = myUser.objectForKey("facebookID")!
query.whereKey("facebookID", equalTo: FBID)
query.whereKey("private", equalTo: "false")
return query
}
In your cellForRowAtIndexPath-
if cell is to be displayed {
tableView.rowHeight = 120
//Replace 120 with desired rowHeight
} else {
tableView.rowHeight = 0
}
Hope this helps :-)
You want to make the cell disappear when there is no data. If I am right, then you can use this library called as 'DZNEmptyDataSet' to display some images telling the user that there is no data to load. Use Cocoapods to install it, or just drag the files and create a bridging header. The usage is pretty straightforward as well - just follow the documentation at the GitHub.
The proper solution is to remove the data from your data source so that it won't be displayed in the first place. But sometimes this is a lot of work.
If you just want to hide a cell you can give it a zero height using the tableView(_:heightForRowAtIndexPath:) delegate method:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.row == 1 {
return 0.0
}
return tableView.rowHeight
}
Parse provides a type PFGeopoint and it is even supported in the Query, with Parse framework you can also obtain location the same way you download stuff from database, this way you can download only those posts that are available for the user in his range...
PFGeoPoint.geoPointForCurrentLocationInBackground { (geoPoint: PFGeoPoint?, error: NSError?) -> Void in
if error == nil { //asynchronous, U have access to GPS location
// create Parse Query
let query: PFQuery = PFQuery(className: "Post")
// 5km range
query.whereKey("gps", nearGeoPoint: geoPoint, withinKilometers: 5.0)
//rest of query....
} else { // dont have access to GPS or whatever
print("Eror location: \(error)")
}
}
I'm creating a yik yak clone and I can't seem to see the messages I post in the textField(string) on Parse. Is there something wrong I'm doing in my code that's not letting it show up on Parse?
#IBAction func postPressed(sender: AnyObject) {
if(currLocation != nil){
let testObj = PFObject(className: "BubbleTest")
testObj["userName"] = PFUser.currentUser()?.username
//testObj["profileName"] = PFUser.valueForKey("profileName") as! String
//testObj["photo"] = PFUser.currentUser()?.valueForKey("photo") as! PFFile
testObj["textField"] = self.textField.text
testObj["location"] = PFGeoPoint(latitude: currLocation!.latitude , longitude: currLocation!.longitude)
testObj["count"] = 0
testObj["replies"] = 0
testObj.saveInBackground()
self.dismissViewControllerAnimated(true, completion: nil)
}
else {
alert()
}
The reason you are not seeing anything because you post it into the wrong class. According to the picture BubbleTest is the name of the class not YikYakTest
replace this line
let testObj = PFObject(className: "YikYakTest")
by
let testObj = PFObject(className: "BubbleTest")
your code should look like :
Note use saveInBackgroundWithBlock method so you could check if there is an error while saving
let testObj = PFObject(className: "BubbleTest")
let username = PFUser.currentUser()?.username
testObj["userName"] = username
testObj["textField"] = self.textField.text
testObj["Location"] = PFGeoPoint(latitude:currLocation.latitude , longitude: currLocation.longitude)
testObj["count"] = 0
testObj["replies"] = 0
testObj.saveInBackgroundWithBlock { (success:Bool, error :NSError?) -> Void in
if error == nil
{
print("detail is saved")
self.dismissViewControllerAnimated(true, completion: nil)
}
else
{
print("error")
}
}
when you are saving PFGeopoint coordinates save it into Location column not location
I know many developer friends of mine who ran into a similar issue. I myself had this problem as well, now resolved. So hopefully I can provide some insight from what I learned querying data from Parse:
Try changing the numberOfSectionsInTableView method to return 1 instead of 0 like so:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
You may need to also have some data structure to hold the users' posts (messages):
var userPosts:NSMutableArray! = NSMutableArray()
Also, your table view could then have as many rows as you will have posts stored in userPosts:
override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
return userPosts.count
}
In cellForRowAtIndexPath, replace this:
let object = PFObject(className: "BubbleTest")
WITH THIS:
let userPost : PFObject = self.posts.objectAtIndex(indexPath!.row) as! PFObject
...
cell.message.text = userPost.objectForKey("message") as! String
return cell
}
This will set the text of your custom cell's message property to whatever the user's message is (i.e.: "Testing 1 2").
Note: These steps aren't intended to be the only steps needed to solve your problem. It is meant to guide you in the right direction with some basic steps.
Hope that helps! :)
Now I'm doing a normal topic and comment integrated app. Already carried out the functions like posting topic, posting comment, and setting the pointer of my comment to the Topic class.
But now the problem that trouble me now for the past few days, is the comments displayed cannot be classified to each topic, they are all gathering together no matter what different topic they're from.
My PFObjects are Topics and Comment, two classes totally.
Topics has objects:Title, content and user.
Comment has objects: content, parent(pointer to Topics) and user.
Already check out the parse database, seeming like each comment has a same parent key from its own topic's object ID. But still wonder why they cannot be shown accordingly.
Here's my coding. I'd be grateful if ones could lend ones' helping hand.
var timelineCommentData:NSMutableArray = NSMutableArray()
func loadData(){
timelineCommentData.removeAllObjects()
var findCommentData:PFQuery = PFQuery(className: "Comment")
findCommentData.findObjectsInBackgroundWithBlock({
(objects:[AnyObject]!,error:NSError!)->Void in
if (error == nil) {
for object in objects {
self.timelineCommentData.addObject(object)
}
let array:NSArray = self.timelineCommentData.reverseObjectEnumerator().allObjects
self.timelineCommentData = array.mutableCopy() as NSMutableArray
self.tableView.reloadData()
}
})
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return timelineCommentData.count + 1 //+1 is for displaying the topic
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let identifier = indexPath.row == 0 ? "storyCell" : "CommentCell"
let cell = tableView.dequeueReusableCellWithIdentifier(identifier) as UITableViewCell
if let storyCell = cell as? TopicTableViewCell{
storyCell.titleLabel.text = topic?.objectForKey("title") as? String
storyCell.contentLabel.text = topic?.objectForKey("content") as? String
storyCell.timestampLabel.text = timeAgoSinceDate(topic!.createdAt, true)
}
storyCell.delegate = self
}
if let CommentCell = cell as? CommentsTableViewCell {
let comment:PFObject = self.timelineCommentData.objectAtIndex(indexPath.row - 1) as PFObject ///// Is anything wrong here??
var query = PFQuery(className: "Comment")
query.whereKey("parent", equalTo:topic)
query.findObjectsInBackgroundWithBlock({
(objects:[AnyObject]!,error:NSError!)->Void in
if (error == nil) {
CommentCell.commentLabel.text = comment.objectForKey("commentContent") as? String /////Or here
CommentCell.timeLabel.text = timeAgoSinceDate(comment.createdAt, true)
}
else{
println("no comment")
}
})
CommentCell.delegate = self
}
return cell
}