Cannot load image from PARSE into PFTableViewCell's imageView property - swift

I'm working with the PFQueryTableViewController and setting it up to find only friendships for this user which has been approved or sent to him.
"fromUser" and "toUser" are pointers to the user class, where I need the username and profilePicture from for each of the users contained in the queries results.
Now I try to fetch those in my cellForRowAtIndexPath method and load the image:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell? {
let cellIdentifier = "contactCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as! PFTableViewCell
if let user = object?["fromUser"] as? PFUser {
user.fetchInBackgroundWithBlock({ (user, error) -> Void in
if error != nil {
println("Could not fetch user object")
}
let user = user as! PFUser
cell.textLabel?.text = user.username!
cell.imageView?.file = user["profilePicture"] as? PFFile
cell.imageView?.loadInBackground()
})
} }
Getting the username to display in the tableView works just fine, but the image is actually never loaded. I tried different approaches to get the image loaded, but my cell's imageView property is always nil.
The prototype's class is set to PFTableViewCell
The controller is linked to the view in storyboard
Please let me know, if you guys have any idea why this built in property is nil and how to fix that.
Thanks,

Well I found a workaround which actually works quite good:
Create a prototype cell in your TableView (Storyboard) and set it's class to the normal "UITableViewCell"
Set the reuseIdentifier property of this cell to a value of your liking
Let your custom cell's file owner (I created a nib file for this cell) to be a subclass of PFTableViewCell
Create custom outlets for the textLabel and imageView
Register that Nib for the reuseIdentifier set in Step 2 in your TableViewController
Finally, use that class in your cellForRowAtIndexPath method like this:
let cell: PeopleTableViewCell! = tableView.dequeueReusableCellWithIdentifier("peopleCell") as? PeopleTableViewCell
I hope this will fix your problems too.
Regards,
[Edit]: It seems like SO doesn't like my code snippet to be formatted. It's embedded in code tags..

The PFTableViewCell does not work well with standard styles. So you have to make your cell a custom cell.
These re the steps I took to make it work (in Storyboard):
subclass PFTableViewCell with you own class
in the storyboard, customize the prototype cell using a custom cell
drag a UIImageView from the palette but then set its class as PFImageView
connect it to an IBOutlet in your PFQueryTableViewController subclass
implement cellForRowAtIndexPath: in the PFQueryTableViewController the way you did (your code was OK).
That way worked for me.

Related

Swift: Update UITableView from UIButton inside custom UITableViewCell

I have a UITableViewController called TableVC and a custom UITableViewCell called CustomCell. Inside the CustomCell class, I have a UIButton:
#IBAction func reloadTableView(sender: AnyObject) {
TableVC().tableView.reloadData()
}
When the corresponding button is tapped, it does not seem to update/reload my table view. Why not, and what can I do to resolve my issue?
Thanks!
You can get the tableView of a cell in several ways. The superview of your cell should be of type UITableView so in your cell you can (superview as? UITableView)?.reloadData().
However a more stable method is using the hierarchy of responders. I have created a really useful extension that allows you to find the next of responder of a particular type such a UITableView.
extension UIResponder {
func nextResponder<T: UIResponder>(ofType type: T.Type) -> T? {
switch nextResponder() {
case let responder as T:
return responder
case let .Some(responder):
return responder.nextResponder(ofType: type)
default:
return nil
}
}
}
This is a recursive function, so it climbs the responder hierarchy until it successfully casts a responder to the provided type or runs out of responders to try and cast.
#IBAction func reloadTableView(sender: AnyObject) {
nextResponder(ofType: UITableView.self)?.reloadData()
}
Whenever you are clicking a button you are creating a new instance of TableVC and you are reloading the that tableview data. instead of this store a TableVC object somewhere and reload data on that. either create a IBOutlet of TableVC or create a property in a class and assign TableVC ref to that.
use only
self.tableView.reloadData()

Selecting Multiple Table View Cells At Once in Swift

I am trying to make an add friends list where the user selects multiple table view cells and a custom check appears for each selection. I originally used didSelectRowAtIndexPath, but this did not give me the results I am looking for since you can highlight multiple cells, but unless you unhighlight the original selected row you cannot select anymore. I then tried using didHighlighRowAtIndexPath, but this doesn't seem to work because now I am getting a nil value for my indexPath. Here is my code:
override func tableView(tableView: UITableView, didHighlightRowAtIndexPath indexPath: NSIndexPath) {
let indexPath = tableView.indexPathForSelectedRow
let currentCell = tableView.cellForRowAtIndexPath(indexPath!) as! AddedYouCell
let currentUser = PFUser.currentUser()?.username
let username = currentCell.Username.text
print(currentCell.Username.text)
let Friends = PFObject(className: "Friends");
Friends.setObject(username!, forKey: "To");
Friends.setObject(currentUser!, forKey: "From");
Friends.saveInBackgroundWithBlock { (success: Bool,error: NSError?) -> Void in
print("Friend has been added.");
currentCell.Added.image = UIImage(named: "checked.png")
}
}
How can I solve this? Thanks
I'm not going to write the code for you, but this should help you on your way:
To achieve your goal, you should separate the data from your views (cells).
Use an Array (i.e. friendList) to store your friend list and selected state of each of them, and use that Array to populate your tableView.
numberOfCellsForRow equals friendList.count
In didSelectRowAtIndexPath, use indexPath.row to change the state of your view (cell) and set the state for the same index in your Array
In cellForRowAtIndexpath, use indexPath.row to retrieve from the Array what the initial state of the cell should be.

Why do I have to click the row to reveal the image since it has been loaded?

I am on the assignment 4 of Stanford Course "Developing iOS 8 Apps With Swift" by Paul Hegarty. The assignment is developing an app searching Twitter to get some tweets and display them in a tweets table view. And if i click one row of the tweets table view, it segues to a detail table view which displays the hashtag, urls, user mentioned and attached media photos of the tweet in four sections.
The media photo section displays the images attached in the tweet in its own custom cell called mediaCell which contains only a single imageView. But I find, after the image in the image URL is loaded to the imageView of the mediaCell using NSData(contentsOfURL:) and UIImage(data:)method, the imageView's image doesn't show until I click the row which has no segues. Just one click can show the image and if i don't click, the image just exists in the memory and can't be drawn in the correspond imageView.
Here is the code downloading image in the URL and loading it to the imageView in the mediaCell.
The mediaCell is a custom UITableViewCell which only has an imageView called mediaImageView.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(Storyboard.tweetDetailGeneralCellIdentifier, forIndexPath: indexPath)
// Configure the cell...
switch indexPath.section {
case 0:
cell.textLabel?.text = tweet.hashtags[indexPath.row].keyword
return cell
case 1:
cell.textLabel?.text = tweet.urls[indexPath.row].keyword
return cell
case 2:
cell.textLabel?.text = tweet.userMentions[indexPath.row].keyword
return cell
case 3:
let mediaCell = tableView.dequeueReusableCellWithIdentifier(Storyboard.tweetDetailMediaCellIdentifier, forIndexPath: indexPath) as! TweetDetailMediaCell
if !tweet.media.isEmpty {
for media in tweet.media {
let qos = Int(QOS_CLASS_USER_INITIATED.rawValue)
let queue = dispatch_get_global_queue(qos, 0)
dispatch_async(queue){
let imageData = NSData(contentsOfURL: media.url)
if imageData != nil {
dispatch_async(dispatch_get_main_queue()) {
mediaCell.mediaImageView.image = UIImage(data: imageData!)
print("mediaCellImage is loaded")
}
}
}
}
}
return mediaCell
default: break
}
return cell
}
when the "mediaCellImage is loaded" is printed, the image load should be finished, but if i don't click the row, the image never show up. if i click even just one time, it will show up.
there is no change if i add "mediaCell.mediaImageView.setNeedsDisplay" after the print("mediaCellImage is loaded").
The problem is that when the image would be loaded, the mediaCell may be dequeued for another row.
Dequeue the cell one more time when an image would be loaded (inside the main queue):
if let mediaCell = tableView.cellForRowAtIndexPath(indexPath) as? TweetDetailMediaCell {
mediaCell.mediaImageView.image = UIImage(data: imageData!)
}
Finally, i find the real reason lies in the number of prototype cell instance which is created more than the number needed.
whenever the table view asks for a cell, i create a prototype cell instance first. and then i check whether it is suitable for the indexPath. if not, i then create a custom cell instance and return it.
That means some prototype cell instances are created unused. cell instance is expensive, i think apple uses this unused cells for some performance enhance(i am not sure about this). Therefore, they affect the custom cell instance appearance, resulting in situation where i have to click the row to reveal the image. After i correct this error, everything works fine. If anybody knows the detail reason, please post here. Really appreciated.
Hope this answer is helpful.

Passing Core Data objects from UITableViewCell to another View Controller

Currently I have the application loading up the data in a tableview presented to the user that I want them to select the object they wish to view further details about. From their I want to be able to read out the data in the object to the user in various forms either labels or a tableview dependent on the data.
I have been reading around and trying to understand this stuff but it is all still new to me! I had another guy on here help me out with saving the data and passing the data around to store but reading it out is a seemingly different thing. Any examples in swift language would lead me down the right path I know that I need to use the didselectrow method and call a segue and also that I need a prepare for segue but I am not quite sure how it should look.
I have read multiple posts and some do actually attempt to pass objects but not in the manner in which I am trying to..Are you able to pass whole objects after they have been have been selected in a tableview to another view controller to present all data related to that object or are you only able to pass information from that object to the next viewcontroller? I have examples of prepareforsegue and perform segue before not sure what else I am missing but I am currently not able to pass any information between the tableview and the viewcontroller.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "decktoRecord") {
var detailVC: StatWidgetViewController = segue.destinationViewController as! StatWidgetViewController
detailVC.deckRecord = decktoPass
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let indexPath = tableView.indexPathForSelectedRow()
let selectedCell = tableView.cellForRowAtIndexPath(indexPath!) as UITableViewCell?
self.performSegueWithIdentifier("decktoRecord", sender: indexPath);
}
decktoPass is just a variable that contains the type of data entity that is the primary entity of the object.
A little confused by what you're asking but from what I understand, once the user clicks on a cell you want to segue to another view controller where they can edit the the details?
To add an exception breakpoint, open up your left panel with all your files/viewcontrollers, at the very top of it should be a small panel with a few icons, the first one is a folder, click on the second last one (the one that looks like a tag)
click on the plus in the bottom right and click add exception breakpoint, this should let you know where the problem in your code is occurring
Okay to edit the details in another View controller start by preparing the segue from the original view controller
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showTaskDetail" {
let detailVC: TaskDetailViewController = segue.destinationViewController as! TaskDetailViewController
let indexPath = self.tableView.indexPathForSelectedRow()
let thisTask = fetchedResultsController.objectAtIndexPath(indexPath!) as! TaskModel
detailVC.detailTaskModel = thisTask
detailVC.delegate = self
}
else if segue.identifier == "showTaskAdd" {
let addTaskVC:AddTaskViewController = segue.destinationViewController as! AddTaskViewController
addTaskVC.delegate = self
}
}
okay so like the code is showing above, I have a segue called showTaskDetail which shows the details of whatever it is, in my case its a simple task. You said you want to edit this information in another view controller when a user clicks on the row, well you need to be able to get this information in the other view controller.
So create a variable in the other viewcontroller that will hold these values, for me i called it detailTaskModel
var detailTaskModel: TaskModel!
Incase you're confused about what TaskModel is, I'm using CoreData to store my data, and TaskModel is a NSMangedObject class.
let detailVC: TaskDetailViewController = segue.destinationViewController as! TaskDetailViewController
this line you're just specifying what your other view controller is, replace TaskDetailViewController with your swift class.
let detailVC: TaskDetailViewController = segue.destinationViewController as! TaskDetailViewController
this line fetches the data from the row selected
Now you should be able to pass your information into the other view controller and edit it using the detailTaskModel, do you need help with that as well?

Reloading single row in PFTableViewCell - Returns that PFTableViewCell does not have cellForRowMethod

Ok so I've been trying to make a like button for an app I'm working on and I'm using parse as a backend. So far I can update the like button in parse, however, I can only reload the entire tableview not just the single cell. Reloading the entire tableview sometimes causes the tableview to move up or down I believe because of different sized cells.
#IBAction func topButton(sender: UIButton) {
let uuid = UIDevice.currentDevice().identifierForVendor.UUIDString
let hitPoint = sender.convertPoint(CGPointZero, toView: self.tableView)
let hitIndex = self.tableView.indexPathForRowAtPoint(hitPoint)
let object = objectAtIndexPath(hitIndex)
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: hitIndex!) as TableViewCell
//var indexOfCell:NSIndexPath
//indexOfCell = hitIndex!
if object.valueForKey("likedBy") != nil {
var UUIDarray = object.valueForKey("likedBy")? as NSArray
var uuidArray:[String] = UUIDarray as [String]
if !isStringPresentInArray(uuidArray, str: uuid) {
object.addObject(uuid, forKey: "likedBy")
object.incrementKey("count")
object.saveInBackground()
//self.tableView.reloadData()
}
}
}
I've already tried the cellForRow with the proper indexPath and it always returns that PFTableViewCell does not contain the cellForRowAtIndexPath method. I read on a forum that someone was able to create a custom method to reload the cell and Parse's documentation uses PAPCache to do the like button. Lucky for me the documentation for the like button on Parse's page is in Obj-C and I've coded my app in Swift. Here's the link to the page if you want to see: https://www.parse.com/tutorials/anypic#like. Section 6.1.
TD;LR I'm unsure how to update the cell so the entire tableview does not get reloaded without the cellForRowAtIndexPath method. Maybe Custom method? Maybe PAPCache method?
SOLVED
if object.valueForKey("likedBy") != nil {
var UUIDarray = object.valueForKey("likedBy")? as NSArray
var uuidArray:[String] = UUIDarray as [String]
if !isStringPresentInArray(uuidArray, str: uuid) {
object.addObject(uuid, forKey: "likedBy")
object.incrementKey("count")
object.saveInBackground()
let count = object.valueForKey("count") as Int?
//cell.count.text = "\(count)"
self.tableView.reloadRowsAtIndexPaths([hitIndex!], withRowAnimation: UITableViewRowAnimation.None)
This properly reloads only the cell, however, now my entire tableview always scrolls to the top which is not ideal.
I don't understand what are you trying to with the UUID; hitpoint ;hitIndex, and I don't understand what you want your app to do and I don't have and experience with parse ? I would comment on your question,but I need 50 rep to do this.Sorry that this isn't an answer,but a question .And if you are referring about cellForRowAtIndexPath,you need a UITableView not a cell.