I don't understand what is going on. I have an image saved as a PFFile in Parse. I can see it and I know it is there. I want to have it as a cell image. The rest of the code below works fine and the memory addresses of PFFile also print. textLabel and detailTextLabel also fine but the images won't show (even if I delete 'loadInBackground'). Any ideas?
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell? {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! PFTableViewCell!
if cell == nil {
cell = PFTableViewCell(style: .Subtitle, reuseIdentifier: "Cell")
}
if let name = object?["name"] as? String {
cell.textLabel?.text = name
}
if let artist = object?["artist"] as? String {
cell.detailTextLabel?.text = artist
}
if let artwork = object?["artwork"] as? PFFile {
println("we've got an artwork image \(artwork)")
//cell!.imageView!.image = UIImage(named: "placeholder.jpg")
cell.imageView?.file = artwork
cell.imageView?.loadInBackground()
}
return cell
}
Parse just saves an reference to the image in the table, you will have to do another async call to retrieve the message.:
let artwork = object?["artwork"] as PFFile
artwork.getDataInBackgroundWithBlock {
(imageData: NSData?, error: NSError?) -> Void in
if error == nil {
if let imageData = imageData {
let image = UIImage(data:imageData)
}
}
}
Related
My images take a second to load before they appear, which looks bad. On apps such as instagram, the tableview is hidden until the tableview is loaded... how do they do this? I have a loader that I want to display but don't know when to stop it and show tableview and detect the images have first finished loading? Where do I put stopTimer() ?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MainTableViewCell",
for: indexPath) as! MainTableViewCell
let payment = self.payments[indexPath.row]
cell.profilePicture.layer.cornerRadius = cell.profilePicture.frame.size.width / 2
cell.profilePicture.clipsToBounds = true
if let profileImageUrl = payment.picture {
cell.profilePicture.loadImageUsingCacheWithUrlString(profileImageUrl)
}
if payment.message == "none" {
cell.detailsLabel.text = "No Message"
} else {
cell.detailsLabel.text = "\"\(payment.message ?? "")\""
}
}
MY CODE TO FETCH IMAGE IN TABLEVIEW:
let imageCache = NSCache<NSString, AnyObject>()
extension UIImageView {
func loadImageUsingCacheWithUrlString(_ urlString: String) {
self.image = nil
//check cache for image first
if let cachedImage = imageCache.object(forKey: urlString as NSString) as? UIImage {
self.image = cachedImage
return
}
//otherwise fire off a new download
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
//download hit an error so lets return out
if let error = error {
print(error)
return
}
DispatchQueue.main.async(execute: {
if let downloadedImage = UIImage(data: data!) {
imageCache.setObject(downloadedImage, forKey: urlString as NSString)
self.image = downloadedImage
}
})
}).resume()
}
}
You can simple use SDWebImage with cocoaPods and use it for async image downloader with cache support. Your cell will look like after ad SDWebImage.
import SDWebImage
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MainTableViewCell",
for: indexPath) as! MainTableViewCell
let payment = self.payments[indexPath.row]
cell.profilePicture.layer.cornerRadius = cell.profilePicture.frame.size.width / 2
cell.profilePicture.clipsToBounds = true
if let profileImageUrl = payment.picture {
cell.profilePicture.sd_setImage(with: profileImageUrl, placeholderImage: UIImage(named: "yourPlaceholderImage.png"))
}
if payment.message == "none" {
cell.detailsLabel.text = "No Message"
} else {
cell.detailsLabel.text = "\"\(payment.message ?? "")\""
}
}
There is no need to hide tableView for downloading image.
I have a TableView and I am filling it with data retrieved from database. Everything works fine except the images. Because of the cell reuse behaviour, and I am fetching image in cellForRowAtIndexPath. I chose to fetch images in cellForRowAtIndexPath because in the details retrieving function (which is triggered in viewDidLoad), I need to do another request, which is causing other problems (reloading tableview before storing image url)
The problem is that when I scroll fast, the resuable cells bugs while displaying user images
override func viewDidLoad() {
super.viewDidLoad()
fetchData()
}
var theUser =
func fetchData() {
//.. after data is retrieved
var innerDict = [String:String]()
if let user = details.value![key] {
if let name = user["name"] {
// works
innerDict["name"] = name
}
if let image = user["imageName"] {
// gets the image name but at this point I need to;
// a) retrieve the url here (with another call), which will eventually fail
// to catch up with `innerDict` so `innerDict` won't contain `image` variable.
// ie;
myRef.downloadURLWithCompletion { (URL, error) -> Void in }
// b) Store the `image` name in innerDict and download image from url
// in `cellForRowAtIndexPath`. I chose this method:
innerDict["image"] = image
}
user[id] = innerDict
tableView.reloadData()
}
Now the tableView as usual.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = ...
// more stuff
if let imageName = user["image"] {
let storage = FIRStorage.storage()
let storageRef = storage.referenceForURL("gs://bucket.com").child(user[id]).child(imageName)
storageRef.downloadURLWithCompletion { (URL, error) -> Void in
if (error != nil) {
// handle
} else {
// I thought using Haneke would help to cache the image
cell.image.hnk_setImage(URL!)
}
}
}
This is the closest one I could reach. However images bug on displaying when I scroll fast.
Edit:
I also tried using this approach but it's downloading the same image multiple times with this method, so it takes time for the same images to displayed.
islandRef.dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
if (error != nil) {
// Uh-oh, an error occurred!
} else {
let image: UIImage! = UIImage(data: data!)
cell.userImage.hnk_setImage(image, key: "\(userID)")
}
}
However, with top approach the speed was very fast. The only problem of the above code was the glitch when I scroll fast.
Edit 2
var images = [UIImage]()
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("ItemCell", forIndexPath: indexPath) as! ItemDetailTableViewCell
let item = items[indexPath.section][indexPath.row]
if let uid = item["owner"] as? String {
if let user = users[uid] {
if let imageName = user["image"] {
if let img: UIImage = images[indexPath.row] { // crash here "fatal error: Index out of range"
cell.userImage.image = img
}
} else {
let storage = FIRStorage.storage()
let storageRef = storage.referenceForURL("gs://bucket").child(uid).child(imageName)
storageRef.downloadURLWithCompletion { (URL, error) -> Void in
if (error != nil) {
} else {
dispatch_async(dispatch_get_main_queue(), {
cell.userImage.hnk_setImageFromURL(URL!)
self.images[indexPath.row] = cell.image.image!
})
}
}
}
}
}
I think you should save images from url and show images when the cell is going to reuse, hopefully this will fix your glitch
var myImages = [String: UIImage]()
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("ItemCell", forIndexPath: indexPath) as! ItemDetailTableViewCell
let item = items[indexPath.section][indexPath.row]
if let img: UIImage = myImages["\(indexPath.section)\(indexPath.row)"] {
cell.image.image = img
} else {
if let uid = item["owner"] as? String {
if let imageName = user["image"] {
let storage = FIRStorage.storage()
let storageRef = storage.referenceForURL("gs://bucket.com").child(user[id]).child(imageName)
storageRef.downloadURLWithCompletion { (URL, error) -> Void in
if (error != nil) {
cell.image = UIImage(named: "placeholder") // put default Image when failed to download Image
} else {
dispatch_async(dispatch_get_main_queue(), {
cell.image.hnk_setImage(URL!)
// Store the image in to our cache
self.myImages["\(indexPath.section)\(indexPath.row)"]= cell.image.image
})
}
}
}
}
}
I have an async problem - I have a function that loads images from S3, stores them into an array of UIImages (here called Images)
I also have a tableview that loads its cells from firebase fetched data, my question is, how to update the cell image once the async finishes loading ?
I'm also afraid that the queue of images won't match exactly the indexPath.row since some images might load faster than other images.
func download(key:String, myindex:NSIndexPath, myrow:Int) -> NSString {
let path:NSString = NSTemporaryDirectory().stringByAppendingString("image.jpg")
let url:NSURL = NSURL(fileURLWithPath: path as String)
// let downloadingFilePath = downloadingFileURL.path!
let downloadRequest = AWSS3TransferManagerDownloadRequest()
downloadRequest.bucket = "witnesstest/" + rootref.authData.uid
downloadRequest.key = key
downloadRequest.downloadingFileURL = url
switch (downloadRequest.state) {
case .NotStarted, .Paused:
let transferManager = AWSS3TransferManager.defaultS3TransferManager()
transferManager.download(downloadRequest).continueWithBlock({ (task) -> AnyObject! in
if let error = task.error {
if error.domain == AWSS3TransferManagerErrorDomain as String
&& AWSS3TransferManagerErrorType(rawValue: error.code) == AWSS3TransferManagerErrorType.Paused {
print("Download paused.")
} else {
print("download failed: [\(error)]")
}
} else if let exception = task.exception {
print("download failed: [\(exception)]")
} else {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let tempimage:UIImage = UIImage(contentsOfFile: path as String)!
print("dl ok")
self.Images.append(tempimage)
})
}
return nil
})
break
default:
break
}
return path
}
and the cell :
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: MainWitnessTableViewCell = self.tableView.dequeueReusableCellWithIdentifier("RIcell") as! MainWitnessTableViewCell
// populate the cell
let postitem = posts[indexPath.row]
cell.postOwner.text = postitem.author
cell.postContent.text = postitem.content
cell.postDate.text = postitem.createon
let myindex = indexPath
let myrow = indexPath.row
// cell.cellImage.image = Images[indexPath.row] // returns array index out of range
// download(postitem.imagekey, myindex: myindex, myrow: myrow)
return cell
}
You can just set the imageView's image after you download the image and at the same time set postitem's image property (assuming you add one). It's hard for me to understand everything your download method is doing, but I think the gist of what you want is something like:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: MainWitnessTableViewCell = self.tableView.dequeueReusableCellWithIdentifier("RIcell") as! MainWitnessTableViewCell
// populate the cell
let postitem = posts[indexPath.row]
cell.postOwner.text = postitem.author
cell.postContent.text = postitem.content
cell.postDate.text = postitem.createon
//postitem should also have a imageURL property or you should have some way of getting the image url.
if post item.image == nil{
dispatch_queue_t imageFetchQ = dispatch_queue_create("image fetcher", NULL)
dispatch_async(imageFetchQ, ^{
let imageData = NSData(contentsOfURL: postitem.imageURL)
let image = UIImage(data: imageData)
postitem.image = image
dispatch_async(dispatch_get_main_queue(), ^{
//the UITableViewCell may have been dequeued and reused so check if the cell for the indexPath != nil
if tableView.cellForRowAtIndexPath(indexPath) != nil{
cell.imageView.image = image
}
})
})
return cell
}
I would recommend you create a cache of images and the indices they're supposed to be associated with, and then instead of loading all the images in your function, you would create the table view, and then for that specific cell ask for the image from your server or from the cache if it's already there, rather then trying to download them all async at one time
I'm building an app based around pictures. So it's very important for me to retrieve all images asyncronously like Instagram does. I understand that this function...
var query = PFQuery(className: "Post")
query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if let objects = objects as! [PFObjects] {
for object in objects {
objectsArray.append(object)
}
}
}
...is asynchronous. But I want a way to load images from Parse into a table asynchronously so it loads images while scrolling.
You should take a look at PFImageView's function loadInBackground().
For example, if you are using a PFTableViewController with a PFTableViewCell, you can do the following
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("CustomCell") as! CustomTableViewCell!
if cell == nil {
cell = CustomTableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "CustomCell")
}
if let name = object?["name"] as? String {
cell.nameLbl.text = name
}
var initialThumbnail = UIImage(named: "placeholder")
cell.imgView.image = initialThumbnail
if let thumbnail = object?["photo"] as? PFFile {
cell.imgView.file = thumbnail
cell.imgView.loadInBackground()
}
return cell
}
with PFTableViewCell having
class CustomCell: PFTableViewCell {
#IBOutlet weak var nameLbl: UILabel!
#IBOutlet weak var imgView: PFImageView!
}
Also from another SO reply, you can try this:
let userImageFile = userPhoto["imageFile"] as PFFile
userImageFile.getDataInBackgroundWithBlock {
(imageData: NSData!, error: NSError!) -> Void in
if !error {
let image = UIImage(data:imageData)
}
}
I am having trouble working with images from Parse. I have created a function to take all image files from a Parse class, and save them into a dictionary (with key value being the objectId). Any idea why and/or how to fix? Below are the codes:
private func generateDictionaryOfImages(imageKeyToLookup: String, dictionaryKeyToReturn: String) {
for object in objectList {
var currentObjectId = object.objectId!
var image = UIImage()
let imageFile = object[imageKeyToLookup] as! PFFile
imageFile.getDataInBackgroundWithBlock({ (data: NSData?, error: NSError?) -> Void in
if data != nil {
if let imageData = data {
image = UIImage(data: imageData)!
}
}
})
// This imageDictionary is copied to the downloadedClientImages variable separately
self.imageDictionary[currentObjectId] = image
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Profile") as! ProfileTableViewCell
let title: String = grandCentral.returnValueForKeyAtIndexPath(indexPath.row, key: "forename") as! String
let currentUserId: String = grandCentral.returnCurrentUserId()
// Error is generated here. '#lvalue UIImage??' is not convertible to 'UIImage'
let image: UIImage = downloadedClientImages[currentUserId]
cell.setCell(title, image: image)
return cell
}
You need to unwrap optional after you access it from a dictionary.
if let image = downloadedClientImages[currentUserId] {
cell.setCell(title, image: image)
}