Error when using parse.com image in tableview - ios

I am running this code for creating a tableview based on a parse query. It works, problem is I get the following error:
2014-09-24 01:09:32.187 inventario[253:20065] Warning: A long-running Parse operation is being executed on the main thread. Break on warnParseOperationOnMainThread() to debug.
I get this when using "var datta = image?.getData()" for getting the image in place. Any ideas?
{
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("ModeloEquipoInventarioCell", forIndexPath: indexPath) as UITableViewCell;
let sweet:PFObject = self.timelineData.objectAtIndex(indexPath.row) as PFObject
cell.textLabel?.text = sweet.objectForKey("Modelo") as? String
cell.detailTextLabel?.text = sweet.objectForKey("Marca") as? String
// This part is the problem
var image = sweet.objectForKey("Foto3") as? PFFile
var datta = image?.getData()
cell.imageView?.image = UIImage(data: datta!)
cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
return cell
}
The method for the query was:
{
#IBAction func loadData(){
var findTimelineData:PFQuery = PFQuery(className: "InventarioListado")
findTimelineData.whereKey("Categoria", equalTo: toPassInventario)
findTimelineData.whereKey("Descripcion", equalTo: toPassModeloEquipoInventario)
//findTimelineData.orderByAscending("Descripcion")
findTimelineData.limit = 500
findTimelineData.findObjectsInBackgroundWithBlock{
(objects:[AnyObject]!, error:NSError!)->Void in
if error == nil{
for object in objects{
let sweet:PFObject = object as PFObject
let sweeter:NSString! = sweet.objectForKey("Modelo") as? NSString
var filtro = self.categoriasFiltradasDeInventario.containsObject(sweeter!)
if (filtro == false) {
self.categoriasFiltradasDeInventario.addObject(sweeter)
self.timelineData.addObject(sweet)
}
self.tableView.reloadData()
}
}
}
}
}

The problem is that the getData() function loads data via network connection and it can take some time to download an image. Your main thread would be blocked during this time so it's highly recommended to run it in the background. You can use getDataInBackgroundWithBlock() to do that easily.
let image = sweet["Foto3"] as PFFile
image.getDataInBackgroundWithBlock {
(imageData: NSData!, error: NSError!) -> Void in
if error == nil {
cell.imageView?.image = UIImage(data:imageData)
}
}

There is a basic concept in programming, Run the UI code on UI thread and Non-UI code in Non-UI thread.
Running the CPU intensive code like Network calls, I/O calls etc should be on Non-UIthread/Background thread.
Parse query to fetch image is a network call that is to be made on a background thread so the UI don't get strucked.
Use shared NSOperationQueue to shoot the query.

Related

UITableview reloading tableview and cell

I am trying to reload my table view using
self.tableView.reloadData()
It works properly if I'm loading static datasource using array. Everything work properly.
But when I try to use my query function with parse, it loads a new cell but the contents of the tableview cell doesn't change. If I re-open the app, the cells will update properly.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "EmpPostTVCellIdentifier"
let cell: EmpPostTVCell? = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as? EmpPostTVCell
//If datasource
if dataSource.isEmpty{
fetchDataFromParse()
print("no posts")
}
let itemArr:PFObject = self.dataSource[indexPath.row]
cell?.companyPostLabel.text = (PFUser.currentUser()?.objectForKey("companyName")!.capitalizedString)! as String
cell?.occupationPostLabel.text = itemArr["occupation"]!.capitalizedString as String
cell?.countryPostLabel.text = itemArr["country"]!.capitalizedString as String
let companyImage: PFFile?
companyImage = PFUser.currentUser()?.objectForKey("profileImageEmployer") as? PFFile
companyImage?.getDataInBackgroundWithBlock({ (data, error) -> Void in
if error == nil{
cell?.companyLogoImage.image = UIImage(data: data!)
}
})
let dateArr = createdByDate[indexPath.row]
let strDate = Settings.dateFormatter(dateArr)
cell?.closingDateLabel .text = strDate
return cell!
}
I am using pull to refresh my tableviews contents using this code
func refresh(sender:AnyObject)
{
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.fetchDataFromParse()
self.tableView.reloadData()
self.refreshControl?.endRefreshing()
})
}
with or without the dispatch_asynch function the results remains the same. It just add new tableviewcell but the contents in it does not change. Any ideas guys?
edit 1 :
func fetchDataFromParse() {
// MARK: - JOB POST QUERY
if PFUser.currentUser()?.objectId == nil{
PFUser.currentUser()?.saveInBackgroundWithBlock({ (success, error) -> Void in
let query = PFQuery(className: "JobPost")
//creating a pointer
let userPointer = PFUser.objectWithoutDataWithObjectId(PFUser.currentUser()?.objectId)
query.whereKey("postedBy", equalTo: userPointer)
query.orderByDescending("createdAt")
let objects = query.findObjects()
for object in (objects as? [PFObject])!{
//print(object.objectId)
self.dataSource.append(object)
self.createdByDate.append((object.objectForKey("closingDate") as? NSDate)!)
print(self.dataSource)
print(self.createdByDate)
}
})
} else {
let query = PFQuery(className: "JobPost")
//creating a pointer
let userPointer = PFUser.objectWithoutDataWithObjectId(PFUser.currentUser()?.objectId)
query.whereKey("postedBy", equalTo: userPointer)
query.orderByDescending("createdAt")
let objects = query.findObjects()
for object in (objects as? [PFObject])!{
//print(object.objectId)
self.dataSource.append(object)
self.createdByDate.append((object.objectForKey("closingDate") as? NSDate)!)
print(self.dataSource)
print(self.createdByDate)
}
}//end of PFUser objectID == nil else clause
}
Let's see the content of the fetchDataFromParse() function where I presume you're filling the self.dataSource array
Try to call self.tableview.reloadData() when fetchDataFromParse() is finished.
Check whether your dataSource array is empty at the end of your fetchDataFromParse method
PFUser.currentUser()?.saveInBackgroundWithBlock is an asynchronus method. So your tableView cell is having no data.

Load image from Parse to UICollectionView cell without lag

I have a pretty elaborate problem and I think someone with extensive async knowledge may be able to help me.
I have a collectionView that is populated with "Picture" objects. These objects are created from a custom class and then again, these objects are populated with data fetched from Parse (from PFObject).
First, query Parse
func queryParseForPictures() {
query.findObjectsInBackgroundWithBlock { (objects: [PFObject]?, err: NSError?) -> Void in
if err == nil {
print("Success!")
for object in objects! {
let picture = Picture(hashtag: "", views: 0, image: UIImage(named: "default")!)
picture.updatePictureWithParse(object)
self.pictures.insert(picture, atIndex: 0)
}
dispatch_async(dispatch_get_main_queue()) { [unowned self] in
self.filtered = self.pictures
self.sortByViews()
self.collectionView.reloadData()
}
}
}
}
Now I also get a PFFile inside the PFObject, but seeing as turning that PFFile into NSData is also an async call (sync would block the whole thing..), I can't figure out how to load it properly. The function "picture.updatePictureWithParse(PFObject)" updates everything else except for the UIImage, because the other values are basic Strings etc. If I would also get the NSData from PFFile within this function, the "collectionView.reloadData()" would fire off before the pictures have been loaded and I will end up with a bunch of pictures without images. Unless I force reload after or whatever. So, I store the PFFile in the object for future use within the updatePictureWithParse. Here's the super simple function from inside the Picture class:
func updateViewsInParse() {
let query = PFQuery(className: Constants.ParsePictureClassName)
query.getObjectInBackgroundWithId(parseObjectID) { (object: PFObject?, err: NSError?) -> Void in
if err == nil {
if let object = object as PFObject? {
object.incrementKey("views")
object.saveInBackground()
}
} else {
print(err?.description)
}
}
}
To get the images in semi-decently I have implemented the loading of the images within the cellForItemAtIndexPath, but this is horrible. It's fine for the first 10 or whatever, but as I scroll down the view it lags a lot as it has to fetch the next cells from Parse. See my implementation below:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(Constants.PictureCellIdentifier, forIndexPath: indexPath) as! PictureCell
cell.picture = filtered[indexPath.item]
// see if image already loaded
if !cell.picture.loaded {
cell.loadImage()
}
cell.hashtagLabel.text = "#\(cell.picture.hashtag)"
cell.viewsLabel.text = "\(cell.picture.views) views"
cell.image.image = cell.picture.image
return cell
}
And the actual fetch is inside the cell:
func loadImage() {
if let imageFile = picture.imageData as PFFile? {
image.alpha = 0
imageFile.getDataInBackgroundWithBlock { [unowned self] (imageData: NSData?, err: NSError?) -> Void in
if err == nil {
self.picture.loaded = true
if let imageData = imageData {
let image = UIImage(data: imageData)
self.picture.image = image
dispatch_async(dispatch_get_main_queue()) {
UIView.animateWithDuration(0.35) {
self.image.image = self.picture.image
self.image.alpha = 1
self.layoutIfNeeded()
}
}
}
}
}
}
}
I hope you get a feel of my problem. Having the image fetch inside the cell dequeue thing is pretty gross. Also, if these few snippets doesn't give the full picture, see this github link for the project:
https://github.com/tedcurrent/Anonimg
Thanks all!
/T
Probably a bit late but when loading PFImageView's from the database in a UICollectionView I found this method to be much more efficient, although I'm not entirely sure why. I hope it helps. Use in your cellForItemAtIndexPath in place of your cell.loadImage() function.
if let value = filtered[indexPath.row]["imageColumn"] as? PFFile {
if value.isDataAvailable {
cell.cellImage.file = value //assign the file to the imageView file property
cell.cellImage.loadInBackground() //loads and does the PFFile to PFImageView conversion for you
}
}

TabBarController: Going Tab-To-Tab - fatal error: Array index out of range

I have a TabBarController with two tabs (one TableViewController, one CollectionViewController). My app is crashing at times when I switch from one tab to another. It always crashes on the UITableViewController tab which is a simple Feed. The following breakpoint and info is shown:
0x498e44 <+68>: bl 0x4efb9c ; function signature specialization <Arg[0] = Exploded, Arg[1] = Exploded> of Swift.(_fatalErrorMessage (Swift.StaticString, Swift.StaticString, Swift.StaticString, Swift.UInt) -> ()).(closure #2)
-> 0x498e48 <+72>: trap // **This is the breakpoint
In the FeedView, I have the following and found another breakpoint in line 3:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("feedCell", forIndexPath: indexPath) as! FeedViewCell
let currentItem = feedItems[indexPath.row] // **Other error breakpoint occurs here
let date = currentItem.createdAt
let formatter = NSDateFormatter()
formatter.dateFormat = "hh:mm"
let dateString = formatter.stringFromDate(date!)
cell.userName?.text = currentItem.userName
cell.itemName?.text = currentItem.itemName
cell.timeStamp?.text = dateString
// MARK: Setup image for cell
cell.itemImage?.image = UIImage(named: "1.png")
let image = currentItem.imageFile
image.getDataInBackgroundWithBlock { (imageData: NSData?, error: NSError?) -> Void in
if !(error != nil) {
cell.itemImage?.image = UIImage(data: imageData!)
cell.imageView?.contentMode = .ScaleAspectFit
cell.imageView?.clipsToBounds = true
}
}
return cell
}
func getAndShowFeedItems() { // I call this in ViewWillAppear
feedItems.removeAll(keepCapacity: false)
let getFeedItems = FeedItem.query()
getFeedItems!.orderByDescending("createdAt")
getFeedItems!.findObjectsInBackgroundWithBlock { (objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
for object in objects! {
self.feedItems.append(object as! FeedItem)
if self.feedItems.count == 35 {
break
}
}
} else if error!.code == PFErrorCode.ErrorConnectionFailed.rawValue {
self.showNetworkAlert()
print("there's a networking problem")
}
self.tableView.reloadData()
self.refreshControl?.endRefreshing()
self.resetUserDefaults()
self.scrollToFirstRow()
}
}
NOTE: I Am using Parse.com as my image store, and venueImage is a PFImage which doesn't allow me to unwrap the image the way you would a UIImage.
You have error in getAndShowFeedItems method. The server request can take more time. You are clean your array but don't reload table view. You have a crash if scroll table view before get items. You need change your code for this:
func getAndShowFeedItems() { // I call this in ViewWillAppear
feedItems.removeAll(keepCapacity: false)
self.tableView.reloadData() // Need reload tableview when change it content
let getFeedItems = FeedItem.query()
getFeedItems!.orderByDescending("createdAt")
getFeedItems!.findObjectsInBackgroundWithBlock { (objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
for object in objects! {
self.feedItems.append(object as! FeedItem)
if self.feedItems.count == 35 {
break
}
}
} else if error!.code == PFErrorCode.ErrorConnectionFailed.rawValue {
self.showNetworkAlert()
print("there's a networking problem")
}
self.tableView.reloadData()
self.refreshControl?.endRefreshing()
self.resetUserDefaults()
self.scrollToFirstRow()
}
}
Also you have error in cellForRowAtIndexPath section. You can set wrong image when do scroll. Because table view will reusable invisible cell. So when your finish download image data cell can contain in wrong indexPath. You can rework your code like this.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("feedCell", forIndexPath: indexPath) as! FeedViewCell
let currentItem = feedItems[indexPath.row] // **Other error breakpoint occurs here
let date = currentItem.createdAt
let formatter = NSDateFormatter()
formatter.dateFormat = "hh:mm"
let dateString = formatter.stringFromDate(date!)
cell.userName?.text = currentItem.userName
cell.itemName?.text = currentItem.itemName
cell.timeStamp?.text = dateString
// MARK: Setup image for cell
if let image = UIImage(data: currentItem.imageData)
{
cell.itemImage?.image = image
cell.imageView?.contentMode = .ScaleAspectFit
cell.imageView?.clipsToBounds = true
}else
{
cell.itemImage?.image = UIImage(named: "1.png")
let image = currentItem.imageFile
image.getDataInBackgroundWithBlock { (imageData: NSData?, error: NSError?) -> Void in
if !(error != nil) {
currentItem.imageData = imageData!
let index = feedItems.indexOfObject(currentItem)
if index != NSNotFound
{
tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: index, inSection: 0)], withRowAnimation: .None)
}
}
}
}
return cell
}
The code can contains some inaccuracies I don't compiling it. Also you need add new property in class currentItem variable.
You have a fundamental design error here. You are trying to get data for a cell and set it with that block. But cell objects get re-used, and once a tableview goes offscreen, all cells might get disassociated from the table. What you need to do is fetch data, populate your model (i.e. array or dictionary), then post a block to the main thread that such and such a row needs to be refreshed. If that row is showing, you can get it from the tableview (via the array of visible cells). If its not showing, do nothing, when you are asked for the cell you'll have the data to populate it.
What I do in my apps is to have a separate method that takes a cell, its index, and updates the cell content. When I am asked for a new cell, I allocate one, then call this method. When I get some kind of internal notification that visible cell info might have changed, I call the same method (the point being you put the code that writes cell content in one place, not two).
EDIT: again, your problem is here:
(imageData: NSData?, error: NSError?) -> Void in
if !(error != nil) {
cell.itemImage?.image = UIImage(data: imageData!)
cell.imageView?.contentMode = .ScaleAspectFit
cell.imageView?.clipsToBounds = true
}
You are saving a reference to the "cell" which will in the future not be valid for the information you want to save in it, and the cell itself may have gotten deallocated.
As data comes in from the background, post a block to the main thread and save it in an array or dictionary. That same method can then update all visible cells. You don't need a NSNotification to do this - posting a block back on the main thread will do the trick nicely.

Loading TableViewDataSource after a method finish

I want to call TableViewData Sources method for Seeting up Ui after it has been fethced from parse . With this i am able to fetch
func loadImages() {
var query = PFQuery(className: "TestClass")
query.orderByDescending("objectId")
query.findObjectsInBackgroundWithBlock ({(objects:[AnyObject]!, error: NSError!) in
if(error == nil){
self.getImageData(objects as [PFObject])
}
else{
println("Error in retrieving \(error)")
}
})//findObjectsInBackgroundWithblock - end
}
func getImageData(objects: [PFObject]) {
for object in objects {
let thumbNail = object["image"] as PFFile
println(thumbNail)
thumbNail.getDataInBackgroundWithBlock({
(imageData: NSData!, error: NSError!) -> Void in
if (error == nil) {
var imageDic = NSMutableArray()
self.image1 = UIImage(data:imageData)
//image object implementation
self.imageResources.append(self.image1!)
println(self.image1)
println(self.imageResources.count)
}
}, progressBlock: {(percentDone: CInt )-> Void in
})//getDataInBackgroundWithBlock - end
}//for - end
self.tableView.reloadData()
But not able to populate these fetched data to tableview like this
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
println("in table view")
println(self.imageResources.count)
return imageResources.count+1;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:CustomTableViewCell = tableView.dequeueReusableCellWithIdentifier("customCell") as CustomTableViewCell
var (title, image) = items[indexPath.row]
cell.loadItem(title: title, image: image)
println("message : going upto this line")
println(self.imageResources.count)
var (image1) = imageResources[indexPath.row]
cell.loadItem1(image1: image1)
return cell
}
Then on loaditem i am trying to show up the images and i have writen my own array to populate to the image array but i am geeting a zero value when populating so not able to set it up
Any Help is much appreciated!
You have several problems, all related to concurrency - your load is occurring in the background and in parallel.
The first problem is the use of self.image1 as a temporary variable in the loading process - this variable may be accessed concurrently by multiple threads. You should use a local variable for this purpose.
Second, you are appending to self.imageResources from multiple threads, but Swift arrays are not thread safe.
Third, you need to call reload on your tableview after you have finished loading all of the data, which isn't happening now because you call it while the background operations are still taking place.
Finally, your getImageData function is executing on a background queue, and you must perform UI operations (such as reloading a table) on the main queue.
The simplest option is to change get thumbnail loading to synchronous calls - This means that your thumbnails will load sequentially and may take a bit longer that the multiple parallel tasks but it is easier to manage -
func getImageData(objects: [PFObject]) {
for object in objects {
let thumbNail = object["image"] as PFFile
println(thumbNail)
let imageData? = thumbNail.getData
if (imageData != nil) {
let image1 = UIImage(data:imageData!)
//image object implementation
self.imageResources.append(image1!)
println(self.imageResources.count)
}
}//for - end
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
}
A more sophisticated approach would be to use a dispatch group and keep the parallel image loading. In order to do this you would need to guard the access to the shared array

UITableView and Parse Data

I am having issues setting with Parse data and a UITableView. Everytime I run the application, whether on the simulator or my phone, the app stops working and the only thing the console shows is (lldb). When checking the debugger, only following two lines of code are highlighted.
findTimelineData.findObjectsInBackgroundWithBlock{
and
self.timelineData = array as NSMutableArray
Both have the error: Thread 1: EXC_BREAKPOINT (code=EXC_I386_BPT,subcode=0x0) and im not quite sure what that means...
Here are snippets of my code:
override func viewDidAppear(animated: Bool)
{
self.loadData()
}
func loadData()
{
timelineData.removeAllObjects()
var findTimelineData : PFQuery = PFUser.query()
findTimelineData.findObjectsInBackgroundWithBlock{
(objects: [AnyObject]!, error: NSError!)-> Void in
if error == nil
{
println("No error")
if let object = objects as? [PFObject!]
{
for object in objects
{
self.timelineData.addObject(object)
}
}
let array : NSArray = self.timelineData.reverseObjectEnumerator().allObjects
self.timelineData = array as NSMutableArray
self.tableView.reloadData()
}
}
}
and these would be the cells that i'm trying to load, but the code never gets this far...
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
println("loading cell")
let postCell : LocationTableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as LocationTableViewCell
let post : PFObject = self.timelineData.objectAtIndex(indexPath.row) as PFObject
postCell.imageView.image = post.objectForKey("currentLocation") as? UIImage
postCell.userInfo.text = post.objectForKey("FirstLastName") as? String
// Configure the cell...
return postCell
}
for the full code: http://pastebin.com/MN7qcFhq
this worked for me:
in your code just only replace
self.timelineData = array as NSMutableArray
to
self.timelineData = NSMutableArray(array: array)
Aren't you missing an s in:
if let object = objects as? [PFObject!]
{
for object in objects
{
self.timelineData.addObject(object)
}
}
Should be:
if let objects = objects as? [PFObject!]
{
for object in objects
{
self.timelineData.addObject(object)
}
}
Hey Buddy I got you on this one. I had the same issues but I got help and got it figured out.
This is your Load Function
Try this
func loadData(){
timelineData.removeAll(keepCapacity: false)
var findTimelineData:PFQuery = PFQuery(className:"Sweets")
findTimelineData.findObjectsInBackgroundWithBlock
{
(objects:[AnyObject]! , error:NSError!) -> Void in
if error == nil
{
self.timelineData = objects.reverse() as [PFObject]
//let array:NSArray = self.timelineData.reverseObjectEnumerator().allObjects
println(objects)
// self.timelineData = array as NSMutableArray
self.tableView.reloadData()
}
}
}
Then go to your
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
And change
let sweet:PFObject = self.timelineData.objectAtIndex(indexPath.row) as PFObject
To This
let sweet: PFObject = self.timelineData[indexPath.row] as PFObject
Whats going on is you are mixing some Obj C with Swift , the code you are following was probably written in Beta so make sure you go back and learn the differences. If you are still having problems check all your conditions if(error != nil) should not be in your code.

Resources